From fd84e1c9f829f90d3c7ee61f916fe186cb73671c Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 19 Oct 2020 19:59:54 +0200 Subject: [PATCH 01/27] Refactor crop editor into a proper class. --- public/js/crop_editor.js | 406 ++++++++++++++++++++------------------- 1 file changed, 204 insertions(+), 202 deletions(-) diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index e010d03..8e3884d 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -1,218 +1,220 @@ -function CropEditor(opt) { - this.opt = opt; +class CropEditor { + constructor(opt) { + this.opt = opt; - this.edit_crop_button = document.createElement("span"); - this.edit_crop_button.className = "btn"; - this.edit_crop_button.innerHTML = "Edit crop"; - this.edit_crop_button.addEventListener('click', this.show.bind(this)); + this.edit_crop_button = document.createElement("span"); + this.edit_crop_button.className = "btn"; + this.edit_crop_button.textContent = "Edit crop"; + this.edit_crop_button.addEventListener('click', this.show.bind(this)); - this.thumbnail_select = document.getElementById(opt.thumbnail_select_id); - this.thumbnail_select.addEventListener('change', this.toggleCropButton.bind(this)); - this.thumbnail_select.parentNode.insertBefore(this.edit_crop_button, this.thumbnail_select.nextSibling); + this.thumbnail_select = document.getElementById(opt.thumbnail_select_id); + this.thumbnail_select.addEventListener('change', this.toggleCropButton.bind(this)); + this.thumbnail_select.parentNode.insertBefore(this.edit_crop_button, this.thumbnail_select.nextSibling); - this.toggleCropButton(); -} - -CropEditor.prototype.buildContainer = function() { - this.container = document.createElement("div"); - this.container.id = "crop_editor"; - - this.position = document.createElement("div"); - this.position.className = "crop_position"; - this.container.appendChild(this.position); - - var source_x_label = document.createTextNode("Source X:"); - this.position.appendChild(source_x_label); - - this.source_x = document.createElement("input"); - this.source_x.addEventListener("keyup", this.positionBoundary.bind(this)); - this.position.appendChild(this.source_x); - - var source_y_label = document.createTextNode("Source Y:"); - this.position.appendChild(source_y_label); - - this.source_y = document.createElement("input"); - this.source_y.addEventListener("keyup", this.positionBoundary.bind(this)); - this.position.appendChild(this.source_y); - - var crop_width_label = document.createTextNode("Crop width:"); - this.position.appendChild(crop_width_label); - - this.crop_width = document.createElement("input"); - this.crop_width.addEventListener("keyup", this.positionBoundary.bind(this)); - this.position.appendChild(this.crop_width); - - var crop_height_label = document.createTextNode("Crop height:"); - this.position.appendChild(crop_height_label); - - this.crop_height = document.createElement("input"); - this.crop_height.addEventListener("keyup", this.positionBoundary.bind(this)); - this.position.appendChild(this.crop_height); - - this.save_button = document.createElement("span"); - this.save_button.className = "btn"; - this.save_button.innerHTML = "Save"; - this.save_button.addEventListener('click', this.save.bind(this)); - this.position.appendChild(this.save_button); - - this.abort_button = document.createElement("span"); - this.abort_button.className = "btn btn-red"; - this.abort_button.innerHTML = "Abort"; - this.abort_button.addEventListener('click', this.hide.bind(this)); - this.position.appendChild(this.abort_button); - - this.image_container = document.createElement("div"); - this.image_container.className = "crop_image_container"; - this.container.appendChild(this.image_container); - - this.crop_boundary = document.createElement("div"); - this.crop_boundary.id = "crop_boundary"; - this.image_container.appendChild(this.crop_boundary); - - this.original_image = document.createElement("img"); - this.original_image.id = "original_image"; - this.original_image.src = this.opt.original_image_src; - this.image_container.appendChild(this.original_image); - - this.parent = document.getElementById(this.opt.editor_container_parent_id); - this.parent.appendChild(this.container); -}; - -CropEditor.prototype.setInputValues = function() { - var current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset; - - if (typeof current.crop_region === "undefined") { - var source_ratio = this.original_image.naturalWidth / this.original_image.naturalHeight, - crop_ratio = current.crop_width / current.crop_height, - min_dim = Math.min(this.original_image.naturalWidth, this.original_image.naturalHeight); - - // Cropping from the centre? - if (current.crop_method === "c") { - // Crop vertically from the centre, using the entire width. - if (source_ratio < crop_ratio) { - this.crop_width.value = this.original_image.naturalWidth; - this.crop_height.value = Math.ceil(this.original_image.naturalWidth / crop_ratio); - this.source_x.value = 0; - this.source_y.value = Math.ceil((this.original_image.naturalHeight - this.crop_height.value) / 2); - } - // Crop horizontally from the centre, using the entire height. - else { - this.crop_width.value = Math.ceil(current.crop_width * this.original_image.naturalHeight / current.crop_height); - this.crop_height.value = this.original_image.naturalHeight; - this.source_x.value = Math.ceil((this.original_image.naturalWidth - this.crop_width.value) / 2); - this.source_y.value = 0; - } - } - // Cropping a top or bottom slice? - else { - // Can we actually take a top or bottom slice from the original image? - if (source_ratio < crop_ratio) { - this.crop_width.value = this.original_image.naturalWidth; - this.crop_height.value = Math.floor(this.original_image.naturalHeight / crop_ratio); - this.source_x.value = "0"; - this.source_y.value = current.crop_method.indexOf("t") !== -1 ? "0" : this.original_image.naturalHeight - this.crop_height.value; - } - // Otherwise, take a vertical slice from the centre. - else { - this.crop_width.value = Math.floor(this.original_image.naturalHeight * crop_ratio); - this.crop_height.value = this.original_image.naturalHeight; - this.source_x.value = Math.floor((this.original_image.naturalWidth - this.crop_width.value) / 2); - this.source_y.value = "0"; - } - } - } else { - var region = current.crop_region.split(','); - this.crop_width.value = region[0]; - this.crop_height.value = region[1]; - this.source_x.value = region[2]; - this.source_y.value = region[3]; - } -}; - -CropEditor.prototype.showContainer = function() { - this.container.style.display = "block"; - this.setInputValues(); - this.positionBoundary(); -} - -CropEditor.prototype.save = function() { - var current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset; - var payload = { - thumb_width: current.crop_width, - thumb_height: current.crop_height, - crop_method: current.crop_method, - crop_width: this.crop_width.value, - crop_height: this.crop_height.value, - source_x: this.source_x.value, - source_y: this.source_y.value - }; - var req = HttpRequest("post", this.opt.submitUrl + "?id=" + this.opt.asset_id + "&updatethumb", - "data=" + encodeURIComponent(JSON.stringify(payload)), function(response) { - this.opt.after_save(response); - this.hide(); - }.bind(this)); -}; - -CropEditor.prototype.show = function() { - if (typeof this.container === "undefined") { - this.buildContainer(); + this.toggleCropButton(); } - // Defer showing and positioning until image is loaded. - // !!! TODO: add a spinner in the mean time? - if (this.original_image.naturalWidth > 0) { - this.showContainer(); - } else { - this.original_image.addEventListener("load", function() { + buildContainer() { + this.container = document.createElement("div"); + this.container.id = "crop_editor"; + + this.position = document.createElement("div"); + this.position.className = "crop_position"; + this.container.appendChild(this.position); + + let source_x_label = document.createTextNode("Source X:"); + this.position.appendChild(source_x_label); + + this.source_x = document.createElement("input"); + this.source_x.addEventListener("keyup", this.positionBoundary.bind(this)); + this.position.appendChild(this.source_x); + + let source_y_label = document.createTextNode("Source Y:"); + this.position.appendChild(source_y_label); + + this.source_y = document.createElement("input"); + this.source_y.addEventListener("keyup", this.positionBoundary.bind(this)); + this.position.appendChild(this.source_y); + + let crop_width_label = document.createTextNode("Crop width:"); + this.position.appendChild(crop_width_label); + + this.crop_width = document.createElement("input"); + this.crop_width.addEventListener("keyup", this.positionBoundary.bind(this)); + this.position.appendChild(this.crop_width); + + let crop_height_label = document.createTextNode("Crop height:"); + this.position.appendChild(crop_height_label); + + this.crop_height = document.createElement("input"); + this.crop_height.addEventListener("keyup", this.positionBoundary.bind(this)); + this.position.appendChild(this.crop_height); + + this.save_button = document.createElement("span"); + this.save_button.className = "btn"; + this.save_button.textContent = "Save"; + this.save_button.addEventListener('click', this.save.bind(this)); + this.position.appendChild(this.save_button); + + this.abort_button = document.createElement("span"); + this.abort_button.className = "btn btn-red"; + this.abort_button.textContent = "Abort"; + this.abort_button.addEventListener('click', this.hide.bind(this)); + this.position.appendChild(this.abort_button); + + this.image_container = document.createElement("div"); + this.image_container.className = "crop_image_container"; + this.container.appendChild(this.image_container); + + this.crop_boundary = document.createElement("div"); + this.crop_boundary.id = "crop_boundary"; + this.image_container.appendChild(this.crop_boundary); + + this.original_image = document.createElement("img"); + this.original_image.id = "original_image"; + this.original_image.src = this.opt.original_image_src; + this.image_container.appendChild(this.original_image); + + this.parent = document.getElementById(this.opt.editor_container_parent_id); + this.parent.appendChild(this.container); + } + + setInputValues() { + let current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset; + + if (typeof current.crop_region === "undefined") { + let source_ratio = this.original_image.naturalWidth / this.original_image.naturalHeight, + crop_ratio = current.crop_width / current.crop_height, + min_dim = Math.min(this.original_image.naturalWidth, this.original_image.naturalHeight); + + // Cropping from the centre? + if (current.crop_method === "c") { + // Crop vertically from the centre, using the entire width. + if (source_ratio < crop_ratio) { + this.crop_width.value = this.original_image.naturalWidth; + this.crop_height.value = Math.ceil(this.original_image.naturalWidth / crop_ratio); + this.source_x.value = 0; + this.source_y.value = Math.ceil((this.original_image.naturalHeight - this.crop_height.value) / 2); + } + // Crop horizontally from the centre, using the entire height. + else { + this.crop_width.value = Math.ceil(current.crop_width * this.original_image.naturalHeight / current.crop_height); + this.crop_height.value = this.original_image.naturalHeight; + this.source_x.value = Math.ceil((this.original_image.naturalWidth - this.crop_width.value) / 2); + this.source_y.value = 0; + } + } + // Cropping a top or bottom slice? + else { + // Can we actually take a top or bottom slice from the original image? + if (source_ratio < crop_ratio) { + this.crop_width.value = this.original_image.naturalWidth; + this.crop_height.value = Math.floor(this.original_image.naturalHeight / crop_ratio); + this.source_x.value = "0"; + this.source_y.value = current.crop_method.indexOf("t") !== -1 ? "0" : this.original_image.naturalHeight - this.crop_height.value; + } + // Otherwise, take a vertical slice from the centre. + else { + this.crop_width.value = Math.floor(this.original_image.naturalHeight * crop_ratio); + this.crop_height.value = this.original_image.naturalHeight; + this.source_x.value = Math.floor((this.original_image.naturalWidth - this.crop_width.value) / 2); + this.source_y.value = "0"; + } + } + } else { + let region = current.crop_region.split(','); + this.crop_width.value = region[0]; + this.crop_height.value = region[1]; + this.source_x.value = region[2]; + this.source_y.value = region[3]; + } + } + + showContainer() { + this.container.style.display = "block"; + this.setInputValues(); + this.positionBoundary(); + } + + save() { + let current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset; + let payload = { + thumb_width: current.crop_width, + thumb_height: current.crop_height, + crop_method: current.crop_method, + crop_width: this.crop_width.value, + crop_height: this.crop_height.value, + source_x: this.source_x.value, + source_y: this.source_y.value + }; + let req = HttpRequest("post", this.opt.submitUrl + "?id=" + this.opt.asset_id + "&updatethumb", + "data=" + encodeURIComponent(JSON.stringify(payload)), function(response) { + this.opt.after_save(response); + this.hide(); + }.bind(this)); + } + + show() { + if (typeof this.container === "undefined") { + this.buildContainer(); + } + + // Defer showing and positioning until image is loaded. + // !!! TODO: add a spinner in the mean time? + if (this.original_image.naturalWidth > 0) { this.showContainer(); - }.bind(this)); + } else { + this.original_image.addEventListener("load", function() { + this.showContainer(); + }.bind(this)); + } } -}; -CropEditor.prototype.hide = function() { - this.container.style.display = "none"; -}; + hide() { + this.container.style.display = "none"; + } -CropEditor.prototype.addEvents = function(event) { - var drag_target = document.getElementById(opt.drag_target); - drag_target.addEventListener('dragstart', this.dragStart); - drag_target.addEventListener('drag', this.drag); - drag_target.addEventListener('dragend', this.dragEnd); -}; + addEvents(event) { + let drag_target = document.getElementById(opt.drag_target); + drag_target.addEventListener('dragstart', this.dragStart); + drag_target.addEventListener('drag', this.drag); + drag_target.addEventListener('dragend', this.dragEnd); + } -CropEditor.prototype.dragStart = function(event) { - console.log(event); - event.preventDefault(); -}; + dragStart(event) { + console.log(event); + event.preventDefault(); + } -CropEditor.prototype.dragEnd = function(event) { - console.log(event); -}; + dragEnd(event) { + console.log(event); + } -CropEditor.prototype.drag = function(event) { - console.log(event); -}; + drag(event) { + console.log(event); + } -CropEditor.prototype.toggleCropButton = function() { - var current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset; - this.edit_crop_button.style.display = typeof current.crop_method === "undefined" ? "none" : ""; -}; + toggleCropButton() { + let current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset; + this.edit_crop_button.style.display = typeof current.crop_method === "undefined" ? "none" : ""; + } -CropEditor.prototype.positionBoundary = function(event) { - var source_x = parseInt(this.source_x.value), - source_y = parseInt(this.source_y.value), - crop_width = parseInt(this.crop_width.value), - crop_height = parseInt(this.crop_height.value), - real_width = this.original_image.naturalWidth, - real_height = this.original_image.naturalHeight, - scaled_width = this.original_image.clientWidth, - scaled_height = this.original_image.clientHeight; + positionBoundary(event) { + let source_x = parseInt(this.source_x.value), + source_y = parseInt(this.source_y.value), + crop_width = parseInt(this.crop_width.value), + crop_height = parseInt(this.crop_height.value), + real_width = this.original_image.naturalWidth, + real_height = this.original_image.naturalHeight, + scaled_width = this.original_image.clientWidth, + scaled_height = this.original_image.clientHeight; - var width_scale = scaled_width / real_width, - height_scale = scaled_height / real_height; + let width_scale = scaled_width / real_width, + height_scale = scaled_height / real_height; - crop_boundary.style.left = (this.source_x.value) * width_scale + "px"; - crop_boundary.style.top = (this.source_y.value) * height_scale + "px"; - crop_boundary.style.width = (this.crop_width.value) * width_scale + "px"; - crop_boundary.style.height = (this.crop_height.value) * height_scale + "px"; -}; + crop_boundary.style.left = (this.source_x.value) * width_scale + "px"; + crop_boundary.style.top = (this.source_y.value) * height_scale + "px"; + crop_boundary.style.width = (this.crop_width.value) * width_scale + "px"; + crop_boundary.style.height = (this.crop_height.value) * height_scale + "px"; + } +} \ No newline at end of file From 2c68b6a7984eedc5482134b058798bb13f12577d Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 23 Nov 2020 12:22:05 +0100 Subject: [PATCH 02/27] Improve crop editor styling. --- public/css/admin.css | 9 +++++---- public/js/crop_editor.js | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/public/css/admin.css b/public/css/admin.css index 4e58d3e..af84404 100644 --- a/public/css/admin.css +++ b/public/css/admin.css @@ -150,7 +150,7 @@ body { left: 0; height: 100%; width: 100%; - background: #000; + background: rgba(0, 0, 0, 0.8); z-index: 100; color: #fff; } @@ -163,6 +163,8 @@ body { position: relative; } .crop_position { + background: rgba(0, 0, 0, 1.0); + border: none; padding: 5px; text-align: center; } @@ -176,15 +178,14 @@ body { max-height: 700px; } #crop_boundary { - border: 1px solid rgba(255, 255, 255, 0.75); - background: rgba(255, 255, 255, 0.75); + border: 1px dashed rgb(255, 255, 255); + background: rgba(255, 255, 255, 0.4); position: absolute; z-index: 200; width: 500px; height: 300px; top: 400px; left: 300px; - filter: invert(100%); /* temp */ } diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index 8e3884d..a067910 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -18,7 +18,7 @@ class CropEditor { this.container = document.createElement("div"); this.container.id = "crop_editor"; - this.position = document.createElement("div"); + this.position = document.createElement("fieldset"); this.position.className = "crop_position"; this.container.appendChild(this.position); @@ -217,4 +217,4 @@ class CropEditor { crop_boundary.style.width = (this.crop_width.value) * width_scale + "px"; crop_boundary.style.height = (this.crop_height.value) * height_scale + "px"; } -} \ No newline at end of file +} From d4cc72304edc4c5364f0c66bdb016794f6374df4 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 23 Nov 2020 12:35:20 +0100 Subject: [PATCH 03/27] Use flexbox for crop editor box sizing. --- public/css/admin.css | 3 +++ public/js/crop_editor.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/public/css/admin.css b/public/css/admin.css index af84404..36534d2 100644 --- a/public/css/admin.css +++ b/public/css/admin.css @@ -145,6 +145,8 @@ body { /* Crop editor ----------------*/ #crop_editor { + display: flex; + flex-direction: column; position: fixed; top: 0; left: 0; @@ -161,6 +163,7 @@ body { } .crop_image_container { position: relative; + flex-grow: 1; } .crop_position { background: rgba(0, 0, 0, 1.0); diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index a067910..ec1511a 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -131,7 +131,7 @@ class CropEditor { } showContainer() { - this.container.style.display = "block"; + this.container.style.display = ''; this.setInputValues(); this.positionBoundary(); } From 9d95df81fe68ee12f48275eea4f28b9c7f79527d Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 23 Nov 2020 12:35:42 +0100 Subject: [PATCH 04/27] WIP: finally implement drag events. --- public/js/crop_editor.js | 28 ++++++++++++++++++++-------- templates/EditAssetForm.php | 2 +- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index ec1511a..f272e9d 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -71,6 +71,7 @@ class CropEditor { this.image_container.appendChild(this.crop_boundary); this.original_image = document.createElement("img"); + this.original_image.draggable = false; this.original_image.id = "original_image"; this.original_image.src = this.opt.original_image_src; this.image_container.appendChild(this.original_image); @@ -134,6 +135,7 @@ class CropEditor { this.container.style.display = ''; this.setInputValues(); this.positionBoundary(); + this.addEvents(); } save() { @@ -175,23 +177,33 @@ class CropEditor { } addEvents(event) { - let drag_target = document.getElementById(opt.drag_target); - drag_target.addEventListener('dragstart', this.dragStart); - drag_target.addEventListener('drag', this.drag); - drag_target.addEventListener('dragend', this.dragEnd); + let drag_target = this.image_container; + drag_target.addEventListener('mousedown', this.dragStart); + drag_target.addEventListener('mousemove', this.drag); + drag_target.addEventListener('mouseup', this.dragEnd); + drag_target.addEventListener('mouseout', this.dragEnd); + + this.original_image.addEventListener('mousedown', event => {return false}); } dragStart(event) { - console.log(event); - event.preventDefault(); + this.isDragging = true; + console.log(`Start @ x=${event.pageX}, y=${event.pageY}, which=${event.which}`); } dragEnd(event) { - console.log(event); + if (!this.isDragging) { + return; + } + this.isDragging = false; + console.log(`End @ x=${event.pageX}, y=${event.pageY}, which=${event.which}`); } drag(event) { - console.log(event); + if (!this.isDragging) { + return; + } + console.log(`Drag @ x=${event.pageX}, y=${event.pageY}, which=${event.which}`); } toggleCropButton() { diff --git a/templates/EditAssetForm.php b/templates/EditAssetForm.php index 1dd3ed6..55911d7 100644 --- a/templates/EditAssetForm.php +++ b/templates/EditAssetForm.php @@ -200,7 +200,7 @@ class EditAssetForm extends SubTemplate original_image_src: "', $this->asset->getUrl(), '", editor_container_parent_id: "asset_form", thumbnail_select_id: "thumbnail_src", - drag_target: "drag_target", + drag_target: ".crop_image_container", asset_id: ', $this->asset->getId(), ', after_save: function(data) { // Update thumbnail From c392105814e9f0b9e1b5a54be0eaaf3e44597b5b Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 23 Nov 2020 12:42:02 +0100 Subject: [PATCH 05/27] Refactor crop editor DOM functions. --- public/js/crop_editor.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index f272e9d..043d064 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -14,10 +14,18 @@ class CropEditor { this.toggleCropButton(); } - buildContainer() { + initDOM() { this.container = document.createElement("div"); this.container.id = "crop_editor"; + this.initPositionForm(); + this.initImageContainer(); + + this.parent = document.getElementById(this.opt.editor_container_parent_id); + this.parent.appendChild(this.container); + } + + initPositionForm() { this.position = document.createElement("fieldset"); this.position.className = "crop_position"; this.container.appendChild(this.position); @@ -61,7 +69,9 @@ class CropEditor { this.abort_button.textContent = "Abort"; this.abort_button.addEventListener('click', this.hide.bind(this)); this.position.appendChild(this.abort_button); + } + initImageContainer() { this.image_container = document.createElement("div"); this.image_container.className = "crop_image_container"; this.container.appendChild(this.image_container); @@ -75,9 +85,6 @@ class CropEditor { this.original_image.id = "original_image"; this.original_image.src = this.opt.original_image_src; this.image_container.appendChild(this.original_image); - - this.parent = document.getElementById(this.opt.editor_container_parent_id); - this.parent.appendChild(this.container); } setInputValues() { @@ -158,7 +165,7 @@ class CropEditor { show() { if (typeof this.container === "undefined") { - this.buildContainer(); + this.initDOM(); } // Defer showing and positioning until image is loaded. @@ -166,9 +173,7 @@ class CropEditor { if (this.original_image.naturalWidth > 0) { this.showContainer(); } else { - this.original_image.addEventListener("load", function() { - this.showContainer(); - }.bind(this)); + this.original_image.addEventListener("load", event => this.showContainer()); } } From 837c92db441f45dacf2d92525fc311f5ba06c5f6 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 23 Nov 2020 17:52:54 +0100 Subject: [PATCH 06/27] CropEditor: split setDefaultCrop from setInputValues and rename. --- public/js/crop_editor.js | 84 +++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index 043d064..770445a 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -87,48 +87,52 @@ class CropEditor { this.image_container.appendChild(this.original_image); } - setInputValues() { + setDefaultCrop(cropAspectRatio, cropMethod) { + let source = this.original_image; + let sourceAspectRatio = source.naturalWidth / source.naturalHeight; + + // Cropping from the centre? + if (cropMethod === "c") { + // Crop vertically from the centre, using the entire width. + if (sourceAspectRatio < cropAspectRatio) { + this.crop_width.value = source.naturalWidth; + this.crop_height.value = Math.ceil(source.naturalWidth / cropAspectRatio); + this.source_x.value = 0; + this.source_y.value = Math.ceil((source.naturalHeight - this.crop_height.value) / 2); + } + // Crop horizontally from the centre, using the entire height. + else { + this.crop_width.value = Math.ceil(cropAspectRatio * source.naturalHeight); + this.crop_height.value = source.naturalHeight; + this.source_x.value = Math.ceil((source.naturalWidth - this.crop_width.value) / 2); + this.source_y.value = 0; + } + } + // Cropping a top or bottom slice? + else { + // Can we actually take a top or bottom slice from the original image? + if (sourceAspectRatio < cropAspectRatio) { + this.crop_width.value = source.naturalWidth; + this.crop_height.value = Math.floor(source.naturalHeight / cropAspectRatio); + this.source_x.value = "0"; + this.source_y.value = cropMethod.indexOf("t") !== -1 ? "0" : source.naturalHeight - this.crop_height.value; + } + // Otherwise, take a vertical slice from the centre. + else { + this.crop_width.value = Math.floor(source.naturalHeight * cropAspectRatio); + this.crop_height.value = source.naturalHeight; + this.source_x.value = Math.floor((source.naturalWidth - this.crop_width.value) / 2); + this.source_y.value = "0"; + } + } + } + + setPositionFormValues() { let current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset; if (typeof current.crop_region === "undefined") { - let source_ratio = this.original_image.naturalWidth / this.original_image.naturalHeight, - crop_ratio = current.crop_width / current.crop_height, - min_dim = Math.min(this.original_image.naturalWidth, this.original_image.naturalHeight); - - // Cropping from the centre? - if (current.crop_method === "c") { - // Crop vertically from the centre, using the entire width. - if (source_ratio < crop_ratio) { - this.crop_width.value = this.original_image.naturalWidth; - this.crop_height.value = Math.ceil(this.original_image.naturalWidth / crop_ratio); - this.source_x.value = 0; - this.source_y.value = Math.ceil((this.original_image.naturalHeight - this.crop_height.value) / 2); - } - // Crop horizontally from the centre, using the entire height. - else { - this.crop_width.value = Math.ceil(current.crop_width * this.original_image.naturalHeight / current.crop_height); - this.crop_height.value = this.original_image.naturalHeight; - this.source_x.value = Math.ceil((this.original_image.naturalWidth - this.crop_width.value) / 2); - this.source_y.value = 0; - } - } - // Cropping a top or bottom slice? - else { - // Can we actually take a top or bottom slice from the original image? - if (source_ratio < crop_ratio) { - this.crop_width.value = this.original_image.naturalWidth; - this.crop_height.value = Math.floor(this.original_image.naturalHeight / crop_ratio); - this.source_x.value = "0"; - this.source_y.value = current.crop_method.indexOf("t") !== -1 ? "0" : this.original_image.naturalHeight - this.crop_height.value; - } - // Otherwise, take a vertical slice from the centre. - else { - this.crop_width.value = Math.floor(this.original_image.naturalHeight * crop_ratio); - this.crop_height.value = this.original_image.naturalHeight; - this.source_x.value = Math.floor((this.original_image.naturalWidth - this.crop_width.value) / 2); - this.source_y.value = "0"; - } - } + let aspectRatio = current.crop_width / current.crop_height; + this.setDefaultCrop(aspectRatio, current.crop_method); } else { let region = current.crop_region.split(','); this.crop_width.value = region[0]; @@ -140,7 +144,7 @@ class CropEditor { showContainer() { this.container.style.display = ''; - this.setInputValues(); + this.setPositionFormValues(); this.positionBoundary(); this.addEvents(); } From 8e7a09f3f30e7609a8ae58f9e37446e0a1245604 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 23 Nov 2020 18:13:38 +0100 Subject: [PATCH 07/27] Initial version of crop boundary dragging. --- public/js/crop_editor.js | 47 +++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index 770445a..92c4caf 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -187,16 +187,19 @@ class CropEditor { addEvents(event) { let drag_target = this.image_container; - drag_target.addEventListener('mousedown', this.dragStart); - drag_target.addEventListener('mousemove', this.drag); - drag_target.addEventListener('mouseup', this.dragEnd); - drag_target.addEventListener('mouseout', this.dragEnd); + drag_target.addEventListener('mousedown', this.dragStart.bind(this)); + drag_target.addEventListener('mousemove', this.drag.bind(this)); + drag_target.addEventListener('mouseup', this.dragEnd.bind(this)); + // drag_target.addEventListener('mouseout', this.dragEnd.bind(this)); this.original_image.addEventListener('mousedown', event => {return false}); + this.original_image.addEventListener('dragstart', event => {return false}); } dragStart(event) { this.isDragging = true; + this.dragStartX = event.pageX - event.target.offsetLeft; + this.dragStartY = event.pageY - event.target.offsetTop; console.log(`Start @ x=${event.pageX}, y=${event.pageY}, which=${event.which}`); } @@ -206,13 +209,47 @@ class CropEditor { } this.isDragging = false; console.log(`End @ x=${event.pageX}, y=${event.pageY}, which=${event.which}`); + this.handleDragEvent(event); } drag(event) { if (!this.isDragging) { return; } - console.log(`Drag @ x=${event.pageX}, y=${event.pageY}, which=${event.which}`); + // console.log(`Drag @ x=${event.pageX}, y=${event.pageY}, which=${event.which}`); + this.handleDragEvent(event); + } + + getScaleFactor() { + let real_width = this.original_image.naturalWidth, + real_height = this.original_image.naturalHeight, + scaled_width = this.original_image.clientWidth, + scaled_height = this.original_image.clientHeight; + + return scaled_width / real_width; + } + + handleDragEvent(event) { + if (!this.isDragging) { + return; + } + + let curX = event.pageX - event.target.offsetLeft, + curY = event.pageY - event.target.offsetTop; + + this.dragEndX = Math.max(curX, this.dragStartX); + this.dragEndY = Math.max(curY, this.dragStartY); + this.dragStartX = Math.min(curX, this.dragStartX); + this.dragStartY = Math.min(curY, this.dragStartY); + + let scaleFactor = this.getScaleFactor(); + + this.source_x.value = this.dragStartX; + this.source_y.value = this.dragStartY; + this.crop_width.value = Math.ceil(this.dragEndX / scaleFactor); + this.crop_height.value = Math.ceil(this.dragEndY / scaleFactor); + + this.positionBoundary(); } toggleCropButton() { From 5895f4faa63902b325a058172336163d950f3e41 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 23 Nov 2020 18:13:57 +0100 Subject: [PATCH 08/27] Rewrite CropEditor.positionBoundary --- public/js/crop_editor.js | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index 92c4caf..172733f 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -258,21 +258,10 @@ class CropEditor { } positionBoundary(event) { - let source_x = parseInt(this.source_x.value), - source_y = parseInt(this.source_y.value), - crop_width = parseInt(this.crop_width.value), - crop_height = parseInt(this.crop_height.value), - real_width = this.original_image.naturalWidth, - real_height = this.original_image.naturalHeight, - scaled_width = this.original_image.clientWidth, - scaled_height = this.original_image.clientHeight; - - let width_scale = scaled_width / real_width, - height_scale = scaled_height / real_height; - - crop_boundary.style.left = (this.source_x.value) * width_scale + "px"; - crop_boundary.style.top = (this.source_y.value) * height_scale + "px"; - crop_boundary.style.width = (this.crop_width.value) * width_scale + "px"; - crop_boundary.style.height = (this.crop_height.value) * height_scale + "px"; + let scaleFactor = this.getScaleFactor(); + crop_boundary.style.left = parseInt(this.source_x.value) * scaleFactor + "px"; + crop_boundary.style.top = parseInt(this.source_y.value) * scaleFactor + "px"; + crop_boundary.style.width = parseInt(this.crop_width.value) * scaleFactor + "px"; + crop_boundary.style.height = parseInt(this.crop_height.value) * scaleFactor + "px"; } } From 893d31af520c725e50651c6db9d2a51949e14deb Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 23 Nov 2020 19:00:42 +0100 Subject: [PATCH 09/27] Proper dragging of the crop bounding rectangle/area. --- public/js/crop_editor.js | 45 ++++++++++++---------------------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index 172733f..9fb700e 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -198,35 +198,21 @@ class CropEditor { dragStart(event) { this.isDragging = true; - this.dragStartX = event.pageX - event.target.offsetLeft; - this.dragStartY = event.pageY - event.target.offsetTop; - console.log(`Start @ x=${event.pageX}, y=${event.pageY}, which=${event.which}`); + this.dragStartX = event.x - this.image_container.offsetLeft; + this.dragStartY = event.y - this.image_container.offsetTop; } dragEnd(event) { - if (!this.isDragging) { - return; - } this.isDragging = false; - console.log(`End @ x=${event.pageX}, y=${event.pageY}, which=${event.which}`); this.handleDragEvent(event); } drag(event) { - if (!this.isDragging) { - return; - } - // console.log(`Drag @ x=${event.pageX}, y=${event.pageY}, which=${event.which}`); this.handleDragEvent(event); } getScaleFactor() { - let real_width = this.original_image.naturalWidth, - real_height = this.original_image.naturalHeight, - scaled_width = this.original_image.clientWidth, - scaled_height = this.original_image.clientHeight; - - return scaled_width / real_width; + return this.original_image.naturalWidth / this.original_image.clientWidth; } handleDragEvent(event) { @@ -234,20 +220,15 @@ class CropEditor { return; } - let curX = event.pageX - event.target.offsetLeft, - curY = event.pageY - event.target.offsetTop; - - this.dragEndX = Math.max(curX, this.dragStartX); - this.dragEndY = Math.max(curY, this.dragStartY); - this.dragStartX = Math.min(curX, this.dragStartX); - this.dragStartY = Math.min(curY, this.dragStartY); + this.dragEndX = event.x - this.image_container.offsetLeft; + this.dragEndY = event.y - this.image_container.offsetTop; let scaleFactor = this.getScaleFactor(); - this.source_x.value = this.dragStartX; - this.source_y.value = this.dragStartY; - this.crop_width.value = Math.ceil(this.dragEndX / scaleFactor); - this.crop_height.value = Math.ceil(this.dragEndY / scaleFactor); + this.source_x.value = Math.ceil(Math.min(this.dragStartX, this.dragEndX) * scaleFactor); + this.source_y.value = Math.ceil(Math.min(this.dragStartY, this.dragEndY) * scaleFactor); + this.crop_width.value = Math.ceil(Math.abs(this.dragEndX - this.dragStartX) * scaleFactor); + this.crop_height.value = Math.ceil(Math.abs(this.dragEndY - this.dragStartY) * scaleFactor); this.positionBoundary(); } @@ -259,9 +240,9 @@ class CropEditor { positionBoundary(event) { let scaleFactor = this.getScaleFactor(); - crop_boundary.style.left = parseInt(this.source_x.value) * scaleFactor + "px"; - crop_boundary.style.top = parseInt(this.source_y.value) * scaleFactor + "px"; - crop_boundary.style.width = parseInt(this.crop_width.value) * scaleFactor + "px"; - crop_boundary.style.height = parseInt(this.crop_height.value) * scaleFactor + "px"; + crop_boundary.style.left = parseInt(this.source_x.value) / scaleFactor + "px"; + crop_boundary.style.top = parseInt(this.source_y.value) / scaleFactor + "px"; + crop_boundary.style.width = parseInt(this.crop_width.value) / scaleFactor + "px"; + crop_boundary.style.height = parseInt(this.crop_height.value) / scaleFactor + "px"; } } From e84c4f2b4383c0057cc59dfd2d1306de0bde090a Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 23 Nov 2020 20:20:37 +0100 Subject: [PATCH 10/27] Constrain crop selection to image dimensions. --- public/js/crop_editor.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index 9fb700e..9f4a6ca 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -227,8 +227,12 @@ class CropEditor { this.source_x.value = Math.ceil(Math.min(this.dragStartX, this.dragEndX) * scaleFactor); this.source_y.value = Math.ceil(Math.min(this.dragStartY, this.dragEndY) * scaleFactor); - this.crop_width.value = Math.ceil(Math.abs(this.dragEndX - this.dragStartX) * scaleFactor); - this.crop_height.value = Math.ceil(Math.abs(this.dragEndY - this.dragStartY) * scaleFactor); + + let width = Math.ceil(Math.abs(this.dragEndX - this.dragStartX) * scaleFactor); + this.crop_width.value = Math.min(width, this.original_image.naturalWidth - this.source_x.value); + + let height = Math.ceil(Math.abs(this.dragEndY - this.dragStartY) * scaleFactor); + this.crop_height.value = Math.min(height, this.original_image.naturalHeight - this.source_y.value); this.positionBoundary(); } From 5e0d4df2f75f12cca6a5841360f99c62ff1b5fc3 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 23 Nov 2020 20:36:46 +0100 Subject: [PATCH 11/27] Allow moving/dragging the crop boundary. Currently unconstrained. --- public/css/admin.css | 1 + public/js/crop_editor.js | 70 +++++++++++++++++++++++++++++++++------- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/public/css/admin.css b/public/css/admin.css index 36534d2..34a56b4 100644 --- a/public/css/admin.css +++ b/public/css/admin.css @@ -183,6 +183,7 @@ body { #crop_boundary { border: 1px dashed rgb(255, 255, 255); background: rgba(255, 255, 255, 0.4); + cursor: move; position: absolute; z-index: 200; width: 500px; diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index 9f4a6ca..e603210 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -186,36 +186,44 @@ class CropEditor { } addEvents(event) { - let drag_target = this.image_container; - drag_target.addEventListener('mousedown', this.dragStart.bind(this)); - drag_target.addEventListener('mousemove', this.drag.bind(this)); - drag_target.addEventListener('mouseup', this.dragEnd.bind(this)); - // drag_target.addEventListener('mouseout', this.dragEnd.bind(this)); + let cropTarget = this.image_container; + cropTarget.addEventListener('mousedown', this.cropSelectionStart.bind(this)); + cropTarget.addEventListener('mousemove', this.cropSelection.bind(this)); + cropTarget.addEventListener('mouseup', this.cropSelectionEnd.bind(this)); + // cropTarget.addEventListener('mouseout', this.cropSelectionEnd.bind(this)); this.original_image.addEventListener('mousedown', event => {return false}); this.original_image.addEventListener('dragstart', event => {return false}); + + let moveTarget = this.crop_boundary; + moveTarget.addEventListener('mousedown', this.moveSelectionStart.bind(this)); + moveTarget.addEventListener('mousemove', this.moveSelection.bind(this)); + moveTarget.addEventListener('mouseup', this.moveSelectionEnd.bind(this)); } - dragStart(event) { + cropSelectionStart(event) { + if (this.isMoving) { + return false; + } this.isDragging = true; this.dragStartX = event.x - this.image_container.offsetLeft; this.dragStartY = event.y - this.image_container.offsetTop; } - dragEnd(event) { + cropSelectionEnd(event) { this.isDragging = false; - this.handleDragEvent(event); + this.handleCropSelectionEvent(event); } - drag(event) { - this.handleDragEvent(event); + cropSelection(event) { + this.handleCropSelectionEvent(event); } getScaleFactor() { return this.original_image.naturalWidth / this.original_image.clientWidth; } - handleDragEvent(event) { + handleCropSelectionEvent(event) { if (!this.isDragging) { return; } @@ -237,6 +245,46 @@ class CropEditor { this.positionBoundary(); } + handleCropMoveEvent(event) { + if (!this.isMoving) { + return; + } + + this.dragEndX = event.x - this.crop_boundary.offsetLeft; + this.dragEndY = event.y - this.crop_boundary.offsetTop; + + let scaleFactor = this.getScaleFactor(); + + this.source_x.value = parseInt(this.source_x.value) + Math.ceil((this.dragEndX - this.dragStartX) * scaleFactor); + this.source_y.value = parseInt(this.source_y.value) + Math.ceil((this.dragEndY - this.dragStartY) * scaleFactor); + + // let width = Math.ceil(Math.abs(this.dragEndX - this.dragStartX) * scaleFactor); + // this.crop_width.value = Math.min(width, this.original_image.naturalWidth - this.source_x.value); + + // let height = Math.ceil(Math.abs(this.dragEndY - this.dragStartY) * scaleFactor); + // this.crop_height.value = Math.min(height, this.original_image.naturalHeight - this.source_y.value); + + this.positionBoundary(); + } + + moveSelectionStart(event) { + if (this.isDragging) { + return false; + } + this.isMoving = true; + this.dragStartX = event.x - this.crop_boundary.offsetLeft; + this.dragStartY = event.y - this.crop_boundary.offsetTop; + } + + moveSelectionEnd(event) { + this.isMoving = false; + this.handleCropMoveEvent(event); + } + + moveSelection(event) { + this.handleCropMoveEvent(event); + } + toggleCropButton() { let current = this.thumbnail_select.options[this.thumbnail_select.selectedIndex].dataset; this.edit_crop_button.style.display = typeof current.crop_method === "undefined" ? "none" : ""; From 2a740d8cefc446df39df023b7f457310470b970c Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 23 Nov 2020 20:46:13 +0100 Subject: [PATCH 12/27] Constrain boundary movement to image canvas. --- public/js/crop_editor.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index e603210..ddfab11 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -255,14 +255,17 @@ class CropEditor { let scaleFactor = this.getScaleFactor(); - this.source_x.value = parseInt(this.source_x.value) + Math.ceil((this.dragEndX - this.dragStartX) * scaleFactor); - this.source_y.value = parseInt(this.source_y.value) + Math.ceil((this.dragEndY - this.dragStartY) * scaleFactor); + let x = parseInt(this.source_x.value) + Math.ceil((this.dragEndX - this.dragStartX) * scaleFactor); + if (x + parseInt(this.crop_width.value) > this.original_image.naturalWidth) { + x += this.original_image.naturalWidth - (x + parseInt(this.crop_width.value)); + } + this.source_x.value = Math.max(x, 0); - // let width = Math.ceil(Math.abs(this.dragEndX - this.dragStartX) * scaleFactor); - // this.crop_width.value = Math.min(width, this.original_image.naturalWidth - this.source_x.value); - - // let height = Math.ceil(Math.abs(this.dragEndY - this.dragStartY) * scaleFactor); - // this.crop_height.value = Math.min(height, this.original_image.naturalHeight - this.source_y.value); + let y = parseInt(this.source_y.value) + Math.ceil((this.dragEndY - this.dragStartY) * scaleFactor); + if (y + parseInt(this.crop_height.value) > this.original_image.naturalHeight) { + y += this.original_image.naturalHeight - (y + parseInt(this.crop_height.value)); + } + this.source_y.value = Math.max(y, 0); this.positionBoundary(); } From 93884e2e93571a7e6996b542b03717784b4793d0 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Tue, 24 Nov 2020 11:33:16 +0100 Subject: [PATCH 13/27] Fix initial slicing dimensions in CropEditor.setDefaultCrop. Subtle bug. This has been in for years... :) --- public/js/crop_editor.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/js/crop_editor.js b/public/js/crop_editor.js index ddfab11..e1e89e5 100644 --- a/public/js/crop_editor.js +++ b/public/js/crop_editor.js @@ -94,7 +94,7 @@ class CropEditor { // Cropping from the centre? if (cropMethod === "c") { // Crop vertically from the centre, using the entire width. - if (sourceAspectRatio < cropAspectRatio) { + if (sourceAspectRatio <= cropAspectRatio) { this.crop_width.value = source.naturalWidth; this.crop_height.value = Math.ceil(source.naturalWidth / cropAspectRatio); this.source_x.value = 0; @@ -111,9 +111,9 @@ class CropEditor { // Cropping a top or bottom slice? else { // Can we actually take a top or bottom slice from the original image? - if (sourceAspectRatio < cropAspectRatio) { + if (sourceAspectRatio <= cropAspectRatio) { this.crop_width.value = source.naturalWidth; - this.crop_height.value = Math.floor(source.naturalHeight / cropAspectRatio); + this.crop_height.value = Math.floor(source.naturalWidth / cropAspectRatio); this.source_x.value = "0"; this.source_y.value = cropMethod.indexOf("t") !== -1 ? "0" : source.naturalHeight - this.crop_height.value; } From 340ed8427205c51a699229e0482007202fa594f2 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Tue, 24 Nov 2020 11:41:15 +0100 Subject: [PATCH 14/27] Show first available thumbnail instead of a potentially hidden one. --- templates/EditAssetForm.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/templates/EditAssetForm.php b/templates/EditAssetForm.php index 55911d7..dc7682c 100644 --- a/templates/EditAssetForm.php +++ b/templates/EditAssetForm.php @@ -138,11 +138,14 @@ class EditAssetForm extends SubTemplate

Thumbnails

View: