Browse Source

Implemented error handling for file size limits and other errors

pull/170/head
Michael Olund 5 years ago
parent
commit
19af0727d7
3 changed files with 150 additions and 59 deletions
  1. +15
    -2
      vue/src/components/Uploader/Image.vue
  2. +12
    -3
      vue/src/components/Uploader/ItemTile.vue
  3. +123
    -54
      vue/src/components/Uploader/Main.vue

vue/src/components/Uploader/UploadedImage.vue → vue/src/components/Uploader/Image.vue View File

@ -80,6 +80,14 @@ export default {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
z-index: 2; z-index: 2;
display: flex;
align-items: center;
img {
position: absolute;
top: 0;
left: 0;
}
i.fa-file-pdf-o { i.fa-file-pdf-o {
color: #F40F02; color: #F40F02;
@ -95,7 +103,6 @@ export default {
content: "\f06e"; content: "\f06e";
position: absolute; position: absolute;
left: 58px; left: 58px;
top: 65px;
font-size: 43px; font-size: 43px;
color: transparent; color: transparent;
} }
@ -129,13 +136,19 @@ export default {
left: 130px; left: 130px;
background-color: white; background-color: white;
border-radius: 10px; border-radius: 10px;
height: 22px;
height: 18px;
line-height: 1; line-height: 1;
z-index: 4; z-index: 4;
i.fa { i.fa {
color: #365EBE; color: #365EBE;
font-size: 23px; font-size: 23px;
&::before {
display: block;
margin-top: -2px;
margin-left: -1px;
}
} }
} }
} }

+ 12
- 3
vue/src/components/Uploader/ItemTile.vue View File

@ -3,7 +3,7 @@
<uploaded-image :file="file" :image-style="imageStyle" @imageclick="showPreview" @removeclick="$emit('remove')" /> <uploaded-image :file="file" :image-style="imageStyle" @imageclick="showPreview" @removeclick="$emit('remove')" />
<div class="bottom-wrapper"> <div class="bottom-wrapper">
<div class="item-text"> <div class="item-text">
{{file.name}} ({{ Math.round(file.size/1024 * 100) / 100 }}KB)
{{file.name}} <span class="no-wrap">({{ Math.round(file.size/1024/1024 * 100) / 100 }} MB)</span>
</div> </div>
<div class="button-wrapper"> <div class="button-wrapper">
<div v-if="!file.active && file.success && !isPdf"> <div v-if="!file.active && file.success && !isPdf">
@ -20,7 +20,7 @@
<i class="fa fa-undo fa-flip-horizontal"></i> <i class="fa fa-undo fa-flip-horizontal"></i>
</button> </button>
</div> </div>
<div class="alert alert-danger" style="padding: 4px; margin-bottom: 0" v-if="file.error">Upload Error</div>
<div class="alert alert-danger" v-if="file.error">Upload Error</div>
</div> </div>
</div> </div>
</div> </div>
@ -30,7 +30,7 @@
</template> </template>
<script> <script>
import UploadedImage from './UploadedImage'
import UploadedImage from './Image'
import ProgressBar from './ProgressBar' import ProgressBar from './ProgressBar'
export default { export default {
@ -77,6 +77,10 @@ export default {
padding: 5px; padding: 5px;
line-height: 1.05; line-height: 1.05;
font-size: 0.95em; font-size: 0.95em;
.no-wrap {
white-space: nowrap;
}
} }
.button-wrapper { .button-wrapper {
@ -89,6 +93,11 @@ export default {
border: 1px solid silver; border: 1px solid silver;
background-color: #F2F2F2; background-color: #F2F2F2;
margin-bottom: 10px; margin-bottom: 10px;
.alert-danger {
margin-bottom: 0;
padding: 0;
}
} }
} }
</style> </style>


+ 123
- 54
vue/src/components/Uploader/Main.vue View File

@ -1,57 +1,82 @@
<template> <template>
<div> <div>
<h5 class="uploader-label"> <h5 class="uploader-label">
{{ formInfo.preText }}
{{ formDef.preText }}
<a href="javascript:void(0)" :id="'Tooltip-' + uniqueId"> <a href="javascript:void(0)" :id="'Tooltip-' + uniqueId">
{{ formInfo.name }} <i class="fa fa-question-circle"></i>
{{ formDef.name }} <i class="fa fa-question-circle"></i>
</a> </a>
<strong v-if="party === 1"> - For You</strong> <strong v-if="party === 1"> - For You</strong>
<strong v-if="party === 2"> - For Your Spouse</strong> <strong v-if="party === 2"> - For Your Spouse</strong>
</h5> </h5>
<tooltip :text="formInfo.help" :target="'#Tooltip-' + uniqueId"></tooltip>
<tooltip :text="formDef.help" :target="'#Tooltip-' + uniqueId"></tooltip>
<label :for="inputId" class="sr-only"> <label :for="inputId" class="sr-only">
{{ formInfo.preText }} {{ formInfo.name }}
{{ formDef.preText }} {{ formDef.name }}
<span v-if="party === 1"> - For You</span> <span v-if="party === 1"> - For You</span>
<span v-if="party === 2"> - For Your Spouse</span> <span v-if="party === 2"> - For Your Spouse</span>
</label> </label>
<div @dragover="draggingOn" @dragenter="draggingOn" @dragleave="draggingOff" @dragend="draggingOff" @drop="draggingOff">
<div @dragover="dragOn" @dragenter="dragOn" @dragleave="dragOff" @dragend="dragOff" @drop="dragOff">
<file-upload <file-upload
ref="upload" ref="upload"
v-model="files" v-model="files"
:multiple="true" :multiple="true"
:maximum="maxFiles"
:size="maxMegabytes * 1024 * 1024"
:drop="true" :drop="true"
:drop-directory="false" :drop-directory="false"
:post-action="postAction" :post-action="postAction"
:input-id="inputId" :input-id="inputId"
name="file" name="file"
:class="['drop-zone', dragging ? 'dragging' : '']" :class="['drop-zone', dragging ? 'dragging' : '']"
:data="inputIdentifiers"
:data="inputKeys"
@input-file="inputFile" @input-file="inputFile"
@input-filter="inputFilter"> @input-filter="inputFilter">
<div v-if="files.length === 0" class="placeholder"> <div v-if="files.length === 0" class="placeholder">
<i class="fa fa-plus-circle"></i><br> <i class="fa fa-plus-circle"></i><br>
<em>Drag and Drop the PDF document or JPG pages here,<br>or click here to Browse for files.</em> <em>Drag and Drop the PDF document or JPG pages here,<br>or click here to Browse for files.</em>
</div> </div>
<div v-else class="cards">
<div v-for="(file, index) in files" v-bind:key="index" class="card">
<item-tile
:file="file"
:index="index"
:file-count="files.length"
@remove="remove(file)"
@moveup="moveUp(index)"
@movedown="moveDown(index)"
@rotateleft="rotateLeft(index)"
@rotateright="rotateRight(index)"/>
<template v-else>
<div class="text-danger error-top" v-if="fileErrors === 1">
<strong>One file has failed to upload to the server. Please remove the file marked 'Upload Error' and try uploading it again.</strong>
</div> </div>
<div class="card upload-button">
<div class="upload-button-wrapper">
<i class="fa fa-plus-circle"></i>
<div class="text-danger error-top" v-if="fileErrors > 1">
<strong>Some files have failed to upload to the server. Please remove the files marked 'Upload Error' and try uploading them again.</strong>
</div>
<div class="text-danger error-top" v-if="tooBig">
<strong>The total of all uploaded files for this form cannot exceed {{ maxMegabytes }} MB.
Please reduce the size of your files so the total is below this limit.</strong>
</div>
<div class="cards">
<div v-for="(file, index) in files" v-bind:key="index" class="card">
<item-tile
:file="file"
:index="index"
:file-count="files.length"
@remove="remove(file)"
@moveup="moveUp(index)"
@movedown="moveDown(index)"
@rotateleft="rotateLeft(index)"
@rotateright="rotateRight(index)"/>
</div>
<div class="card upload-button" v-if="!tooBig">
<div class="upload-button-wrapper">
<i class="fa fa-plus-circle"></i>
</div>
</div> </div>
</div>
<div class="text-danger pull-right error-bottom" v-if="tooBig">
<em>
<strong>
(Total {{ Math.round(totalSize/1024/1024 * 100) / 100 }}
MB of {{ maxMegabytes }} MB)
</strong>
</em>
</div> </div>
</div>
</template>
</file-upload> </file-upload>
</div> </div>
<div class="pull-right" v-if="!tooBig">
<em>(Maximum {{ maxMegabytes }} MB)</em>
</div>
<modal ref="warningModal" v-model="showWarning"> <modal ref="warningModal" v-model="showWarning">
{{ warningText }} {{ warningText }}
</modal> </modal>
@ -62,7 +87,7 @@
import VueUploadComponent from 'vue-upload-component' import VueUploadComponent from 'vue-upload-component'
import { Tooltip, Modal } from 'uiv'; import { Tooltip, Modal } from 'uiv';
import ItemTile from './ItemTile' import ItemTile from './ItemTile'
import Forms from "../../utils/forms";
import FormDefinitions from "../../utils/forms";
import rotateFix from '../../utils/rotation'; import rotateFix from '../../utils/rotation';
export default { export default {
@ -72,6 +97,8 @@ export default {
}, },
data: function () { data: function () {
return { return {
maxFiles: 30,
maxMegabytes: 10,
files: [], files: [],
dragging: false, dragging: false,
showWarning: false, showWarning: false,
@ -86,26 +113,47 @@ export default {
Modal Modal
}, },
computed: { computed: {
uniqueId() {
if (this.party === 0) {
return this.docType;
}
return this.docType + this.party;
},
inputId() { inputId() {
return "Uploader-" + this.uniqueId; return "Uploader-" + this.uniqueId;
}, },
formInfo() {
return Forms[this.docType];
inputKeys() {
return {
doc_type: this.docType,
party_code: this.party
};
},
formDef() {
return FormDefinitions[this.docType];
}, },
postAction() { postAction() {
return this.$parent.proxyRootPath + "poc/storage" return this.$parent.proxyRootPath + "poc/storage"
}, },
inputIdentifiers() {
return {
doc_type: this.docType,
party_code: this.party
};
uniqueId() {
if (this.party === 0) {
return this.docType;
}
return this.docType + this.party;
},
totalSize() {
let size = 0;
this.files.forEach((file) => {
if (!file.error) {
size += file.size;
}
});
return size;
},
fileErrors() {
let count = 0;
this.files.forEach((file) => {
if (file.error) {
count++;
}
});
return count;
},
tooBig() {
return this.totalSize > this.maxMegabytes * 1024 * 1024;
} }
}, },
methods: { methods: {
@ -122,8 +170,7 @@ export default {
// Get the response status code (we can use this for error handling) // Get the response status code (we can use this for error handling)
if (newFile.xhr.status !== 200) { if (newFile.xhr.status !== 200) {
// todo: handler errors // todo: handler errors
this.warningText = 'Error: ' + newFile.xhr.statusText;
this.showWarning = true;
this.showError('Error: ' + newFile.xhr.statusText);
console.log('status', newFile.xhr.status) console.log('status', newFile.xhr.status)
} }
} }
@ -134,29 +181,44 @@ export default {
if (newFile && !oldFile) { if (newFile && !oldFile) {
// Filter non-image file // Filter non-image file
if (!/\.(jpeg|jpg|gif|png|pdf)$/i.test(newFile.name)) { if (!/\.(jpeg|jpg|gif|png|pdf)$/i.test(newFile.name)) {
this.warningText = 'Unsupported file type. Allowed extensions are jpeg, jpg, gif,png and pdf.';
this.showWarning = true;
this.showError('Unsupported file type. Allowed extensions are jpeg, jpg, gif,png and pdf.');
return prevent() return prevent()
} }
this.files.forEach((file) => { this.files.forEach((file) => {
// prevent duplicates (based on filename and length) // prevent duplicates (based on filename and length)
if (file.name === newFile.name && file.length === newFile.length) { if (file.name === newFile.name && file.length === newFile.length) {
this.warningText = 'Duplicate file: ' + newFile.name;
this.showWarning = true;
return prevent();
this.showError('Duplicate file: ' + newFile.name);
return prevent();
} }
}); });
} }
if (newFile) { if (newFile) {
// make sure the user isn't uploading more MB of files than allowed
if (this.totalSize > this.maxMegabytes * 1024 * 1024) {
this.showError('The total of all uploaded files for this form cannot exceed ' + this.maxMegabytes + ' MB. Please reduce the size of your files so the total is below this limit.');
// only allow one file over the limit (so we can show the red messaging on the screen)
let previousTotalSize = 0;
this.files.forEach((file) => {
if ((file.name !== newFile.name || file.size !== newFile.size) && !file.error) {
previousTotalSize += file.size;
}
});
// if the user is more than one file over the limit, then block the upload
if (previousTotalSize > this.maxMegabytes * 1024 * 1024) {
this.$refs.upload.remove(newFile);
return prevent();
}
}
// if it's a PDF, make sure it's the only item being uploaded // if it's a PDF, make sure it's the only item being uploaded
if (newFile.type === 'application/pdf') { if (newFile.type === 'application/pdf') {
if (this.files.length > 0) { if (this.files.length > 0) {
if (this.files[0].name != newFile.name || this.files[0].length != newFile.length) { if (this.files[0].name != newFile.name || this.files[0].length != newFile.length) {
this.warningText = 'Only one PDF is allowed per form, and PDF documents cannot be combined with images.';
this.showWarning = true;
this.showError('Only one PDF is allowed per form, and PDF documents cannot be combined with images.');
this.$refs.upload.remove(newFile); this.$refs.upload.remove(newFile);
return prevent(); return prevent();
} }
@ -166,13 +228,12 @@ export default {
// if it's not a PDF, make sure there are no PDFs already uplaoded // if it's not a PDF, make sure there are no PDFs already uplaoded
this.files.forEach((file) => { this.files.forEach((file) => {
if (file.type === 'application/pdf') { if (file.type === 'application/pdf') {
this.warningText = 'PDF documents cannot be combined with images.';
this.showWarning = true;
this.showError('PDF documents cannot be combined with images.');
this.$refs.upload.remove(newFile); this.$refs.upload.remove(newFile);
return prevent(); return prevent();
} }
}); });
}
}
// Add extra data to to the file object // Add extra data to to the file object
newFile.objectURL = '' newFile.objectURL = ''
@ -213,12 +274,16 @@ export default {
this.files[index].rotation += 90; this.files[index].rotation += 90;
this.isDirty = true; this.isDirty = true;
}, },
draggingOn() {
dragOn() {
this.dragging = true; this.dragging = true;
}, },
draggingOff() {
dragOff() {
this.dragging = false; this.dragging = false;
}, },
showError(message) {
this.warningText = message;
this.showWarning = true;
},
saveMetaData() { saveMetaData() {
let allFiles = []; let allFiles = [];
this.files.forEach((file) => { this.files.forEach((file) => {
@ -259,10 +324,6 @@ export default {
border-radius: 6px; border-radius: 6px;
padding: 18px; padding: 18px;
&:hover .fa-plus-circle {
opacity: 0.85;
}
&.dragging { &.dragging {
background-color: #F2E3F2; background-color: #F2E3F2;
} }
@ -295,6 +356,14 @@ export default {
.placeholder { .placeholder {
text-align: center; text-align: center;
} }
.error-top {
padding-bottom: 16px;
}
.error-bottom {
margin-bottom: -10px;
}
} }
h5.uploader-label { h5.uploader-label {


Loading…
Cancel
Save