a LOT of cleanup

This commit is contained in:
Patrick 2026-03-28 17:30:48 +01:00
parent 410ad6c7b9
commit 21f8096636
4 changed files with 280 additions and 218 deletions

View File

@ -23,20 +23,6 @@ function gallery_app_init($hook) {
// Wordpress Media Library Dialog
wp_enqueue_media();
// Image Cropping Library
/*wp_enqueue_style(
'cropper-css',
'https://unpkg.com/cropperjs@2.1.0/dist/cropper.css',
array(),
null
);
wp_enqueue_script(
'cropper-js',
'https://unpkg.com/cropperjs@2.1.0/dist/cropper.js',
array(),
null
);*/
/*
* Own scripts
*/
@ -51,6 +37,12 @@ function gallery_app_init($hook) {
array( 'jquery', 'jcrop' )
);
wp_localize_script( 'theatergf-gallery-admin-js', 'wpAPISettings', array(
'root' => esc_url_raw( rest_url() ),
'nonce' => wp_create_nonce( 'wp_rest' )
));
}
add_action('admin_enqueue_scripts', 'TheaterGF\Gallery\Admin\gallery_app_init');

View File

@ -9,13 +9,11 @@ function admin_gallery_html($args) {
<div class="theatergf gallery_app">
<div id="theatergf-editor" class="theatergf image-editor">
<img class="editor-image">
</div>
<div class="editor-button-row">
<button id="theatergf-select-image" class="button button-primary">Select image</button>
<div style="height: 15px"></div>
<div class="theatergf image-edit-wrap">
<canvas id="theatergf-image-editor"></canvas>
<button id="theatergf-edit-save" class="button button-primary">Save</button
</div>

View File

@ -4,25 +4,54 @@ const devicePixelRatio = window.devicePixelRatio || 1;
function clamp(n, min, max) {
return Math.max(Math.min(n, max), min)
}
function round(n, base = 10) {
return Math.round(n / base) * base;
}
function floor(n, base = 10) {
return Math.floor(n / base) * base;
}
function ceil(n, base = 10) {
return Math.ceil(n / base) * base;
}
function getOffsetRect(element) {
return new DOMRect(element.offsetLeft, element.offsetTop, element.offsetWidth, element.offsetHeight);
function getRelativeClientRect(element) {
const element_bounds = element.getBoundingClientRect();
const parents_bounds = element.parentElement.getBoundingClientRect();
return new DOMRect(
element_bounds.x - parents_bounds.x,
element_bounds.y - parents_bounds.y,
element_bounds.width,
element_bounds.height
);
}
class ResizableBox {
constructor(element, bounding, aspectRatio = null) {
this.element = element;
this.bounding = bounding;
constructor(parent, bounding, resolution, aspectRatio = null) {
this.DIRECTIONS = {
nw: { sign: { x: -1, y: -1 }, anchor: 'se' },
ne: { sign: { x: +1, y: -1 }, anchor: 'sw' },
sw: { sign: { x: -1, y: +1 }, anchor: 'ne' },
se: { sign: { x: +1, y: +1 }, anchor: 'nw' }
}
this.bounding = bounding;
this.resolution = resolution;
this.aspectRatio = aspectRatio;
this.mouseStart = {
x: 0,
y: 0
}
this.mouseStart = { x: 0, y: 0 };
this.maxDelta = { x: 0, y: 0 };
this.minDelta = { x: 0, y: 0 };
this.elemStart = new DOMRect()
this.elemStart = new DOMRect();
this.direction = undefined;
this._constructElement(parent);
this.res_scale = {
x: resolution.x / bounding.width,
y: resolution.y / bounding.height
};
this.direction = undefined;
const dragMouseMove = (event) => {
event.preventDefault();
@ -32,146 +61,20 @@ class ResizableBox {
y: event.clientY - this.mouseStart.y
};
const delta = {...offset};
if (this.direction && aspectRatio) {
const handle = this.element.querySelector('.handle')
const handle_size = getOffsetRect(handle)
const { anchor, corner_pos, sign} = (() => {
switch (this.direction) {
case 'nw':
return {
anchor: {
x: this.elemStart.right,
y: this.elemStart.bottom
},
corner_pos: {
x: this.elemStart.left,
y: this.elemStart.top
},
sign: {
x: -1,
y: -1
}
}
case 'ne':
return {
anchor: {
x: this.elemStart.left,
y: this.elemStart.bottom
},
corner_pos: {
x: this.elemStart.right,
y: this.elemStart.top
},
sign: {
x: +1,
y: -1
}
}
case 'sw':
return {
anchor: {
x: this.elemStart.right,
y: this.elemStart.top
},
corner_pos: {
x: this.elemStart.left,
y: this.elemStart.bottom
},
sign: {
x: -1,
y: +1
}
}
case 'se':
return {
anchor: {
x: this.elemStart.left,
y: this.elemStart.top
},
corner_pos: {
x: this.elemStart.right,
y: this.elemStart.bottom
},
sign: {
x: +1,
y: +1
}
}
}
})()
const px = corner_pos.x + offset.x
const py = corner_pos.y + offset.y
const dx = sign.x * (px - anchor.x)
const dy = sign.y * (py - anchor.y)
const sh = dx/this.elemStart.width
const sv = dy/this.elemStart.height
const dv = Math.abs(dx - this.elemStart.width)
const dh = Math.abs(dy - this.elemStart.height)
const s = (dv > dh) ? sv : sh
const sMaxX = (sign.x === +1)
? (this.bounding.right - anchor.x) / this.elemStart.width
: (anchor.x - this.bounding.left) / this.elemStart.width
const sMaxY = (sign.y === +1)
? (this.bounding.bottom - anchor.y) / this.elemStart.height
: (anchor.y - this.bounding.top) / this.elemStart.height
const sMin = Math.max(2*handle_size.width / this.elemStart.width, 2*handle_size.height / this.elemStart.height)
const sClamped = Math.max(Math.min(s, sMaxX, sMaxY), sMin)
delta.x = sign.x * (sClamped * this.elemStart.width - this.elemStart.width)
delta.y = sign.y * (sClamped * this.elemStart.height - this.elemStart.height)
if (this.direction) {
this.resize(offset, this.direction, this.aspectRatio);
} else {
this.move(offset);
}
switch(this.direction) {
case 'nw':
this.element.style.left = this.elemStart.left + delta.x+ "px";
this.element.style.top = this.elemStart.top + delta.y + "px";
this.element.style.width = this.elemStart.width - delta.x+ "px";
this.element.style.height = this.elemStart.height - delta.y + "px";
break;
case 'ne':
this.element.style.top = this.elemStart.top + delta.y + "px";
this.element.style.width = this.elemStart.width + delta.x + "px";
this.element.style.height = this.elemStart.height - delta.y + "px";
break;
case 'sw':
this.element.style.left = this.elemStart.left + delta.x+ "px";
this.element.style.width = this.elemStart.width - delta.x+ "px";
this.element.style.height = this.elemStart.height + delta.y + "px";
break;
case 'se':
this.element.style.width = this.elemStart.width + delta.x+ "px";
this.element.style.height = this.elemStart.height + delta.y + "px";
break;
// moving is the default
default:
this.element.style.left = clamp(this.elemStart.left + offset.x, this.bounding.left, this.bounding.right - this.elemStart.width) + "px";
this.element.style.top = clamp(this.elemStart.top + offset.y, this.bounding.top, this.bounding.bottom - this.elemStart.height) + "px";
}
console.log(this.bounding)
console.log(this.elemStart)
}
const dragMouseUp = (_) => {
this.element.classList.remove("moving");
this.direction = undefined
this.direction = undefined;
document.removeEventListener('mousemove', dragMouseMove);
document.removeEventListener('mouseup', dragMouseUp);
document.removeEventListener('mouseup', dragMouseUp);
};
const dragMouseDown = (event) => {
@ -180,25 +83,142 @@ class ResizableBox {
this.mouseStart.x = event.clientX;
this.mouseStart.y = event.clientY;
this.elemStart = getOffsetRect(this.element)
this.elemStart = getRelativeClientRect(this.element)
this.direction = [...event.target.classList].find(c => ["nw","ne","sw","se"].includes(c));
if (this.direction) {
const handle_size = getRelativeClientRect(this.element.querySelector(".handle"));
this.minDelta.x = -1 * (this.elemStart.width - 2*handle_size.width);
this.minDelta.y = -1 * (this.elemStart.height - 2*handle_size.height);
switch (this.direction) {
case 'nw':
this.maxDelta.x = this.elemStart.left - this.bounding.left;
this.maxDelta.y = this.elemStart.top - this.bounding.top;
break;
case 'ne':
this.maxDelta.x = this.bounding.right - this.elemStart.right;
this.maxDelta.y = this.elemStart.top - this.bounding.top;
break;
case 'sw':
this.maxDelta.x = this.elemStart.left - this.bounding.left;
this.maxDelta.y = this.bounding.bottom - this.elemStart.bottom;
break;
case 'se':
this.maxDelta.x = this.bounding.right - this.elemStart.right;
this.maxDelta.y = this.bounding.bottom - this.elemStart.bottom;
break;
}
}
this.element.classList.add('moving');
document.addEventListener('mousemove', dragMouseMove);
document.addEventListener('mouseup', dragMouseUp);
document.addEventListener('mouseup', dragMouseUp);
};
this.element.addEventListener('mousedown', dragMouseDown);
this.createHandles();
}
createHandles() {
const handles = this.element.querySelectorAll('.handle');
for (const handle of handles) {
this.element.removeChild(handle);
move(delta) {
this.element.style.left = clamp(this.elemStart.left + delta.x, this.bounding.left, this.bounding.right - this.elemStart.width) + "px";
this.element.style.top = clamp(this.elemStart.top + delta.y, this.bounding.top, this.bounding.bottom - this.elemStart.height) + "px";
}
resize(delta, direction, handle_aspect_ratio = true) {
console.log(delta)
const sign = this.DIRECTIONS[direction].sign;
const normalized = {
x: delta.x * sign.x,
y: delta.y * sign.y
};
console.log(normalized)
console.log(this.minDelta)
console.log(this.maxDelta);
if (handle_aspect_ratio) {
//delta = this._applyAspectRatio(delta.x, delta.y, direction)
if (Math.abs(normalized.x) > Math.abs(normalized.y * this.aspectRatio)) {
normalized.x = clamp(normalized.x,
Math.max(this.minDelta.x, this.minDelta.y * this.aspectRatio),
Math.min(this.maxDelta.x, this.maxDelta.y * this.aspectRatio));
normalized.y = normalized.x / this.aspectRatio;
} else {
normalized.y = clamp(normalized.y,
Math.max(this.minDelta.y, this.minDelta.x / this.aspectRatio),
Math.min(this.maxDelta.y, this.maxDelta.x / this.aspectRatio));
normalized.x = normalized.y * this.aspectRatio;
}
} else {
normalized.x = clamp(normalized.x, this.minDelta.x, this.maxDelta.x);
normalized.y = clamp(normalized.y, this.minDelta.y, this.maxDelta.y);
}
delta.x = normalized.x * sign.x;
delta.y = normalized.y * sign.y;
console.log(delta)
switch(this.direction) {
case 'nw':
this.element.style.left = (this.elemStart.left + delta.x) + "px";
this.element.style.top = (this.elemStart.top + delta.y) + "px";
this.element.style.width = (this.elemStart.width - delta.x) + "px";
this.element.style.height = (this.elemStart.height - delta.y) + "px";
break;
case 'ne':
this.element.style.top = (this.elemStart.top + delta.y) + "px";
this.element.style.width = (this.elemStart.width + delta.x) + "px";
this.element.style.height = (this.elemStart.height - delta.y) + "px";
break;
case 'sw':
this.element.style.left = (this.elemStart.left + delta.x) + "px";
this.element.style.width = (this.elemStart.width - delta.x) + "px";
this.element.style.height = (this.elemStart.height + delta.y) + "px";
break;
case 'se':
this.element.style.width = (this.elemStart.width + delta.x) + "px";
this.element.style.height = (this.elemStart.height + delta.y) + "px";
break;
}
}
_constructElement(parent) {
parent.querySelector('.selector')?.remove();
this.element = document.createElement('div');
this.element.classList.add('selector');
if (this.aspectRatio) {
if (this.bounding.width > this.bounding.height * this.aspectRatio) {
this.element.style.width = (this.bounding.height * this.aspectRatio - 2) + "px";
this.element.style.height = (this.bounding.height - 2) + "px";
this.element.style.left = (this.bounding.width - this.bounding.height * this.aspectRatio) / 2 + this.bounding.left + "px";
this.element.style.top = this.bounding.top + "px";
} else {
this.element.style.width = (this.bounding.width - 2) + "px";
this.element.style.height = (this.bounding.width / this.aspectRatio - 2) + "px";
this.element.style.left = this.bounding.left + "px";
this.element.style.top = (this.bounding.height - this.bounding.width / this.aspectRatio) / 2 + this.bounding.top + "px";
}
} else {
this.element.style.left = this.bounding.offsetLeft + "px";
this.element.style.top = this.bounding.offsetTop + "px";
this.element.style.width = this.bounding.clientWidth + "px";
this.element.style.height = this.bounding.clientHeight + "px";
}
const neHandle = document.createElement('div');
@ -217,8 +237,54 @@ class ResizableBox {
this.element.appendChild(nwHandle);
this.element.appendChild(seHandle);
this.element.appendChild(swHandle);
parent.appendChild(this.element);
}
_mapCorner(rect, dir) {
const map = {
nw: { x: rect.left, y: rect.top },
ne: { x: rect.right, y: rect.top },
sw: { x: rect.left, y: rect.bottom },
se: { x: rect.right, y: rect.bottom }
};
return map[dir];
}
_applyAspectRatio(dx, dy, direction) {
const sign = this.DIRECTIONS[direction].sign;
const anchor = this._mapCorner(getRelativeClientRect(this.element), this.DIRECTIONS[direction].anchor);
const handle_size = getRelativeClientRect(this.element.querySelector(".handle"));
const sh = 1 + dx / this.elemStart.width
const sv = 1 + dy / this.elemStart.height
const dv = Math.abs(dx)
const dh = Math.abs(dy)
const s = (dv > dh) ? sv : sh
const sMaxX = (sign.x === +1)
? (this.bounding.right - anchor.x) / this.elemStart.width
: (anchor.x - this.bounding.left) / this.elemStart.width
const sMaxY = (sign.y === +1)
? (this.bounding.bottom - anchor.y) / this.elemStart.height
: (anchor.y - this.bounding.top) / this.elemStart.height
const sMin = Math.max(2*handle_size.width / this.elemStart.width, 2*handle_size.height / this.elemStart.height);
const sClamped = Math.max(Math.min(s, sMaxX, sMaxY), sMin);
return {
x: sign.x * (sClamped * this.elemStart.width - this.elemStart.width),
y: sign.y * (sClamped * this.elemStart.height - this.elemStart.height)
};
}
}
@ -228,56 +294,57 @@ class Editor {
this._editor = editorElement;
this.aspectRatio = aspectRatio;
this._imgElement = undefined
this._imgElement = undefined;
}
loadImage(url) {
this._imgElement = new Image()
this._imgElement.classList.add("editor-image");
this._imgElement = this._editor.querySelector(".editor-image");
this._imgElement.src = url;
this._editor.replaceChildren(this._imgElement);
const that = this;
this._imgElement.addEventListener('load', (_) => {
this._imgElement.style.left = (this._editor.clientWidth - this._imgElement.offsetWidth) / 2 + "px";
this._imgElement.style.top = (this._editor.clientHeight - this._imgElement.offsetHeight) / 2 + "px";
const selector_pos = new DOMRect(0, 0, this._imgElement.offsetWidth, this._imgElement.offsetHeight);
console.log(selector_pos)
if (selector_pos.width > selector_pos.height * that.aspectRatio) {
selector_pos.width = selector_pos.height * that.aspectRatio;
selector_pos.x = this._imgElement.offsetLeft
selector_pos.y = (this._editor.clientHeight - selector_pos.height) / 2
} else {
selector_pos.height = selector_pos.width / that.aspectRatio;
selector_pos.x = (this._editor.clientWidth - selector_pos.width) / 2
selector_pos.y = this._imgElement.offsetTop
}
console.log(selector_pos)
that._createSelectionBox(selector_pos);
this._cutArea = new ResizableBox(this._editor, getRelativeClientRect(this._imgElement), {}, 4/3);
document.querySelector("#theatergf-edit-save").style.visibility = "visible";
});
}
_createSelectionBox(selector_pos) {
const selector = document.createElement('div');
selector.classList.add('selector');
save() {
selector.style.left = selector_pos.left + "px",
selector.style.top = selector_pos.top + "px"
selector.style.width = (selector_pos.width - 1) + "px";
selector.style.height = (selector_pos.height - 1) + "px";
const relative_position = {
x: (this._cutSelector.offsetLeft + 1 - this._imgElement.offsetLeft) / this._imgElement.clientWidth,
y: (this._cutSelector.offsetTop + 1 - this._imgElement.offsetTop) / this._imgElement.clientHeight,
width: (this._cutSelector.clientWidth - 2) / this._imgElement.clientWidth,
height: (this._cutSelector.clientHeight - 2) / this._imgElement.clientHeight
}
this._cutSelector = selector;
this._editor.appendChild(selector);
const mapped_region = {
x: relative_position.x * this._imgElement.naturalWidth,
y: relative_position.y * this._imgElement.naturalHeight,
width: relative_position.width * this._imgElement.naturalWidth,
height: relative_position.height * this._imgElement.naturalHeight
}
this._cutArea = new ResizableBox(selector, getOffsetRect(this._imgElement), selector_pos.width/selector_pos.height);
console.log(mapped_region)
fetch(wpAPISettings.root + 'theatergf/gallery/v1/crop/new', {
method: 'POST',
headers: {
'X-WP-Nonce': wpAPISettings.nonce,
'Content-Type': 'application/json'
},
body: JSON.stringify({
img_id: 53,
x: mapped_region.x,
y: mapped_region.y,
width: mapped_region.width,
height: mapped_region.height
})
}).then((response) => response.json().then((json) => console.log(json))).catch((error) => console.log(error));
}
}
@ -285,7 +352,7 @@ class Editor {
jQuery(document).ready( ($) => {
let wp_file_selector_frame = null;
let editor = new Editor(document.querySelector("#theatergf-editor"), 4/3);
let editor = new Editor(document.querySelector("#theatergf-editor"), 2/1);
$("#theatergf-select-image").on("click", (event) => {
event.preventDefault();
@ -307,10 +374,17 @@ jQuery(document).ready( ($) => {
wp_file_selector_frame.on('select', () => {
const file = wp_file_selector_frame.state().get('selection').first().toJSON();
console.log(file)
editor.loadImage(file.url);
});
wp_file_selector_frame.open();
})
$("#theatergf-edit-save").on("click", (event) => {
event.preventDefault();
editor.save();
})
})

View File

@ -21,7 +21,8 @@
.selector {
position: absolute;
border: blue 1px solid;
border: lightblue 1px solid;
opacity: 50%;
/*top: 50%;
left: 50%;*/
@ -37,7 +38,7 @@
width: 10px;
height: 10px;
border: blue 1px solid;
border: lightblue 1px solid;
position: absolute;
/*transform: translate(-50%, -50%);*/
@ -73,19 +74,16 @@
}
}
.image-edit-wrap {
aspect-ratio: 2 / 1;
.editor-button-row {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 75%;
height: 75%;
background-color: gray;
#theatergf-image-editor {
width: 100%;
}
#theatergf-image-editor-shadow {
display: none;
#theatergf-edit-save {
visibility: hidden;
}
}
}