feat: ✨ config upload image for take SVG
This commit is contained in:
parent
7369d3c820
commit
9d081727bc
5 changed files with 71 additions and 37 deletions
|
|
@ -50,7 +50,7 @@ class SecureHeaders
|
|||
"default-src 'none';",
|
||||
"manifest-src 'self';",
|
||||
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
|
||||
"connect-src 'self' blob: " . implode(' ', [
|
||||
"connect-src 'self' blob: data: " . implode(' ', [
|
||||
// * VITE DEV
|
||||
config('app.debug') ? "wss://{$host}:4443" : '',
|
||||
config('app.debug') ? "https://{$host}:4443" : '',
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ return [
|
|||
'validate' => 'Validate',
|
||||
'preview' => 'Preview',
|
||||
'remove_picture' => 'Remove picture',
|
||||
'svg_not_croppable' => 'SVGs do not require cropping'
|
||||
],
|
||||
'WordCounterComponent' => [
|
||||
'words' => 'Words',
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ return [
|
|||
'validate' => 'Valider',
|
||||
'preview' => 'Prévisualisation',
|
||||
'remove_picture' => 'Supprimer l\'image',
|
||||
'svg_not_croppable' => 'Les SVG ne nécessitent pas de recadrage'
|
||||
],
|
||||
'WordCounterComponent' => [
|
||||
'words' => 'Mots',
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
type="file"
|
||||
class="d-none"
|
||||
:name="parameters.name"
|
||||
accept=".jpg,.jpeg,.gif,.png"
|
||||
accept=".jpg,.jpeg,.gif,.png,.svg"
|
||||
:aria-describedby="`Help${parameters.id}`"
|
||||
>
|
||||
<input
|
||||
|
|
@ -77,22 +77,24 @@
|
|||
>
|
||||
<FontAwesomeIcon icon="fa-solid fa-eye" />
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
@click.prevent="prepareImageEditor()"
|
||||
:disabled="!hasPicture"
|
||||
:class="`btn ${!hasCroppedPicture ? 'btn-primary' : 'btn-success'}`"
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
:data-bs-target="`#Modal${parameters.id}`"
|
||||
</button>
|
||||
<span
|
||||
data-bs-toggle="tooltip"
|
||||
:title="isCurrentFileSvg
|
||||
? trans.methods.__('vue.ImageInputComponent.svg_not_croppable')
|
||||
: trans.methods.__('vue.ImageInputComponent.resize_source_picture')"
|
||||
>
|
||||
<span
|
||||
data-bs-toggle="tooltip"
|
||||
:title="trans.methods.__('vue.ImageInputComponent.resize_source_picture')"
|
||||
<button
|
||||
@click.prevent="prepareImageEditor()"
|
||||
:disabled="!hasPicture || isCurrentFileSvg"
|
||||
:class="`btn ${!hasCroppedPicture ? 'btn-primary' : 'btn-success'}`"
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
:data-bs-target="`#Modal${parameters.id}`"
|
||||
>
|
||||
<FontAwesomeIcon icon="fa-solid fa-crop" />
|
||||
</span>
|
||||
</button>
|
||||
</button>
|
||||
</span>
|
||||
<span
|
||||
v-if="hasCroppedPicture"
|
||||
class="input-group-text text-success"
|
||||
|
|
@ -427,6 +429,7 @@ import Cropper from "cropperjs";
|
|||
import { computed, defineOptions, nextTick, onMounted, reactive, ref, useAttrs, watch } from "vue";
|
||||
import { Tooltips } from "./../../modules/bsTooltips";
|
||||
import trans from "./../../modules/trans";
|
||||
import { Tooltip } from "bootstrap";
|
||||
|
||||
/** If string the it is an absolute file path. */
|
||||
type FileOutputValue = File|string|null;
|
||||
|
|
@ -555,6 +558,14 @@ const parameters = reactive<{
|
|||
},
|
||||
});
|
||||
|
||||
const isCurrentFileSvg = ref<boolean>(false);
|
||||
|
||||
/** Detect SVG by mime type or extension */
|
||||
function isSvgFile(file: File): boolean {
|
||||
return (
|
||||
file.type === "image/svg+xml" || file.name.toLowerCase().endsWith(".svg")
|
||||
);
|
||||
}
|
||||
/** Internal file we are working on. */
|
||||
const fileParameters = reactive<{
|
||||
file: File|null,
|
||||
|
|
@ -666,7 +677,10 @@ watch(previousPropValue, () => {
|
|||
}
|
||||
cropperReset();
|
||||
hasCroppedPicture.value = false;
|
||||
fetchPictureFromPath(fileParameters.filePath)!.then((file) => useThis(file));
|
||||
const fetchPromise = fetchPictureFromPath(fileParameters.filePath);
|
||||
if (fetchPromise) {
|
||||
fetchPromise.then((file) => useThis(file));
|
||||
}
|
||||
});
|
||||
/** Crop parameters changes. */
|
||||
watch(cropperParameters, (newValue: CropperParameters) => {
|
||||
|
|
@ -700,6 +714,11 @@ watch(fileDataUrl, (newValue) => {
|
|||
}
|
||||
fileParameters.dimensions = {...parameters.dimensions};
|
||||
});
|
||||
watch(isCurrentFileSvg, () => {
|
||||
nextTick(() => {
|
||||
tooltips.value?.refreshTooltips();
|
||||
});
|
||||
});
|
||||
|
||||
// * METHODS
|
||||
function assignFromProps(): void {
|
||||
|
|
@ -777,10 +796,12 @@ function chooseAFile(): void {
|
|||
/** Input file has changed. */
|
||||
function pictureInputHasChanged(): void {
|
||||
if (inputFileImage?.value?.files && inputFileImage!.value!.files[0]) {
|
||||
const file = inputFileImage.value.files[0];
|
||||
cropperReset();
|
||||
hasCroppedPicture.value = false;
|
||||
useThis(inputFileImage!.value!.files[0]);
|
||||
fileOutput.value = inputFileImage!.value!.files[0];
|
||||
isCurrentFileSvg.value = isSvgFile(file);
|
||||
useThis(file);
|
||||
fileOutput.value = file;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -793,17 +814,19 @@ function fetchPictureFromPath(filePath: string): Promise<File>|void {
|
|||
const prefixedFilePath: string = filePath.replace(/^\/|^/, "/"),
|
||||
stringFileName: string = prefixedFilePath.substring(prefixedFilePath.lastIndexOf("/")).replace(/^\/|^/, "");
|
||||
|
||||
fetch(prefixedFilePath)
|
||||
.then((response) => response.blob())
|
||||
.then((blob: Blob) => {
|
||||
const file = new File([blob], stringFileName, {
|
||||
type: "image/png",
|
||||
lastModified: new Date().getTime(),
|
||||
});
|
||||
setFileAsDataUrl(file);
|
||||
accept(file);
|
||||
}).catch(() => deny("Failed to fetch picture"));
|
||||
}));
|
||||
fetch(prefixedFilePath)
|
||||
.then((response) => response.blob())
|
||||
.then((blob: Blob) => {
|
||||
const isSvg = stringFileName.toLowerCase().endsWith(".svg");
|
||||
isCurrentFileSvg.value = isSvg;
|
||||
const file = new File([blob], stringFileName, {
|
||||
type: isSvg ? "image/svg+xml" : blob.type || "image/png",
|
||||
lastModified: new Date().getTime(),
|
||||
});
|
||||
setFileAsDataUrl(file);
|
||||
accept(file);
|
||||
}).catch(() => deny("Failed to fetch picture"));
|
||||
}));
|
||||
}
|
||||
|
||||
/** Persist a File added from input or while component init. */
|
||||
|
|
@ -868,6 +891,7 @@ function resetComponent(): void {
|
|||
fileDataUrl.value = null;
|
||||
fileOutput.value = null;
|
||||
inputFileImage.value!.value = "";
|
||||
isCurrentFileSvg.value = false;
|
||||
fileParameters.fileName = trans.methods.__("vue.ImageInputComponent.choose_a_file");
|
||||
fileParameters.filePath = "";
|
||||
fileParameters.file = null;
|
||||
|
|
|
|||
|
|
@ -366,15 +366,14 @@
|
|||
|
||||
<template v-if="imageLoaded">
|
||||
<ImageInputComponent
|
||||
v-model="options.image"
|
||||
:id="'logo'"
|
||||
:name="'logo'"
|
||||
:helper="'L\'image qui va servir de vignette au produit, doit être de 500px (largeur) par 500px (hauteur) maximum'"
|
||||
:width="100"
|
||||
:height="100"
|
||||
:required="false"
|
||||
:value="options.image"
|
||||
@image-file="updatePicture()"
|
||||
:value="logoStoredPath"
|
||||
@image-file="updatePicture"
|
||||
/>
|
||||
</template>
|
||||
<input
|
||||
|
|
@ -428,8 +427,10 @@ export default defineComponent({
|
|||
this.options = data.settings;
|
||||
}
|
||||
if(data.logo !== "") {
|
||||
this.logoStoredPath = data.logo;
|
||||
this.options.image = "/" + data.logo;
|
||||
} else {
|
||||
this.logoStoredPath = "";
|
||||
this.options.image = "";
|
||||
}
|
||||
}
|
||||
|
|
@ -444,15 +445,20 @@ export default defineComponent({
|
|||
},
|
||||
methods: {
|
||||
download() {
|
||||
this.qrCode.update(this.options);
|
||||
this.qrCode.download({ extension: this.extension as FileExtension });
|
||||
},
|
||||
updatePicture() {
|
||||
const el = document.getElementById("PreviewHelplogo")?.parentElement?.children[1] as HTMLElement;
|
||||
if (el instanceof HTMLElement) {
|
||||
this.options.image = el.getAttribute("src") as string;
|
||||
updatePicture(file: File | null) {
|
||||
if (!file) {
|
||||
this.options.image = "";
|
||||
this.qrCode.update(this.options);
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
this.options.image = String(reader.result);
|
||||
this.qrCode.update(this.options);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
},
|
||||
changeColor(color: string) {
|
||||
if (!this.isTransparent) {
|
||||
|
|
@ -483,6 +489,7 @@ export default defineComponent({
|
|||
urlInput: HTMLInputElement,
|
||||
urlInputValue: string,
|
||||
imageLoaded: boolean,
|
||||
logoStoredPath: string,
|
||||
options: {
|
||||
type: DrawType;
|
||||
width: number;
|
||||
|
|
@ -528,6 +535,7 @@ export default defineComponent({
|
|||
urlInput: document.getElementById(this.urlInputId) as HTMLInputElement,
|
||||
urlInputValue: "",
|
||||
imageLoaded: false,
|
||||
logoStoredPath: "",
|
||||
options: {
|
||||
width: 300,
|
||||
height: 300,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue