Add Single file upload component

This commit is contained in:
philipp lang 2024-01-13 21:43:45 +01:00
parent c4eb67c09f
commit 3b8a129fa4
5 changed files with 249 additions and 0 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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">&nbsp;*</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>

View File

@ -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>

View File

@ -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};
}