Files
cloudron-box/dashboard/src/components/ImagePicker.vue
T
2025-03-25 15:05:08 +01:00

159 lines
4.3 KiB
Vue

<script setup>
import { useTemplateRef, ref } from 'vue';
const fileInput = useTemplateRef('fileInput');
const props = defineProps(['src', 'fallbackSrc', 'size', 'maxSize', 'displayHeight', 'displayWidth']);
const emits = defineEmits(['changed']);
defineExpose({
clear(originalSrc = '') {
internalSrc.value = originalSrc || props.src || props.fallbackSrc;
}
});
const image = useTemplateRef('image');
const internalSrc = ref('');
const isChanged = ref(false);
function onShowIconSelector() {
fileInput.value.click();
}
function dataURLtoFile(dataURL, filename) {
// Split the data URL to get the MIME type and the base64 data
const [metadata, base64Data] = dataURL.split(',');
// Extract the MIME type from the metadata
const mimeType = metadata.match(/:(.*?);/)[1];
// Decode the base64 data to binary
const binaryData = atob(base64Data);
// Create an array buffer from the binary data
const arrayBuffer = new ArrayBuffer(binaryData.length);
const view = new Uint8Array(arrayBuffer);
for (let i = 0; i < binaryData.length; i++) {
view[i] = binaryData.charCodeAt(i);
}
// Create a Blob from the array buffer
const blob = new Blob([arrayBuffer], { type: mimeType });
// Create a File object from the Blob
const file = new File([blob], filename, { type: mimeType });
return file;
}
function onChanged(event) {
const fr = new FileReader();
fr.onload = function () {
const image = new Image();
image.onload = function () {
const size = props.size ? parseInt(props.size) : 512;
const maxSize = props.maxSize ? parseInt(props.maxSize) : 0;
const canvas = document.createElement('canvas');
if (maxSize) {
if (image.naturalWidth > maxSize) {
canvas.width = maxSize;
canvas.height = (image.naturalHeight / image.naturalWidth) * maxSize;
} else {
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
}
} else {
canvas.width = size;
canvas.height = size;
}
const imageDimensionRatio = image.width / image.height;
const canvasDimensionRatio = canvas.width / canvas.height;
let renderableHeight, renderableWidth, xStart, yStart;
if (imageDimensionRatio > canvasDimensionRatio) {
renderableHeight = canvas.height;
renderableWidth = image.width * (renderableHeight / image.height);
xStart = (canvas.width - renderableWidth) / 2;
yStart = 0;
} else if (imageDimensionRatio < canvasDimensionRatio) {
renderableWidth = canvas.width;
renderableHeight = image.height * (renderableWidth / image.width);
xStart = 0;
yStart = (canvas.height - renderableHeight) / 2;
} else {
renderableHeight = canvas.height;
renderableWidth = canvas.width;
xStart = 0;
yStart = 0;
}
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(image, xStart, yStart, renderableWidth, renderableHeight);
internalSrc.value = canvas.toDataURL('image/png');
isChanged.value = true;
emits('changed', dataURLtoFile(internalSrc.value, 'image.png'));
};
image.src = fr.result;
};
fr.readAsDataURL(event.target.files[0]);
}
function onError() {
internalSrc.value = props.fallbackSrc;
}
</script>
<template>
<div>
<div class="image-picker">
<input @change="onChanged($event)" type="file" ref="fileInput" style="display: none" accept="image/*"/>
<div ref="image" class="image-picker" @click="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>
</div>
</div>
</template>
<style scoped>
.image-picker {
display: inline-block;
position: relative;
cursor: pointer;
}
.image-picker > img {
display: block;
/* height: 320px;*/
border-radius: 10px;
}
.image-picker-edit-indicator {
position: absolute;
bottom: 4px;
right: 4px;
border-radius: 20px;
padding: 5px;
color: var(--pankow-text-color);
background-color: var(--pankow-input-background-color);
transition: all 250ms;
}
.image-picker:hover .image-picker-edit-indicator {
color: white;
background: var(--pankow-color-primary);
transform: scale(1.2);
}
</style>