diff --git a/assets/cms/components/Image/ThumbnailEditor.vue b/assets/cms/components/Image/ThumbnailEditor.vue
index e5745f13..fba754d6 100644
--- a/assets/cms/components/Image/ThumbnailEditor.vue
+++ b/assets/cms/components/Image/ThumbnailEditor.vue
@@ -3,22 +3,16 @@
@@ -42,13 +36,34 @@ export default {
type: Number,
required: true
},
+ sizingMode: {
+ type: String,
+ required: true,
+ validator: function(value) {
+ return ['proportional', 'exact'].indexOf(value) !== -1
+ }
+ },
+ fileId: {
+ type: Number,
+ required: true
+ },
+ fileVersionId: {
+ type: Number,
+ required: true
+ },
+ thumbnailHandle: {
+ type: String,
+ required: true
+ },
accessToken: {
type: String,
required: true
},
lang: {
type: Object,
- required: true
+ default: () => ({
+ save: 'Save'
+ })
},
src: {
type: String,
@@ -64,35 +79,115 @@ export default {
saveInProgress: false
}),
computed: {
- stencilSize: function() {
- const stencilSize = {}
- stencilSize.width = this.width
- stencilSize.height = this.height
- return stencilSize
+ aspectRatio: function() {
+ // Only lock aspect ratio for EXACT mode
+ // For PROPORTIONAL mode, allow free aspect ratio (let user maintain original image ratio)
+ if (this.sizingMode === 'exact') {
+ return this.width / this.height
+ }
+ return undefined
+ },
+ stencilProps: function() {
+ const props = {
+ // Show corner handlers for resizing
+ // For EXACT mode: corners maintain locked aspect ratio
+ // For PROPORTIONAL mode: corners allow free resizing
+ handlers: {
+ eastNorth: true, // top-right corner
+ north: false, // top edge
+ westNorth: true, // top-left corner
+ west: false, // left edge
+ westSouth: true, // bottom-left corner
+ south: false, // bottom edge
+ eastSouth: true, // bottom-right corner
+ east: false // right edge
+ },
+ movable: true,
+ resizable: true,
+ // Show border lines on all four edges for visibility
+ lines: {
+ north: true,
+ east: true,
+ south: true,
+ west: true
+ },
+ // Style the lines for better visibility
+ linesClasses: {
+ default: 'ccm-thumbnail-stencil-line',
+ north: 'ccm-thumbnail-stencil-line--north',
+ east: 'ccm-thumbnail-stencil-line--east',
+ south: 'ccm-thumbnail-stencil-line--south',
+ west: 'ccm-thumbnail-stencil-line--west'
+ },
+ // Add custom class to preview for border styling
+ previewClass: 'ccm-thumbnail-stencil-preview'
+ }
+
+ // Only lock aspect ratio for EXACT mode
+ if (this.aspectRatio !== undefined) {
+ props.minAspectRatio = this.aspectRatio
+ props.maxAspectRatio = this.aspectRatio
+ }
+
+ return props
+ },
+ resizeImageConfig: function() {
+ return {
+ // Don't adjust stencil size when zooming
+ adjustStencil: false,
+ // Enable touch zoom
+ touch: true,
+ // Enable smooth mouse wheel zoom with fine control (2% per wheel tick)
+ wheel: {
+ ratio: 0.02
+ }
+ }
},
canvas: function() {
- const canvas = {}
- canvas.maxWidth = this.width
- canvas.maxHeight = this.height
- return canvas
+ // Don't restrict canvas size - let it scale naturally
+ // The actual output size is controlled by getResult() parameters
+ return {}
}
},
methods: {
zoomIn() {
- this.$refs.cropper.zoom(1.2)
+ if (this.$refs.cropper) {
+ // Zoom in by 5% (1.05x) for finer control
+ this.$refs.cropper.zoom(1.05)
+ }
},
zoomOut() {
- this.$refs.cropper.zoom(0.8)
+ if (this.$refs.cropper) {
+ // Zoom out by 5% (0.95x) for finer control
+ this.$refs.cropper.zoom(0.95)
+ }
},
save() {
var my = this
this.saveInProgress = true
- const { canvas } = this.$refs.cropper.getResult()
- if (canvas) {
+
+ // Get the cropped result
+ const result = this.$refs.cropper.getResult()
+ const sourceCanvas = result.canvas
+
+ if (sourceCanvas) {
+ // Create a new canvas at the exact thumbnail size
+ const canvas = document.createElement('canvas')
+ canvas.width = this.width
+ canvas.height = this.height
+
+ // Draw the cropped image at the exact size
+ const ctx = canvas.getContext('2d')
+ ctx.imageSmoothingEnabled = true
+ ctx.imageSmoothingQuality = 'high'
+ ctx.drawImage(sourceCanvas, 0, 0, this.width, this.height)
+
const form = new FormData()
form.append('ccm_token', this.accessToken)
+ // Convert canvas to data URL for preview update
+ const imgData = canvas.toDataURL('image/png')
canvas.toBlob(blob => {
- form.append('file', blob)
+ form.append('file', blob, 'thumbnail.png')
new ConcreteAjaxRequest({
url: my.uploadUrl,
processData: false,
@@ -100,7 +195,21 @@ export default {
dataType: 'json',
data: form,
success: function(r) {
- jQuery.fn.dialog.closeTop()
+ // Prepare event data
+ const eventData = {
+ isThumbnail: true,
+ handle: my.thumbnailHandle,
+ fID: my.fileId,
+ fvID: my.fileVersionId,
+ imgData: imgData
+ }
+
+ // Trigger namespaced events for different listeners
+ // Each dialog/page has its own namespace to avoid unbinding conflicts
+ Concrete.event.trigger('ImageEditorDidSave.thumbnails.thumbnailsDialog', eventData)
+ Concrete.event.trigger('ImageEditorDidSave.thumbnails.detailsPage', eventData)
+
+ // Show success notification
ConcreteAlert.notify({
message: ccmi18n_filemanager.thumbnailImageSaved,
title: ccmi18n_filemanager.thumbnailImages
@@ -110,8 +219,64 @@ export default {
})
}
}
- },
- mounted() {
}
}
+