toolshed/frontend/src/components/CombinedFileField.vue
busti 25ee3dd4f2
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
feat: implement WebcamFileSource
2023-12-09 03:01:42 +01:00

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>