From 7aa0e6d64573170e0c057f903954c85ca5095f39 Mon Sep 17 00:00:00 2001 From: Daniil Mordanov Date: Fri, 7 Aug 2026 18:57:47 +0700 Subject: [PATCH 1/2] Limit vehicle images to 400px --- .../src/components/app/ImagePicker.vue | 62 +++++++++++++++++-- .../src/components/app/VehicleBanner.vue | 2 + 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/core/frontend/src/components/app/ImagePicker.vue b/core/frontend/src/components/app/ImagePicker.vue index 3bdabb2f67..a0b17251fd 100755 --- a/core/frontend/src/components/app/ImagePicker.vue +++ b/core/frontend/src/components/app/ImagePicker.vue @@ -143,6 +143,11 @@ export default Vue.extend({ default: null, required: false, }, + maxDimension: { + type: Number, + default: 0, + required: false, + }, }, data() { return { @@ -200,18 +205,18 @@ export default Vue.extend({ this.error = `Error deleting file: ${error}` } }, - onFileInputChange(event: Event) { + async onFileInputChange(event: Event) { const target = event.target as HTMLInputElement const file = target.files?.[0] if (!file) return const fileName = encodeURIComponent(file.name) - this.uploadFile(file, `${this.directory}/${fileName}`) + await this.uploadImage(file, `${this.directory}/${fileName}`) }, onFilePickerClick() { const input = this.$refs.fileInput as HTMLInputElement input.click() }, - async uploadFile(file: File, destination_path: string) { + async uploadFile(file: Blob, destination_path: string) { const config = { headers: { 'Content-Type': file.type, @@ -224,11 +229,60 @@ export default Vue.extend({ }) this.loadImages() }, + async resizeImage(file: File): Promise { + const resizableTypes = ['image/jpeg', 'image/png', 'image/webp'] + if (this.maxDimension <= 0 || !resizableTypes.includes(file.type)) { + return file + } + + const objectUrl = URL.createObjectURL(file) + const image = await new Promise((resolve, reject) => { + const element = new Image() + element.onload = () => resolve(element) + element.onerror = () => reject(new Error(`Unable to decode ${file.name}`)) + element.src = objectUrl + }).finally(() => URL.revokeObjectURL(objectUrl)) + + const longestSide = Math.max(image.naturalWidth, image.naturalHeight) + if (longestSide <= this.maxDimension) { + return file + } + + const scale = this.maxDimension / longestSide + const canvas = document.createElement('canvas') + canvas.width = Math.max(1, Math.round(image.naturalWidth * scale)) + canvas.height = Math.max(1, Math.round(image.naturalHeight * scale)) + const context = canvas.getContext('2d') + if (!context) { + throw new Error('Unable to create an image resize context') + } + context.drawImage(image, 0, 0, canvas.width, canvas.height) + + return new Promise((resolve, reject) => { + canvas.toBlob((blob) => { + if (blob) { + resolve(blob) + } else { + reject(new Error(`Unable to resize ${file.name}`)) + } + }, file.type) + }) + }, + async uploadImage(file: File, destinationPath: string) { + try { + this.upload_error = null + const resizedImage = await this.resizeImage(file) + await this.uploadFile(resizedImage, destinationPath) + } catch (error) { + this.upload_error = `Error preparing image: ${error}` + console.error(this.upload_error) + } + }, async onDrop(event: DragEvent) { const file = event.dataTransfer?.files?.[0] if (!file) return const fileName = encodeURIComponent(file.name) - await this.uploadFile(file, `${this.directory}/${fileName}`) + await this.uploadImage(file, `${this.directory}/${fileName}`) }, }, }) diff --git a/core/frontend/src/components/app/VehicleBanner.vue b/core/frontend/src/components/app/VehicleBanner.vue index 5ba42f9a01..8fcd4eea62 100755 --- a/core/frontend/src/components/app/VehicleBanner.vue +++ b/core/frontend/src/components/app/VehicleBanner.vue @@ -4,6 +4,7 @@ Date: Sun, 9 Aug 2026 02:13:23 +0700 Subject: [PATCH 2/2] frontend: use image compression for branding uploads Signed-off-by: Daniil Mordanov --- core/frontend/package.json | 1 + .../src/components/app/ImagePicker.vue | 50 +++++++------------ .../src/components/app/VehicleBanner.vue | 2 + core/frontend/yarn.lock | 12 +++++ 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/core/frontend/package.json b/core/frontend/package.json index 00b12e0440..c38eb994ed 100644 --- a/core/frontend/package.json +++ b/core/frontend/package.json @@ -39,6 +39,7 @@ "axios": "^1.12.2", "babel-loader": "^8.2.5", "broadway-player": "^0.1.1", + "browser-image-compression": "^2.0.2", "colorthief": "2.4.0", "cytoscape": "^3.32.0", "cytoscape-fcose": "^2.2.0", diff --git a/core/frontend/src/components/app/ImagePicker.vue b/core/frontend/src/components/app/ImagePicker.vue index a0b17251fd..2e133cffa8 100755 --- a/core/frontend/src/components/app/ImagePicker.vue +++ b/core/frontend/src/components/app/ImagePicker.vue @@ -97,6 +97,7 @@