-
@@ -374,14 +376,16 @@
{{ __("Prévisualisation de l'image") }}
+ >
+ {{ __("Prévisualisation de l'image") }}
+
@@ -397,8 +401,8 @@
import { defineComponent } from "vue";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import Cropper from "cropperjs";
-import trans from "../../modules/trans";
-import bsTooltips from "../../modules/bsTooltips";
+import { Tooltip } from "bootstrap";
+import trans from "./../../modules/trans";
const UNITS = [
"byte",
@@ -411,76 +415,20 @@ const UNITS = [
const BYTES_PER_KB = 1024;
export default defineComponent({
+ mixins: [trans],
+ emits: ["browseFile"],
components: {
FontAwesomeIcon,
},
- mixins: [trans, bsTooltips],
- emits: {
- browseFile: (inTId: string) => {
- inTId;
- return true;
- },
- imageFile: (file: File | null) => {
- file;
- return true;
- },
- },
- mounted() {
- const json = String(this.$attrs.json ?? "");
- this.initTooltips();
- if (json.length) {
- // * Init uppon Laravel Blade
- const data = JSON.parse(json);
- this.inTId = data.id;
- this.inTname = data.name;
- this.inThelper = data.helper ?? "";
- this.inTwidth = Number.parseInt(String(data.width));
- this.inTheight = Number.parseInt(String(data.height));
- this.inTvalue = String(data.value);
- if (this.inTvalue.length) {
- this.inTvalueFileName = this.inTvalue.replace(/^\/|^/, "");
- this.inTvalue = this.inTvalue.replace(/^\/|^/, "/");
- }
- if (data.preview !== undefined) {
- this.inTpreview = data.preview ? true : false;
- }
- if (data.required !== undefined) {
- this.inTrequired = data.required ? true : false;
- }
- if (data.browseEvent !== undefined) {
- this.inTbrowseEvent = data.browseEvent ? true : false;
- }
- } else {
- // * Init Uppon Vue component
- this.inTId = this.id;
- this.inTname = this.name;
- this.inThelper = this.helper;
- this.inTwidth = this.width;
- this.inTheight = this.height;
- this.inTvalue = this.value;
- if (this.inTvalue.length) {
- this.inTvalueFileName = this.inTvalue.replace(/^\/|^/, "");
- this.inTvalue = this.inTvalue.replace(/^\/|^/, "/");
- }
- this.inTpreview = this.preview;
- this.inTrequired = this.required;
- this.inTbrowseEvent = this.browseEvent;
- }
- this.inThasImage = false;
- },
props: {
id: {
type: String,
- default: String(Math.pow(10, 16) / Math.random()),
+ default: "",
},
required: {
type: Boolean,
default: false,
},
- showLabels: {
- type: Boolean,
- default: true,
- },
name: {
type: String,
default: "",
@@ -511,74 +459,120 @@ export default defineComponent({
},
},
data(): {
- inTId: string;
- inTvalue: string;
- inTvalueFileName: string;
- inThelper: string;
- inTname: string;
- inTrequired: boolean;
- inTwidth: number;
- inTheight: number;
- inTpreview: boolean;
+ intId: string;
+ intValue: string;
+ intValueFileName: string;
+ intHelper: string;
+ intName: string;
+ intRequired: boolean;
+ intWidth: number;
+ intHeight: number;
+ intPreview: boolean;
- inTbrowseEvent: boolean;
+ intBrowseEvent: boolean;
- inTrotationText: number;
- inTrotationValue: number;
- inTscaleX: number;
- inTscaleY: number;
- inThasImage: boolean;
- inThasModdedImage: boolean;
- inTcropper: Cropper | null;
+ intRotationText: number;
+ intRotationValue: number;
+ intScaleX: number;
+ intScaleY: number;
+ intHasImage: boolean;
+ intHasModdedImage: boolean;
+ intCropper: Cropper | null;
- inToriginalFile: File | null;
- inToriginalFileDimensions: {
+ intOriginalFile: File | null;
+ intOriginalFileDimensions: {
height: number;
width: number;
};
- inToriginalFileName: string;
- inTpreviewStyle: string;
+ intOriginalFileName: string;
+ intPreviewStyle: string;
+ tooltipList: HTMLButtonElement[];
} {
return {
- inTId: "",
- inTvalue: "",
- inTvalueFileName: this.__("Sélectionnez une image"),
- inThelper: "",
- inTname: "",
- inTrequired: true,
- inTwidth: 0,
- inTheight: 0,
- inTpreview: true,
+ intId: "",
+ intValue: "",
+ intValueFileName: this.__("Sélectionnez une image"),
+ intHelper: "",
+ intName: "",
+ intRequired: true,
+ intWidth: 0,
+ intHeight: 0,
+ intPreview: true,
- inTbrowseEvent: false,
+ intBrowseEvent: false,
- inTrotationText: 0,
- inTrotationValue: 0,
- inTscaleX: 1,
- inTscaleY: 1,
- inThasImage: false,
- inThasModdedImage: false,
- inTcropper: null,
+ intRotationText: 0,
+ intRotationValue: 0,
+ intScaleX: 1,
+ intScaleY: 1,
+ intHasImage: false,
+ intHasModdedImage: false,
+ intCropper: null,
- inToriginalFile: null,
- inToriginalFileDimensions: {
+ intOriginalFile: null,
+ intOriginalFileDimensions: {
height: 0,
width: 0,
},
- inToriginalFileName: "",
- inTpreviewStyle: "",
+ intOriginalFileName: "",
+ intPreviewStyle: "",
+ tooltipList: [],
};
},
+ mounted() {
+ const json = String(this.$attrs.json ?? "");
+ if (json.length) {
+ // * Init uppon Laravel Blade
+ const data = JSON.parse(json);
+ this.intId = data.id;
+ this.intName = data.name;
+ this.intHelper = data.helper ?? "";
+ this.intWidth = data.width;
+ this.intHeight = data.height;
+ this.intValue = String(data.value);
+ if (this.intValue.length) {
+ this.intValueFileName = this.intValue;
+ this.intValue = this.intValue.replace(/^\/|^/, "/");
+ }
+ if (data.preview !== undefined) {
+ this.intPreview = data.preview ? true : false;
+ }
+ if (data.required !== undefined) {
+ this.intRequired = data.required ? true : false;
+ }
+ if (data.browseEvent !== undefined) {
+ this.intBrowseEvent = data.browseEvent ? true : false;
+ }
+ } else {
+ // * Init Uppon Vue component
+ this.intId = this.id;
+ this.intName = this.name;
+ this.intHelper = this.helper;
+ this.intWidth = this.width;
+ this.intHeight = this.height;
+ this.intValue = this.value;
+ if (this.intValue.length) {
+ this.intValueFileName = this.intValue;
+ this.intValue = this.intValue.replace(/^\/|^/, "/");
+ }
+ this.intPreview = this.preview;
+ this.intRequired = this.required;
+ this.intBrowseEvent = this.browseEvent;
+ }
+ this.intHasImage = false;
+ this.$nextTick(() => {
+ this.updateBootstrapTooltip();
+ });
+ },
computed: {
imageRatio() {
- return this.inTwidth / this.inTheight;
+ return this.intWidth / this.intHeight;
},
cropperOption(): Cropper.Options {
return {
autoCrop: true,
background: true,
autoCropArea: 1,
- // * full crop box
viewMode: 0,
dragMode: "move",
movable: true,
@@ -590,143 +584,110 @@ export default defineComponent({
cropBoxMovable: true,
cropBoxResizable: false,
toggleDragModeOnDblclick: false,
- aspectRatio: this.inTwidth / this.inTheight,
- preview: `#Preview${this.inTId}`,
+ aspectRatio: this.intWidth / this.intHeight,
+ preview: `#Preview${this.intId}`,
};
},
- /** Removes prepended slash */
- submitIntValue(): string {
- return this.inTvalue.replace(/^\/|^/, "");
- },
},
watch: {
- inTrotationValue(value) {
- if (!this.inTcropper) {
- return;
+ intRotationValue(value) {
+ if (!(this.intCropper instanceof Cropper)) {
+ throw new Error("cropped not initialized");
}
- this.inTcropper.rotateTo(value);
+ this.intCropper.rotateTo(value);
},
- inTvalue() {
- if (this.inTbrowseEvent) {
- this.inThasImage = true;
+ intValue() {
+ if (this.intBrowseEvent) {
+ this.intHasImage = true;
}
},
- value: {
- deep: true,
- immediate: true,
- handler(value: string) {
- // * Ignore non prop usage.
- if (String(this.$attrs.json ?? "").length) {
- return;
- }
- if (!value.length) {
- this.$nextTick(() => {
- this.removeFile();
- });
- return;
- } else {
- this.inThasImage = true;
- }
- // * Using browse event, If user changed imagem recompile blob (File Upload storage strategy)
- if (this.inTbrowseEvent && this.inThasImage) {
- const self = this,
- actualImage = this.$refs.actualImage as HTMLInputElement,
- container = new DataTransfer();
- this.$nextTick(() => {
- this.inTvalue = value.replace(/^\/|^/, "/");
- this.inTvalueFileName = value.replace(/^\/|^/, "");
- fetch(this.inTvalue)
- .then((response) => {
- self.inToriginalFileName = response.url.substring(
- response.url.lastIndexOf("/") + 1
- );
- return response.blob();
- })
- .then((blob: Blob) => {
- if (!actualImage.files) {
- throw new Error("3- missing files list");
- }
- this.fetchDataUrlImageSizes(self.inTvalue);
- const file = new File([blob], this.inTvalueFileName, {
- type: "image/png",
- lastModified: new Date().getTime(),
- });
- this.inThasImage = true;
- this.inToriginalFile = file;
- this.$emit("imageFile", file);
- container.items.add(file);
- actualImage.files = container.files;
- this.changedImage();
- });
- });
- }
- },
+ value() {
+ this.intValue = this.value.replace(/^\/|^/, "/");
+ this.intValueFileName = this.value;
},
},
methods: {
- preventMouseDown(e: MouseEvent) {
- if ((e.target as HTMLElement)?.id !== "range") {
- e.preventDefault();
- return false;
- }
- },
+ /**
+ * Cropper preview style.
+ */
previewStyleCompute() {
const previewBox = this.$refs.previewBox as HTMLElement,
style = window.getComputedStyle(previewBox),
targetWidth = Number.parseFloat(style.width) / 1.2,
targetHeight =
- (this.inTheight * Number.parseFloat(style.width)) /
- this.inTwidth /
+ (this.intHeight * Number.parseFloat(style.width)) /
+ this.intWidth /
1.2;
- this.inTpreviewStyle = `width: ${targetWidth.toFixed(
+ this.intPreviewStyle = `width: ${targetWidth.toFixed(
3
)}px;height:${targetHeight.toFixed(3)}px;`;
},
+ /**
+ * Cropper reset.
+ */
reset() {
- if (!this.inTcropper) {
+ if (!(this.intCropper instanceof Cropper)) {
return;
}
- this.inTcropper.reset();
+ this.intCropper.reset();
},
+ /**
+ * Cropper zoom.
+ */
zoom(v: number) {
- if (!this.inTcropper) {
+ if (!(this.intCropper instanceof Cropper)) {
return;
}
- this.inTcropper.zoom(v);
+ this.intCropper.zoom(v);
},
+ /**
+ * Cropper move.
+ */
move(x: number, y: number) {
- if (!this.inTcropper) {
+ if (!(this.intCropper instanceof Cropper)) {
return;
}
- this.inTcropper.move(x, y);
+ this.intCropper.move(x, y);
},
+ /**
+ * Cropper scale.
+ */
scale(horizontal: boolean) {
- if (!this.inTcropper) {
+ if (!(this.intCropper instanceof Cropper)) {
return;
}
if (horizontal) {
- this.inTscaleX = -1 / this.inTscaleX;
- this.inTcropper.scaleX(this.inTscaleX);
+ this.intScaleX = -1 / this.intScaleX;
+ this.intCropper.scaleX(this.intScaleX);
} else {
- this.inTscaleY = -1 / this.inTscaleY;
- this.inTcropper.scaleY(this.inTscaleY);
+ this.intScaleY = -1 / this.intScaleY;
+ this.intCropper.scaleY(this.intScaleY);
}
},
+ /**
+ * Cropper rotate.
+ */
rotate(value: number) {
const rotationSlider = this.$refs.rotationSlider as HTMLInputElement;
- let res = this.inTrotationValue + value;
+ let res = this.intRotationValue + value;
res = res < 0 ? 0 : res;
res = res > 360 ? 360 : res;
- this.inTrotationValue = res;
- this.inTrotationText = this.inTrotationValue;
- rotationSlider.value = String(this.inTrotationValue);
+ this.intRotationValue = res;
+ this.intRotationText = this.intRotationValue;
+ rotationSlider.value = String(this.intRotationValue);
},
+ /**
+ * Cropper rotate to.
+ */
rotateTo(value: number) {
value = value < 0 ? 0 : value;
value = value > 360 ? 360 : value;
- this.inTrotationValue = value;
- this.inTrotationText = this.inTrotationValue;
+ this.intRotationValue = value;
+ this.intRotationText = this.intRotationValue;
},
+ /**
+ * Cropper rotation input.
+ */
rotationChange(e: Event) {
const el = e.target;
if (!el || !(el instanceof HTMLInputElement)) {
@@ -734,87 +695,90 @@ export default defineComponent({
}
this.rotateTo(Number.parseFloat(el.value));
},
+ /**
+ * Link button change image to the input file image.
+ */
chooseAFile() {
const actualImage = this.$refs.actualImage as HTMLInputElement;
- if (this.inTbrowseEvent) {
- this.$emit("browseFile", this.inTId);
+ if (this.intBrowseEvent) {
+ this.$emit("browseFile", this.intId);
return;
}
actualImage.click();
},
+ /**
+ * Remove one specific image.
+ */
removeFile() {
const actualImage = this.$refs.actualImage as HTMLInputElement;
actualImage.value = "";
- this.inTvalueFileName = this.__("Sélectionnez une image");
- this.inTvalue = "";
- this.inToriginalFileName = "";
- this.inToriginalFile = null;
- this.inThasModdedImage = false;
- this.inToriginalFileDimensions = {
- height: 0,
- width: 0,
- };
- // * To overload watcher for inTvalue
- this.$nextTick(() => (this.inThasImage = false));
- this.$emit("imageFile", null);
+ this.intValueFileName = this.__("Sélectionnez une image");
+ this.intValue = "";
},
- changedImage() {
+ /**
+ * Change one specific image.
+ */
+ changedImage(e: Event) {
const self = this,
- reader = new FileReader(),
- actualImage = this.$refs.actualImage as HTMLInputElement;
- if (!actualImage.files) {
- throw new Error("missing files list");
+ reader = new FileReader();
+ const el = e.target;
+ if (!el || !(el instanceof HTMLInputElement)) {
+ throw new Error(
+ "confirmDoneJS can only be executed on an exising form"
+ );
}
- self.inThasImage = true;
+ if (!el.files) {
+ throw new Error();
+ }
+ self.intHasImage = true;
reader.addEventListener(
"load",
() => {
// On convertit l'image en une chaîne de caractères base64.
- self.inTvalue = String(reader.result);
+ self.intValue = String(reader.result);
},
false
);
-
- if (actualImage.files[0]) {
- self.inToriginalFile = actualImage.files[0];
- self.inToriginalFileName = actualImage.files[0].name;
- reader.readAsDataURL(actualImage.files[0]);
- reader.onloadend = () => {
+ if (el.files[0]) {
+ self.intOriginalFile = el.files[0];
+ reader.readAsDataURL(el.files[0]);
+ reader.onloadend = () =>
this.fetchDataUrlImageSizes(String(reader.result));
- this.exportToBlob();
- };
- this.inTvalueFileName = actualImage.files[0].name;
+ this.intValueFileName = el.files[0].name;
}
},
+ /**
+ * Set cropper to edit a specific image.
+ */
prepareImageEditor(e: Event) {
- const actualImage = this.$refs.actualImage as HTMLInputElement,
+ const self = this,
+ actualImage = this.$refs.actualImage as HTMLInputElement,
imgEditor = this.$refs.imgEditor as HTMLImageElement,
- self = this,
createCropper = () => {
- if (self.inTcropper) {
- self.inTcropper.destroy();
+ if (self.intCropper) {
+ self.intCropper.destroy();
}
- self.inTcropper = new Cropper(imgEditor, self.cropperOption);
+ self.intCropper = new Cropper(imgEditor, self.cropperOption);
};
- if (!this.browseEvent) {
+ if (!self.browseEvent) {
if (!actualImage || !actualImage.files) {
throw new Error("actualImage is not defined");
}
- this.inToriginalFile = actualImage.files[0];
- this.inToriginalFileName = actualImage.files[0].name;
+ self.intOriginalFile = actualImage.files[0];
+ self.intOriginalFileName = actualImage.files[0].name;
imgEditor.src = URL.createObjectURL(actualImage.files[0]);
- this.previewStyleCompute();
+ self.previewStyleCompute();
this.$nextTick(() => createCropper());
return;
}
e.preventDefault();
this.$nextTick(() => {
- fetch(this.value.replace(/^\/|^/, "/"))
+ fetch(self.value.replace(/^\/|^/, "/"))
.then((response) => {
- this.inToriginalFileName = response.url.substring(
+ self.intOriginalFileName = response.url.substring(
response.url.lastIndexOf("/") + 1
);
return response.blob();
@@ -822,11 +786,14 @@ export default defineComponent({
.then((blob: Blob) => {
imgEditor.src = URL.createObjectURL(blob);
- this.previewStyleCompute();
- this.$nextTick(createCropper);
+ self.previewStyleCompute();
+ self.$nextTick(() => createCropper());
});
});
},
+ /**
+ * Create a Blob from a specific url.
+ */
dataURLtoBlob(dataUrl: string) {
const arr = dataUrl.split(",");
if (!arr[0] || !arr[1]) {
@@ -844,57 +811,65 @@ export default defineComponent({
}
return new Blob([u8arr], { type: mime[1] });
},
+ /**
+ * Update image sizes.
+ */
fetchDataUrlImageSizes(dataUrl: string) {
const img = new Image(),
self = this;
try {
img.onload = () => {
- self.inToriginalFileDimensions.width = img.width;
- self.inToriginalFileDimensions.height = img.height;
+ self.intOriginalFileDimensions.width = img.width;
+ self.intOriginalFileDimensions.height = img.height;
};
img.src = dataUrl;
} catch (e) {
- self.inToriginalFileDimensions.width = img.width;
- self.inToriginalFileDimensions.height = img.height;
+ self.intOriginalFileDimensions.width = img.width;
+ self.intOriginalFileDimensions.height = img.height;
}
},
- exportCropperFile() {
- this.inThasModdedImage = true;
- if (!this.inTcropper) {
- return;
- }
- this.inTvalue = this.inTcropper
- .getCroppedCanvas({
- fillColor: "rgba(0, 0, 0, 0)",
- width: this.inTwidth,
- height: this.inTheight,
- imageSmoothingEnabled: true,
- imageSmoothingQuality: "high",
- })
- .toDataURL("image/png");
- this.exportToBlob();
- },
+ /**
+ * Set image after the edit in cropper.
+ */
exportToBlob() {
const self = this,
actualImage = this.$refs.actualImage as HTMLInputElement,
container = new DataTransfer();
- if (!actualImage.files) {
- throw new Error("2- missing files list");
+ if (!(self.intCropper instanceof Cropper)) {
+ return;
}
- this.fetchDataUrlImageSizes(self.inTvalue);
+ if (!actualImage.files) {
+ throw new Error();
+ }
+ self.intHasModdedImage = true;
+ this.$nextTick(() => {
+ this.updateBootstrapTooltip();
+ });
+ self.intValue = self.intCropper
+ .getCroppedCanvas({
+ fillColor: "rgba(0, 0, 0, 0)",
+ width: self.intWidth,
+ height: self.intHeight,
+ imageSmoothingEnabled: true,
+ imageSmoothingQuality: "high",
+ })
+ .toDataURL("image/png");
+ this.fetchDataUrlImageSizes(self.intValue);
const file = new File(
- [self.dataURLtoBlob(self.inTvalue)],
- self.inToriginalFileName,
+ [self.dataURLtoBlob(self.intValue)],
+ self.intOriginalFileName,
{
type: "image/png",
lastModified: new Date().getTime(),
}
);
- this.inToriginalFile = file;
- this.$emit("imageFile", file);
+ this.intOriginalFile = file;
container.items.add(file);
actualImage.files = container.files;
},
+ /**
+ * Get size of a file.
+ */
humanFileSize(sizeBytes: number | bigint): string {
let size = Math.abs(Number(sizeBytes));
@@ -911,50 +886,70 @@ export default defineComponent({
maximumFractionDigits: 1,
}).format(size);
},
+ /**
+ * Update Bootstrap tooltips.
+ */
+ updateBootstrapTooltip() {
+ let newTooltipList = [].slice.call(
+ document.querySelectorAll(
+ ".image-input-" + this.intId + " [data-bs=\"tooltip\"]"
+ )
+ ) as HTMLButtonElement[];
+ let tmp = newTooltipList.filter((x) => !this.tooltipList.includes(x));
+ tmp.map((tooltip) => {
+ return new Tooltip(tooltip);
+ });
+ this.closeBootstrapTooltip();
+ this.tooltipList = newTooltipList;
+ },
+ /**
+ * Close all Bootstrap tooltips.
+ */
+ closeBootstrapTooltip() {
+ this.tooltipList.forEach((tooltip) => {
+ tooltip.blur();
+ Tooltip.getInstance(tooltip)?.hide();
+ });
+ },
},
});
+
diff --git a/resources/ts/bo/components/PasswordVisibility.vue b/resources/ts/bo/components/PasswordVisibility.vue
new file mode 100644
index 0000000..611fcdd
--- /dev/null
+++ b/resources/ts/bo/components/PasswordVisibility.vue
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/ts/bo/vue-bo.ts b/resources/ts/bo/vue-bo.ts
index 2cb9f02..633bb41 100644
--- a/resources/ts/bo/vue-bo.ts
+++ b/resources/ts/bo/vue-bo.ts
@@ -40,7 +40,7 @@ document.addEventListener("DOMContentLoaded", () => {
});
setupApp(app);
app.mount(grpEl);
- }, 500);
+ }, 10);
}
});
});
diff --git a/resources/ts/utilities/fontawesome.ts b/resources/ts/utilities/fontawesome.ts
index 7062930..680b19b 100644
--- a/resources/ts/utilities/fontawesome.ts
+++ b/resources/ts/utilities/fontawesome.ts
@@ -37,8 +37,12 @@ import {
faGripVertical,
faLock,
faLockOpen,
+ faCircleCheck,
+ faCircleXmark,
+ faFloppyDisk,
+ faVideo,
+ faFile,
} from "@fortawesome/free-solid-svg-icons";
-import { faCircleXmark } from "@fortawesome/free-regular-svg-icons";
config.autoReplaceSvg = false;
library.add(faEraser);
@@ -74,11 +78,15 @@ library.add(faArrowAltCircleUp);
library.add(faGripVertical);
library.add(faLock);
library.add(faLockOpen);
+library.add(faCircleCheck);
+library.add(faCircleXmark);
+library.add(faFloppyDisk);
+library.add(faFile);
+library.add(faVideo);
// Front
library.add(faChevronLeft);
library.add(faChevronRight);
-library.add(faCircleXmark);
library.add(faCircleNotch);
library.add(faSliders);
diff --git a/resources/views/bo/layout.blade.php b/resources/views/bo/layout.blade.php
index ef242bb..3505608 100644
--- a/resources/views/bo/layout.blade.php
+++ b/resources/views/bo/layout.blade.php
@@ -7,14 +7,14 @@
@include('bo.layouts.header')
-
+
@include('modules.flash-message')
-
+
@include('modules.breadcrumb.breadcrumb-body')
diff --git a/resources/views/bo/layouts/header.blade.php b/resources/views/bo/layouts/header.blade.php
index 0884889..a0516fd 100644
--- a/resources/views/bo/layouts/header.blade.php
+++ b/resources/views/bo/layouts/header.blade.php
@@ -1,4 +1,4 @@
-