Fixes #5950 -- Fix Vue3 reactivity on flow uploads.

This commit is contained in:
Buster Neece 2022-12-19 19:45:23 -06:00
parent ec50492047
commit 4c61d6288a
No known key found for this signature in database
GPG Key ID: F1D2E64A0005E80E
1 changed files with 143 additions and 125 deletions

View File

@ -1,16 +1,19 @@
<template>
<div class="flow-upload">
<div class="upload-progress">
<template v-for="(file, _) in files">
<div v-if="file.is_visible" class="uploading-file pt-1" :id="'file_upload_' + file.uniqueIdentifier"
:class="{ 'text-success': file.is_completed, 'text-danger': file.error }">
<template v-for="(file, key) in files.value" :key="key">
<div v-if="file.isVisible" class="uploading-file pt-1" :id="'file_upload_' + file.uniqueIdentifier"
:class="{ 'text-success': file.isCompleted, 'text-danger': file.error }">
<h6 class="fileuploadname m-0">{{ file.name }}</h6>
<b-progress v-if="!file.is_completed" :value="file.progress_percent" :max="100"
show-progress class="h-15 my-1"></b-progress>
<div v-if="!file.isCompleted" class="progress h-15 my-1">
<div class="progress-bar h-15" role="progressbar" :style="{width: file.progressPercent+'%'}"
:aria-valuenow="file.progressPercent" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="upload-status" v-if="file.error">
{{ file.error }}
</div>
<div class="size">{{ formatFileSize(file.size) }}</div>
<div class="size">{{ file.size }}</div>
</div>
</template>
</div>
@ -64,131 +67,146 @@ div.flow-upload {
}
</style>
<script>
<script setup>
import formatFileSize from '~/functions/formatFileSize.js';
import Flow from '@flowjs/flow.js';
import Icon from './Icon';
import _ from 'lodash';
import {computed, onMounted, onUnmounted, reactive, ref} from "vue";
import Flow from "@flowjs/flow.js";
export default {
name: 'FlowUpload',
components: {Icon},
emits: ['complete', 'success', 'error'],
props: {
targetUrl: String,
allowMultiple: {
type: Boolean,
default: false
},
validMimeTypes: {
type: Array,
default() {
return ['*'];
}
},
flowConfiguration: {
type: Object,
default() {
return {};
}
const props = defineProps({
targetUrl: String,
allowMultiple: {
type: Boolean,
default: false
},
validMimeTypes: {
type: Array,
default() {
return ['*'];
}
},
data() {
return {
flow: null,
files: []
};
},
mounted() {
let defaultConfig = {
target: () => {
return this.targetUrl
},
singleFile: !this.allowMultiple,
headers: {
'Accept': 'application/json',
'X-API-CSRF': App.api_csrf
},
withCredentials: true,
allowDuplicateUploads: true,
fileParameterName: 'file_data',
uploadMethod: 'POST',
testMethod: 'GET',
method: 'multipart',
maxChunkRetries: 3,
testChunks: false
};
let config = _.defaultsDeep({}, this.flowConfiguration, defaultConfig);
this.flow = new Flow(config);
this.flow.assignBrowse(this.$refs.file_browse_target);
this.flow.assignDrop(this.$refs.file_drop_target);
this.flow.on('fileAdded', (file) => {
file.progress_percent = 0;
file.is_completed = false;
file.error = null;
file.is_visible = true;
this.files.push(file);
return true;
});
this.flow.on('filesSubmitted', () => {
this.flow.upload();
});
this.flow.on('fileProgress', (file) => {
file.progress_percent = file.progress() * 100;
});
this.flow.on('fileSuccess', (file, message) => {
console.log(message);
file.is_completed = true;
let messageJson = JSON.parse(message);
this.$emit('success', file, messageJson);
});
this.flow.on('error', (message, file, chunk) => {
console.error(message, file, chunk);
let messageText = this.$gettext('Could not upload file.');
try {
if (typeof message !== 'undefined') {
let messageJson = JSON.parse(message);
if (typeof messageJson.message !== 'undefined') {
messageText = messageJson.message;
if (messageText.indexOf(': ') > -1) {
messageText = messageText.split(': ')[1];
}
}
}
} catch (e) {
}
file.error = messageText;
this.$emit('error', file, messageText);
});
this.flow.on('complete', () => {
_.forEach(this.files, (file) => {
file.is_visible = false;
});
this.$emit('complete');
});
},
computed: {
validMimeTypesList() {
return this.validMimeTypes.join(', ');
}
},
methods: {
formatFileSize(bytes) {
return formatFileSize(bytes);
flowConfiguration: {
type: Object,
default() {
return {};
}
}
};
});
const emit = defineEmits(['complete', 'success', 'error']);
const validMimeTypesList = computed(() => {
return props.validMimeTypes.join(', ');
});
let flow = null;
const files = reactive({
value: {},
push(file) {
this.value[file.uniqueIdentifier] = {
name: file.name,
uniqueIdentifier: file.uniqueIdentifier,
size: formatFileSize(file.size),
isVisible: true,
isCompleted: false,
progressPercent: 0,
error: null
};
},
get(file) {
return this.value[file.uniqueIdentifier] ?? {};
},
hideAll() {
_.forEach(this.value, (file) => {
file.isVisible = false;
});
},
reset() {
this.value = {};
}
});
const file_browse_target = ref(); // Template Ref
const file_drop_target = ref(); // Template Ref
onMounted(() => {
let defaultConfig = {
target: () => {
return props.targetUrl
},
singleFile: !props.allowMultiple,
headers: {
'Accept': 'application/json',
'X-API-CSRF': App.api_csrf
},
withCredentials: true,
allowDuplicateUploads: true,
fileParameterName: 'file_data',
uploadMethod: 'POST',
testMethod: 'GET',
method: 'multipart',
maxChunkRetries: 3,
testChunks: false
};
let config = _.defaultsDeep({}, props.flowConfiguration, defaultConfig);
flow = new Flow(config);
flow.assignBrowse(file_browse_target.value);
flow.assignDrop(file_drop_target.value);
flow.on('fileAdded', (file) => {
files.push(file);
return true;
});
flow.on('filesSubmitted', () => {
flow.upload();
});
flow.on('fileProgress', (file) => {
files.get(file).progressPercent = parseInt(file.progress() * 100);
});
flow.on('fileSuccess', (file, message) => {
files.get(file).isCompleted = true;
let messageJson = JSON.parse(message);
emit('success', file, messageJson);
});
flow.on('error', (message, file, chunk) => {
console.error(message, file, chunk);
let messageText = this.$gettext('Could not upload file.');
try {
if (typeof message !== 'undefined') {
let messageJson = JSON.parse(message);
if (typeof messageJson.message !== 'undefined') {
messageText = messageJson.message;
if (messageText.indexOf(': ') > -1) {
messageText = messageText.split(': ')[1];
}
}
}
} catch (e) {
}
files.get(file).error = messageText;
emit('error', file, messageText);
});
flow.on('complete', () => {
files.hideAll();
emit('complete');
});
});
onUnmounted(() => {
flow = null;
files.reset();
});
</script>