mirror of
https://github.com/AzuraCast/AzuraCast.git
synced 2024-06-13 20:56:36 +00:00
253 lines
6.4 KiB
Vue
253 lines
6.4 KiB
Vue
<template>
|
|
<div class="flow-upload">
|
|
<div class="upload-progress">
|
|
<template
|
|
v-for="(file, key) in files.value"
|
|
:key="key"
|
|
>
|
|
<div
|
|
v-if="file.isVisible"
|
|
:id="'file_upload_' + file.uniqueIdentifier"
|
|
class="uploading-file pt-1"
|
|
:class="{ 'text-success': file.isCompleted, 'text-danger': file.error }"
|
|
>
|
|
<h6 class="fileuploadname m-0">
|
|
{{ file.name }}
|
|
</h6>
|
|
<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
|
|
v-if="file.error"
|
|
class="upload-status"
|
|
>
|
|
{{ file.error }}
|
|
</div>
|
|
<div class="size">
|
|
{{ file.size }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<div
|
|
ref="$fileDropTarget"
|
|
class="file-drop-target"
|
|
>
|
|
{{ $gettext('Drag file(s) here to upload or') }}
|
|
<button
|
|
ref="$fileBrowseTarget"
|
|
class="file-upload btn btn-primary text-center ml-1"
|
|
type="button"
|
|
>
|
|
<icon icon="cloud_upload" />
|
|
{{ $gettext('Select File') }}
|
|
</button>
|
|
<small class="file-name" />
|
|
<input
|
|
type="file"
|
|
:accept="validMimeTypesList"
|
|
:multiple="allowMultiple"
|
|
style="visibility: hidden; position: absolute;"
|
|
>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import formatFileSize from '~/functions/formatFileSize.js';
|
|
import Icon from './Icon.vue';
|
|
import {defaultsDeep, forEach, toInteger} from 'lodash';
|
|
import {computed, onMounted, onUnmounted, reactive, ref} from "vue";
|
|
import Flow from "@flowjs/flow.js";
|
|
import {useAzuraCast} from "~/vendor/azuracast";
|
|
import {useTranslate} from "~/vendor/gettext";
|
|
|
|
const props = defineProps({
|
|
targetUrl: String,
|
|
allowMultiple: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
validMimeTypes: {
|
|
type: Array,
|
|
default() {
|
|
return ['*'];
|
|
}
|
|
},
|
|
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 $fileBrowseTarget = ref(); // Template Ref
|
|
const $fileDropTarget = ref(); // Template Ref
|
|
|
|
const {apiCsrf} = useAzuraCast();
|
|
|
|
const {$gettext} = useTranslate();
|
|
|
|
onMounted(() => {
|
|
let defaultConfig = {
|
|
target: () => {
|
|
return props.targetUrl
|
|
},
|
|
singleFile: !props.allowMultiple,
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'X-API-CSRF': apiCsrf
|
|
},
|
|
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($fileBrowseTarget.value);
|
|
flow.assignDrop($fileDropTarget.value);
|
|
|
|
flow.on('fileAdded', (file) => {
|
|
files.push(file);
|
|
return true;
|
|
});
|
|
|
|
flow.on('filesSubmitted', () => {
|
|
flow.upload();
|
|
});
|
|
|
|
flow.on('fileProgress', (file) => {
|
|
files.get(file).progressPercent = toInteger(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 = $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>
|
|
|
|
<style lang="scss">
|
|
div.flow-upload {
|
|
div.upload-progress {
|
|
padding: 4px 0;
|
|
|
|
& > div {
|
|
padding: 3px 0;
|
|
}
|
|
|
|
.error {
|
|
color: #a00;
|
|
}
|
|
|
|
.progress {
|
|
margin-bottom: 5px;
|
|
|
|
.progress-bar {
|
|
border-bottom-width: 10px;
|
|
|
|
&::after {
|
|
height: 10px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
div.file-drop-target {
|
|
padding: 25px 0;
|
|
text-align: center;
|
|
|
|
input {
|
|
display: inline;
|
|
}
|
|
}
|
|
}
|
|
</style>
|