193 lines
No EOL
6.3 KiB
Vue
193 lines
No EOL
6.3 KiB
Vue
<template>
|
|
<drag-drop-file-source @input="addFiles">
|
|
<h3 v-if="create">Create New</h3>
|
|
<h3 v-else>Update</h3>
|
|
<ul>
|
|
<li v-for="file in whithout_images(files)" :key="file.id">
|
|
{{ file.name }}
|
|
</li>
|
|
<li v-for="file in whithout_images(item_files)" :key="file.id">
|
|
{{ file.name }}
|
|
</li>
|
|
</ul>
|
|
<hr>
|
|
<div style="position: relative;">
|
|
<div class="image-list">
|
|
<deletable-wrapper v-for="file in only_images(item_files).filter(file => file.owner)" :key="file.id"
|
|
@delete="deleteFile(file)">
|
|
<authenticated-image :src="file.name" :owner="file.owner" class="img-thumbnail"/>
|
|
</deletable-wrapper>
|
|
<deletable-wrapper v-for="file in only_images(item_files).filter(file => file.data)" :key="file.id"
|
|
@delete="deleteTempFile(file)">
|
|
<img :alt="file.name" :src="'data:' + file.mime_type + ';base64,' + file.data"
|
|
class="img-thumbnail border-info">
|
|
</deletable-wrapper>
|
|
<fs-file-source @input="addFiles">
|
|
<div class="img-thumbnail btn btn-outline-primary">
|
|
<b-icon-upload></b-icon-upload>
|
|
</div>
|
|
</fs-file-source>
|
|
<camera-file-source @input="addFiles">
|
|
<div class="img-thumbnail btn btn-outline-primary">
|
|
<b-icon-camera></b-icon-camera>
|
|
</div>
|
|
</camera-file-source>
|
|
<webcam-file-source @input="addFiles">
|
|
<div class="img-thumbnail btn btn-outline-primary">
|
|
<b-icon-camera-video></b-icon-camera-video>
|
|
</div>
|
|
</webcam-file-source>
|
|
<label class="img-thumbnail btn btn-outline-primary" for="file-dropdown">
|
|
<b-icon-plus></b-icon-plus>
|
|
</label>
|
|
</div>
|
|
<input type="checkbox" id="file-dropdown" class="invisible-input">
|
|
<div class="dropdown-menu" v-if="only_images(files).length > 0">
|
|
<div class="image-list">
|
|
<span v-for="file in only_images(files)" :key="file.id" @click="addExistingFiles([file])"
|
|
style="cursor: pointer;">
|
|
<authenticated-image :src="file.name" :owner="file.owner" class="img-thumbnail"/>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</drag-drop-file-source>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.img-thumbnail {
|
|
width: 95px;
|
|
height: 54px;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.img-thumbnail svg {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.image-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 5px;
|
|
}
|
|
|
|
.invisible-input {
|
|
display: none;
|
|
}
|
|
|
|
#file-dropdown:checked ~ .dropdown-menu {
|
|
display: block;
|
|
}
|
|
|
|
.dropdown-menu:hover {
|
|
display: block;
|
|
}
|
|
|
|
#file-dropdown:checked ~ label {
|
|
color: #fff;
|
|
background-color: var(--bs-primary);
|
|
border-color: var(--bs-primary);
|
|
}
|
|
|
|
#file-dropdown:checked ~ label:hover {
|
|
color: var(--bs-primary);
|
|
background-color: initial;
|
|
}
|
|
|
|
</style>
|
|
|
|
<script>
|
|
import * as BIcons from "bootstrap-icons-vue";
|
|
import {mapActions, mapState} from "vuex";
|
|
import DragDropFileSource from "@/components/DragDropFileSource.vue";
|
|
import FsFileSource from "@/components/FsFileSource.vue";
|
|
import CameraFileSource from "@/components/CameraFileSource.vue";
|
|
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
|
import DeletableWrapper from "@/components/DeletableWrapper.vue";
|
|
import WebcamFileSource from "@/components/WebcamFileSource.vue";
|
|
|
|
export default {
|
|
name: "CombinedFileField",
|
|
components: {
|
|
WebcamFileSource,
|
|
...BIcons,
|
|
AuthenticatedImage,
|
|
DeletableWrapper,
|
|
DragDropFileSource,
|
|
CameraFileSource,
|
|
FsFileSource
|
|
},
|
|
props: {
|
|
item_files: {
|
|
type: Array,
|
|
required: true
|
|
},
|
|
item_id: {
|
|
type: Number
|
|
},
|
|
create: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
},
|
|
emits: ["change"],
|
|
computed: {
|
|
...mapState(["files"]),
|
|
},
|
|
methods: {
|
|
...mapActions(["fetchFiles", "pushFile", "deleteItemFile"]),
|
|
async uploadFiles(files) {
|
|
const jobs = files.map(async file => {
|
|
return await this.pushFile({
|
|
file: file,
|
|
item_id: this.item_id
|
|
});
|
|
});
|
|
return await Promise.all(jobs);
|
|
},
|
|
addFiles(files) {
|
|
console.log("add files", files);
|
|
const new_files = files.filter(file => !this.item_files.find(f => f.hash === file.hash));
|
|
if (new_files.length === 0) {
|
|
console.log("no new files");
|
|
return;
|
|
}
|
|
if (!this.create) {
|
|
this.uploadFiles(new_files).then((uploaded) => {
|
|
this.$emit("change", [...this.item_files, ...uploaded]);
|
|
})
|
|
} else {
|
|
this.$emit("change", [...this.item_files, ...new_files]);
|
|
}
|
|
|
|
},
|
|
addExistingFiles(files) {
|
|
console.log("add existing files", files);
|
|
const new_files = files.filter(file => !this.item_files.find(f => f.id === file.id));
|
|
if (new_files.length === 0) {
|
|
console.log("no new files");
|
|
return;
|
|
}
|
|
this.$emit("change", [...this.item_files, ...new_files]);
|
|
},
|
|
deleteFile(file) {
|
|
this.deleteItemFile({item_id: this.item_id, file_id: file.id}).then(() => {
|
|
this.$emit("change", this.item_files.filter(f => f.id !== file.id));
|
|
});
|
|
},
|
|
deleteTempFile(file) {
|
|
this.$emit("change", this.item_files.filter(f => f.hash !== file.hash));
|
|
},
|
|
only_images(files) {
|
|
return files.filter(file => file.mime_type.startsWith("image/"));
|
|
},
|
|
whithout_images(files) {
|
|
return files.filter(file => !file.mime_type.startsWith("image/"));
|
|
}
|
|
},
|
|
mounted() {
|
|
this.fetchFiles();
|
|
}
|
|
}
|
|
</script> |