Rework the ImagePicker component

This commit is contained in:
Johannes Zellner
2025-05-20 14:48:18 +02:00
parent 4615418000
commit d955f0e3d8
4 changed files with 120 additions and 95 deletions

View File

@@ -1,24 +1,20 @@
<script setup>
import { useTemplateRef, ref } from 'vue';
import { useTemplateRef, ref, watch } from 'vue';
import { Button } from 'pankow';
const fileInput = useTemplateRef('fileInput');
const props = defineProps(['src', 'fallbackSrc', 'size', 'maxSize', 'displayHeight', 'displayWidth', 'disabled']);
const emits = defineEmits(['changed']);
defineExpose({
clear(originalSrc = '') {
internalSrc.value = originalSrc || props.src || props.fallbackSrc;
}
});
const props = defineProps(['src', 'fallbackSrc', 'size', 'maxSize', 'displayHeight', 'displayWidth', 'disabled', 'saveHandler', 'unsetHandler']);
const image = useTemplateRef('image');
const internalSrc = ref('');
const isChanged = ref(false);
const busy = ref(false);
function onShowIconSelector() {
fileInput.value.click();
}
watch(() => {
internalSrc.value = props.src;
});
function dataURLtoFile(dataURL, filename) {
// Split the data URL to get the MIME type and the base64 data
@@ -46,6 +42,37 @@ function dataURLtoFile(dataURL, filename) {
return file;
}
async function onSave() {
if (typeof props.saveHandler !== 'function') return console.error('saveHandler must be a function in ImagePicker');
busy.value = true;
const error = await props.saveHandler(dataURLtoFile(internalSrc.value, 'image.png'));
if (!error) isChanged.value = false;
busy.value = false;
}
function onCancel() {
internalSrc.value = props.src || props.fallbackSrc;
isChanged.value = false;
}
function onEdit() {
fileInput.value.click();
}
async function onUnset() {
if (typeof props.unsetHandler !== 'function') return console.error('unsetHandler must be a function in ImagePicker');
busy.value = true;
const error = await props.unsetHandler();
if (!error) isChanged.value = false;
busy.value = false;
}
function onChanged(event) {
const fr = new FileReader();
fr.onload = function () {
@@ -96,8 +123,6 @@ function onChanged(event) {
internalSrc.value = canvas.toDataURL('image/png');
isChanged.value = true;
emits('changed', dataURLtoFile(internalSrc.value, 'image.png'));
};
image.src = fr.result;
@@ -113,12 +138,19 @@ function onError() {
<template>
<div>
<div>
<div class="image-picker-container">
<input @change="onChanged($event)" type="file" ref="fileInput" style="display: none" accept="image/*"/>
<div ref="image" :disabled="disabled || null" class="image-picker" @click="!disabled && onShowIconSelector()">
<img :src="internalSrc || src" @error="onError" :style="{ height: displayHeight || null, width: displayWidth || null }">
<i class="image-picker-edit-indicator fa fa-pencil-alt"></i>
<div ref="image" :disabled="disabled || null" class="image-picker" @click="!disabled && onEdit()">
<img :src="internalSrc || src" @error="onError" class="image-picker-image" :style="{ height: displayHeight || null, width: displayWidth || null }">
<div v-if="isChanged" class="image-picker-actions" style="visibility: visible;">
<Button @click.stop="onCancel" secondary tool small icon="fa fa-undo" :disabled="busy"/>
<Button @click.stop="onSave" success tool small :loading="busy" :disabled="busy" icon="fa fa-floppy-disk"/>
</div>
<div v-else-if="!disabled" class="image-picker-actions">
<Button @click.stop="onEdit" tool small icon="fa fa-pencil-alt" :disabled="busy"/>
<Button @click.stop="onUnset" tool small icon="fa fa-trash" :loading="busy" :disabled="busy" v-if="unsetHandler"/>
</div>
</div>
</div>
</div>
@@ -126,6 +158,38 @@ function onError() {
<style scoped>
.image-picker-container {
position: relative;
display: flex;
gap: 6px;
flex-direction: column;
}
.image-picker-actions {
position: absolute;
display: flex;
gap: 6px;
bottom: 6px;
width: 100%;
justify-content: center;
visibility: hidden;
}
@media (hover: none) {
.image-picker-actions {
visibility: visible;
}
}
.image-picker-actions:hover,
.image-picker:hover .image-picker-actions {
visibility: visible;
}
.image-picker-image {
margin-bottom: 16px;
}
.image-picker {
display: inline-block;
position: relative;