Add Single file upload component
This commit is contained in:
parent
c4eb67c09f
commit
3b8a129fa4
|
@ -0,0 +1,14 @@
|
||||||
|
<template>
|
||||||
|
<svg x="0px" y="0px" viewBox="0 0 511.626 511.627" xml:space="preserve" fill="currentColor">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M392.857,292.354h-18.274c-2.669,0-4.859,0.855-6.563,2.573c-1.718,1.708-2.573,3.897-2.573,6.563v91.361 c0,12.563-4.47,23.315-13.415,32.262c-8.945,8.945-19.701,13.414-32.264,13.414H82.224c-12.562,0-23.317-4.469-32.264-13.414 c-8.945-8.946-13.417-19.698-13.417-32.262V155.31c0-12.562,4.471-23.313,13.417-32.259c8.947-8.947,19.702-13.418,32.264-13.418 h200.994c2.669,0,4.859-0.859,6.57-2.57c1.711-1.713,2.566-3.9,2.566-6.567V82.221c0-2.662-0.855-4.853-2.566-6.563 c-1.711-1.713-3.901-2.568-6.57-2.568H82.224c-22.648,0-42.016,8.042-58.102,24.125C8.042,113.297,0,132.665,0,155.313v237.542 c0,22.647,8.042,42.018,24.123,58.095c16.086,16.084,35.454,24.13,58.102,24.13h237.543c22.647,0,42.017-8.046,58.101-24.13 c16.085-16.077,24.127-35.447,24.127-58.095v-91.358c0-2.669-0.856-4.859-2.574-6.57 C397.709,293.209,395.519,292.354,392.857,292.354z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M506.199,41.971c-3.617-3.617-7.905-5.424-12.85-5.424H347.171c-4.948,0-9.233,1.807-12.847,5.424 c-3.617,3.615-5.428,7.898-5.428,12.847s1.811,9.233,5.428,12.85l50.247,50.248L198.424,304.067 c-1.906,1.903-2.856,4.093-2.856,6.563c0,2.479,0.953,4.668,2.856,6.571l32.548,32.544c1.903,1.903,4.093,2.852,6.567,2.852 s4.665-0.948,6.567-2.852l186.148-186.148l50.251,50.248c3.614,3.617,7.898,5.426,12.847,5.426s9.233-1.809,12.851-5.426 c3.617-3.616,5.424-7.898,5.424-12.847V54.818C511.626,49.866,509.813,45.586,506.199,41.971z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</template>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<svg height="426.66667pt" viewBox="0 0 426.66667 426.66667" width="426.66667pt" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="m405.332031 192h-170.664062v-170.667969c0-11.773437-9.558594-21.332031-21.335938-21.332031-11.773437 0-21.332031 9.558594-21.332031 21.332031v170.667969h-170.667969c-11.773437 0-21.332031 9.558594-21.332031 21.332031 0 11.777344 9.558594 21.335938 21.332031 21.335938h170.667969v170.664062c0 11.777344 9.558594 21.335938 21.332031 21.335938 11.777344 0 21.335938-9.558594 21.335938-21.335938v-170.664062h170.664062c11.777344 0 21.335938-9.558594 21.335938-21.335938 0-11.773437-9.558594-21.332031-21.335938-21.332031zm0 0" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
|
@ -0,0 +1,117 @@
|
||||||
|
<template>
|
||||||
|
<label class="flex flex-col" :for="id">
|
||||||
|
<span v-if="label" class="text-sm font-semibold text-gray-400">
|
||||||
|
{{ label }}
|
||||||
|
<span v-show="required" class="text-red-800"> *</span>
|
||||||
|
</span>
|
||||||
|
<div class="h-[35px] border-2 border-solid relative rounded-lg cursor-pointer flex-none border-gray-600 text-gray-300 bg-gray-700">
|
||||||
|
<div class="flex items-center justify-center h-full" v-if="inner === null">
|
||||||
|
<div class="relative text-sm text-gray-300 leading-none">Klicken oder Datei hierhin ziehen zum hochladen</div>
|
||||||
|
<input class="hidden" ref="uploader" @change="upload($event.target.files)" type="file" :name="name" :id="id" :multiple="false" />
|
||||||
|
<div class="absolute w-full h-full top-0 left-0 cursor-pointer" @drop="onDropping" @dragenter="onDragEnter" @dragover="onDragOver" @dragleave="onDragLeave"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center h-full space-x-2 px-2" v-else>
|
||||||
|
<img :src="inner.icon" class="w-6 h-6" />
|
||||||
|
<div v-text="inner.file_name" class="text-sm text-gray-300 leading-none grow"></div>
|
||||||
|
<a href="#" @click.prevent="onDelete" v-tooltip="`Löschen`" class="flex justify-center items-center w-6 h-6 rounded-full bg-red-200">
|
||||||
|
<trash-icon class="text-red-800 w-3 h-3"></trash-icon>
|
||||||
|
</a>
|
||||||
|
<a :href="inner.original_url" v-tooltip="`Öffnen`" class="flex justify-center items-center w-6 h-6 rounded-full bg-primary-700" target="_BLANK">
|
||||||
|
<external-icon class="text-primary-200 w-3 h-3"></external-icon>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {watch, ref, inject} from 'vue';
|
||||||
|
import TrashIcon from './TrashIcon.vue';
|
||||||
|
import ExternalIcon from './ExternalIcon.vue';
|
||||||
|
import useReadFile from '../composables/useReadFile.js';
|
||||||
|
import {useToast} from 'vue-toastification';
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
const axios = inject('axios');
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const inner = ref(null);
|
||||||
|
const {dropping, onDragEnter, onDragOver, onDragLeave, processDrop, read} = useReadFile();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
|
collection: {
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
parentName: {
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
parentId: {
|
||||||
|
required: true,
|
||||||
|
validator: (value) => typeof value === 'number' || value === null,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
required: false,
|
||||||
|
default: () => null,
|
||||||
|
validator: (value) => value === null || typeof value === 'string',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function parseValue(v) {
|
||||||
|
if (v === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (v.fallback) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reload() {
|
||||||
|
var response = await axios.get(`/mediaupload/${props.parentName}/${props.parentId}/${props.collection}`);
|
||||||
|
inner.value = parseValue(response.data);
|
||||||
|
emit('update:modelValue', inner.value.id);
|
||||||
|
}
|
||||||
|
async function onDropping(e) {
|
||||||
|
await upload(await processDrop(e, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upload(files) {
|
||||||
|
await realUpload(await read(files[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function realUpload(file) {
|
||||||
|
inner.value = (
|
||||||
|
await axios.post('/mediaupload', {
|
||||||
|
parent: {model: props.parentName, collection_name: props.collection, id: props.parentId ? props.parentId : null},
|
||||||
|
payload: file,
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
emit('update:modelValue', inner.value);
|
||||||
|
toast.success('Datei hochgeladen');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDelete() {
|
||||||
|
await axios.delete(`/mediaupload/${inner.value.id}`);
|
||||||
|
inner.value = null;
|
||||||
|
emit('update:modelValue', null);
|
||||||
|
toast.success('Datei entfernt');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.parentId !== null) {
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,35 @@
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="Capa_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
style="enable-background: new 0 0 512 512"
|
||||||
|
xml:space="preserve"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M486.4,102.4h-128V25.6c0-15.36-10.24-25.6-25.6-25.6H179.2c-15.36,0-25.6,10.24-25.6,25.6v76.8h-128
|
||||||
|
C10.24,102.4,0,112.64,0,128s10.24,25.6,25.6,25.6h460.8c15.36,0,25.6-10.24,25.6-25.6S501.76,102.4,486.4,102.4z M307.2,102.4
|
||||||
|
H204.8V51.2h102.4V102.4z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M25.6,204.8l48.64,284.16c2.56,12.8,12.8,23.04,25.6,23.04h312.32c12.8,0,23.04-10.24,25.6-23.04L486.4,204.8H25.6z
|
||||||
|
M153.6,460.8c-15.36,0-25.6-10.24-25.6-25.6l-25.6-153.6c0-15.36,10.24-25.6,25.6-25.6s25.6,10.24,25.6,25.6l25.6,153.6
|
||||||
|
C179.2,450.56,168.96,460.8,153.6,460.8z M281.6,435.2c0,15.36-10.24,25.6-25.6,25.6s-25.6-10.24-25.6-25.6V281.6
|
||||||
|
c0-15.36,10.24-25.6,25.6-25.6s25.6,10.24,25.6,25.6V435.2z M384,435.2c0,15.36-10.24,25.6-25.6,25.6
|
||||||
|
c-15.36,0-25.6-10.24-25.6-25.6l25.6-153.6c0-15.36,10.24-25.6,25.6-25.6s25.6,10.24,25.6,25.6L384,435.2z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</template>
|
|
@ -0,0 +1,77 @@
|
||||||
|
import accounting from 'accounting';
|
||||||
|
import {ref} from 'vue';
|
||||||
|
|
||||||
|
accounting.settings.currency = {
|
||||||
|
...accounting.settings.currency,
|
||||||
|
precision: 2,
|
||||||
|
format: '%v %s',
|
||||||
|
decimal: ',',
|
||||||
|
thousand: '.',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const dropping = ref(false);
|
||||||
|
|
||||||
|
async function read(file) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function () {
|
||||||
|
var r = reader.result;
|
||||||
|
resolve({
|
||||||
|
content: r.substr(r.search(',') + 1),
|
||||||
|
name: file.name,
|
||||||
|
type: file.type,
|
||||||
|
size: file.size,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function onDragEnter(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
dropping.value = true;
|
||||||
|
}
|
||||||
|
function onDragOver(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
function onDragLeave(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
dropping.value = false;
|
||||||
|
}
|
||||||
|
function processDrop(e, maxFiles) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
let dt = e.dataTransfer;
|
||||||
|
let files = [...dt.files].slice(0, maxFiles ? maxFiles : dt.files.length);
|
||||||
|
let result = [];
|
||||||
|
for (const f in files) {
|
||||||
|
if (typeof files[f] === 'object') {
|
||||||
|
result.push(files[f]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dropping.value = false;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function size(file) {
|
||||||
|
if (file.size < 1000) {
|
||||||
|
return accounting.formatMoney(file.size, {symbol: 'B'});
|
||||||
|
}
|
||||||
|
if (file.size < 1000000) {
|
||||||
|
return accounting.formatMoney(file.size / 1000, {symbol: 'KB'});
|
||||||
|
}
|
||||||
|
if (file.size < 1000000000) {
|
||||||
|
return accounting.formatMoney(file.size / 1000000, {symbol: 'MB'});
|
||||||
|
}
|
||||||
|
if (file.size < 1000000000000) {
|
||||||
|
return accounting.formatMoney(file.size / 1000000000, {symbol: 'GB'});
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {dropping, onDragLeave, onDragOver, onDragEnter, processDrop, read};
|
||||||
|
}
|
Loading…
Reference in New Issue