-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
core: frontend: create and use imagepicker
- Loading branch information
1 parent
8586152
commit ea9efd3
Showing
2 changed files
with
329 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
<template> | ||
<div id="imgdiv"> | ||
<v-img id="main" contain :src="image" @click="openDialog()" /> | ||
<v-btn | ||
id="edit-icon" | ||
class="mx-2" | ||
fab | ||
dark | ||
x-small | ||
@click="openDialog" | ||
> | ||
<v-icon> | ||
mdi-pencil | ||
</v-icon> | ||
</v-btn> | ||
<v-dialog v-model="dialog" @dragover.prevent @dragenter.prevent @drop.prevent="onDrop"> | ||
<v-card class="pa-2"> | ||
<v-card-title> | ||
Pick an Image | ||
</v-card-title> | ||
<v-row v-if="images.length > 0" class="overflow-auto" style="max-height: 500px;" justify="space-around"> | ||
<v-col v-for="(image2, index) in images" :key="index" cols="12" sm="3" md="3"> | ||
<v-card | ||
:class="{ 'selected-image': selectedIndex === index }" | ||
class="image-card" | ||
@click="selectImage(index)" | ||
> | ||
<v-img :src="`${directory}/${image2}`" contain aspect-ratio="1" /> | ||
<v-btn | ||
id="trashcan-icon" | ||
class="mx-2" | ||
fab | ||
dark | ||
x-small | ||
@click.stop="deleteImage(index)" | ||
> | ||
<v-icon> | ||
mdi-trash-can | ||
</v-icon> | ||
</v-btn> | ||
</v-card> | ||
</v-col> | ||
</v-row> | ||
<v-row v-else-if="loading"> | ||
<SpinningLogo size="100px" /> | ||
</v-row> | ||
<v-row v-else> | ||
<v-alert> | ||
No images found at {{ directory }}. Please upload some images to this directory using the file browser. | ||
</v-alert> | ||
<v-alert v-if="error" color="red lighten-2"> | ||
{{ error }} | ||
</v-alert> | ||
</v-row> | ||
<v-row> | ||
<v-col cols="12"> | ||
<v-card | ||
class="drop-zone" | ||
@dragover.prevent | ||
@dragenter.prevent | ||
@drop.prevent="onDrop" | ||
> | ||
<v-card-text> | ||
Drag and drop an image file here to upload | ||
</v-card-text> | ||
<input | ||
aria-label="File browser" | ||
id="file-input" | ||
ref="fileInput" | ||
type="file" | ||
accept="image/*" | ||
style="display:none" | ||
@change="onFileInputChange" | ||
/> | ||
<v-btn color="primary" @click="onFilePickerClick"> | ||
Click to upload an image or | ||
</v-btn> | ||
</v-card> | ||
</v-col> | ||
</v-row> | ||
<v-alert v-if="uploadError" color="red lighten-2"> | ||
{{ uploadError }} | ||
</v-alert> | ||
</v-card> | ||
</v-dialog> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import axios from 'axios' | ||
import Vue from 'vue' | ||
import back_axios from '@/utils/api' | ||
import SpinningLogo from '../common/SpinningLogo.vue' | ||
interface FileEntry { | ||
name: string; | ||
type: string; | ||
mtime: string; | ||
} | ||
export default Vue.extend({ | ||
name: 'ImagePicker', | ||
components: { | ||
SpinningLogo, | ||
}, | ||
props: { | ||
directory: { | ||
type: String, | ||
default: '/userdata/images/', | ||
required: false, | ||
}, | ||
image: { | ||
type: String, | ||
default: '/userdata/images/', | ||
required: false, | ||
}, | ||
}, | ||
data() { | ||
return { | ||
dialog: false, | ||
selectedIndex: null as number | null, | ||
images: [] as string[], | ||
error: null as string | null, | ||
loading: true, | ||
uploadError: null as string | null, | ||
} | ||
}, | ||
computed: { | ||
}, | ||
mounted() { | ||
this.loadImages() | ||
}, | ||
methods: { | ||
selectImage(index: number) { | ||
this.selectedIndex = index | ||
this.$emit('image-selected', `${this.directory}/${this.images[index]}`) | ||
this.dialog = false | ||
}, | ||
openDialog() { | ||
this.dialog = true | ||
}, | ||
loadImages() { | ||
back_axios({ | ||
method: 'get', | ||
url: this.directory, | ||
}).then((response) => { | ||
this.images = response.data.filter( | ||
(string: FileEntry) => string.type === 'file', | ||
).map((file: FileEntry) => file.name) | ||
this.loading = false | ||
}).catch((error) => { | ||
this.error = error | ||
}) | ||
}, | ||
async deleteImage(index: number) { | ||
try { | ||
await back_axios({ | ||
method: 'delete', | ||
url: `upload/${this.directory}/${this.images[index]}`, | ||
}) | ||
this.loadImages() | ||
} catch (error) { | ||
console.error('Error deleting file:', error) | ||
} | ||
}, | ||
onFileInputChange(event: Event) { | ||
const target = event.target as HTMLInputElement | ||
if (target.files && target.files.length > 0) { | ||
const file = target.files[0] | ||
const fileName = encodeURIComponent(file.name) | ||
this.uploadFile(file, `${this.directory}/${fileName}`) | ||
} | ||
}, | ||
onFilePickerClick() { | ||
const input = this.$refs.fileInput as HTMLInputElement | ||
input.click() | ||
}, | ||
async uploadFile(file: File, destination_path: string) { | ||
try { | ||
const config = { | ||
headers: { | ||
'Content-Type': file.type, | ||
}, | ||
} | ||
await axios.put(`/upload/${destination_path}`, file, config) | ||
console.log('File uploaded successfully.') | ||
this.loadImages() | ||
} catch (error) { | ||
console.error('Error uploading file:', error) | ||
} | ||
}, | ||
async onDrop(event: DragEvent) { | ||
if (!event.dataTransfer) return | ||
const { files } = event.dataTransfer | ||
if (files.length === 0) return | ||
const file = files[0] | ||
const fileName = encodeURIComponent(file.name) | ||
await this.uploadFile(file, `${this.directory}/${fileName}`) | ||
}, | ||
}, | ||
}) | ||
</script> | ||
<style scoped> | ||
#main { | ||
display: inline-flex; | ||
height: 50px; | ||
width: 50px; | ||
margin: 0px; | ||
object-fit: contain; | ||
position: relative; | ||
} | ||
#edit-icon { | ||
display: none; | ||
position: absolute; | ||
right: -15px; | ||
bottom: 0; | ||
} | ||
#imgdiv:hover #edit-icon{ | ||
display: inline-flex !important; | ||
} | ||
#imgdiv { | ||
position:relative; | ||
} | ||
.drop-zone { | ||
border: 2px dashed #ccc; | ||
border-radius: 4px; | ||
text-align: center; | ||
padding: 20px; | ||
margin: 20px 0; | ||
} | ||
.image-card { | ||
position: relative; | ||
} | ||
#trashcan-icon { | ||
display: none; | ||
position: absolute; | ||
top: -10px; | ||
right: -10px; | ||
} | ||
.image-card:hover #trashcan-icon { | ||
display: inline-flex !important; | ||
} | ||
</style> |
Oops, something went wrong.