2026-03-21 13:54:23 +01:00
|
|
|
|
|
|
|
|
const devicePixelRatio = window.devicePixelRatio || 1;
|
|
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
function clamp(n, min, max) {
|
|
|
|
|
return Math.max(Math.min(n, max), min)
|
2026-03-21 13:54:23 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
function getOffsetRect(element) {
|
|
|
|
|
return new DOMRect(element.offsetLeft, element.offsetTop, element.offsetWidth, element.offsetHeight);
|
|
|
|
|
}
|
2026-03-21 13:54:23 +01:00
|
|
|
|
|
|
|
|
class ResizableBox {
|
2026-03-21 20:46:17 +01:00
|
|
|
constructor(element, bounding, aspectRatio = null) {
|
2026-03-21 13:54:23 +01:00
|
|
|
this.element = element;
|
2026-03-21 20:46:17 +01:00
|
|
|
this.bounding = bounding;
|
2026-03-21 13:54:23 +01:00
|
|
|
this.aspectRatio = aspectRatio;
|
|
|
|
|
|
|
|
|
|
this.mouseStart = {
|
|
|
|
|
x: 0,
|
|
|
|
|
y: 0
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
this.elemStart = new DOMRect()
|
2026-03-21 13:54:23 +01:00
|
|
|
|
|
|
|
|
this.direction = undefined;
|
|
|
|
|
|
|
|
|
|
const dragMouseMove = (event) => {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
|
|
const offset = {
|
|
|
|
|
x: event.clientX - this.mouseStart.x,
|
|
|
|
|
y: event.clientY - this.mouseStart.y
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const delta = {...offset};
|
|
|
|
|
|
|
|
|
|
if (this.direction && aspectRatio) {
|
|
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
const handle = this.element.querySelector('.handle')
|
|
|
|
|
const handle_size = getOffsetRect(handle)
|
|
|
|
|
|
|
|
|
|
const { anchor, corner_pos, sign} = (() => {
|
2026-03-21 13:54:23 +01:00
|
|
|
switch (this.direction) {
|
|
|
|
|
case 'nw':
|
|
|
|
|
return {
|
|
|
|
|
anchor: {
|
2026-03-21 20:46:17 +01:00
|
|
|
x: this.elemStart.right,
|
|
|
|
|
y: this.elemStart.bottom
|
|
|
|
|
},
|
|
|
|
|
corner_pos: {
|
|
|
|
|
x: this.elemStart.left,
|
|
|
|
|
y: this.elemStart.top
|
2026-03-21 13:54:23 +01:00
|
|
|
},
|
|
|
|
|
sign: {
|
|
|
|
|
x: -1,
|
|
|
|
|
y: -1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case 'ne':
|
|
|
|
|
return {
|
|
|
|
|
anchor: {
|
|
|
|
|
x: this.elemStart.left,
|
2026-03-21 20:46:17 +01:00
|
|
|
y: this.elemStart.bottom
|
|
|
|
|
},
|
|
|
|
|
corner_pos: {
|
|
|
|
|
x: this.elemStart.right,
|
|
|
|
|
y: this.elemStart.top
|
2026-03-21 13:54:23 +01:00
|
|
|
},
|
|
|
|
|
sign: {
|
|
|
|
|
x: +1,
|
|
|
|
|
y: -1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case 'sw':
|
|
|
|
|
return {
|
|
|
|
|
anchor: {
|
2026-03-21 20:46:17 +01:00
|
|
|
x: this.elemStart.right,
|
2026-03-21 13:54:23 +01:00
|
|
|
y: this.elemStart.top
|
|
|
|
|
},
|
2026-03-21 20:46:17 +01:00
|
|
|
corner_pos: {
|
|
|
|
|
x: this.elemStart.left,
|
|
|
|
|
y: this.elemStart.bottom
|
|
|
|
|
},
|
2026-03-21 13:54:23 +01:00
|
|
|
sign: {
|
|
|
|
|
x: -1,
|
|
|
|
|
y: +1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case 'se':
|
|
|
|
|
return {
|
|
|
|
|
anchor: {
|
|
|
|
|
x: this.elemStart.left,
|
|
|
|
|
y: this.elemStart.top
|
|
|
|
|
},
|
2026-03-21 20:46:17 +01:00
|
|
|
corner_pos: {
|
|
|
|
|
x: this.elemStart.right,
|
|
|
|
|
y: this.elemStart.bottom
|
|
|
|
|
},
|
2026-03-21 13:54:23 +01:00
|
|
|
sign: {
|
|
|
|
|
x: +1,
|
|
|
|
|
y: +1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})()
|
|
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
const px = corner_pos.x + offset.x
|
|
|
|
|
const py = corner_pos.y + offset.y
|
2026-03-21 13:54:23 +01:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
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)
|
2026-03-21 13:54:23 +01:00
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
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)
|
2026-03-21 13:54:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch(this.direction) {
|
|
|
|
|
case 'nw':
|
2026-03-21 20:46:17 +01:00
|
|
|
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";
|
2026-03-21 13:54:23 +01:00
|
|
|
break;
|
|
|
|
|
case 'ne':
|
2026-03-21 20:46:17 +01:00
|
|
|
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";
|
2026-03-21 13:54:23 +01:00
|
|
|
break;
|
|
|
|
|
case 'sw':
|
2026-03-21 20:46:17 +01:00
|
|
|
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";
|
2026-03-21 13:54:23 +01:00
|
|
|
break;
|
|
|
|
|
case 'se':
|
2026-03-21 20:46:17 +01:00
|
|
|
this.element.style.width = this.elemStart.width + delta.x+ "px";
|
|
|
|
|
this.element.style.height = this.elemStart.height + delta.y + "px";
|
2026-03-21 13:54:23 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// moving is the default
|
|
|
|
|
default:
|
2026-03-21 20:46:17 +01:00
|
|
|
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";
|
2026-03-21 13:54:23 +01:00
|
|
|
}
|
2026-03-21 20:46:17 +01:00
|
|
|
|
|
|
|
|
console.log(this.bounding)
|
|
|
|
|
console.log(this.elemStart)
|
2026-03-21 13:54:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dragMouseUp = (_) => {
|
|
|
|
|
this.element.classList.remove("moving");
|
|
|
|
|
this.direction = undefined
|
|
|
|
|
document.removeEventListener('mousemove', dragMouseMove);
|
|
|
|
|
document.removeEventListener('mouseup', dragMouseUp);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const dragMouseDown = (event) => {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
|
|
this.mouseStart.x = event.clientX;
|
|
|
|
|
this.mouseStart.y = event.clientY;
|
|
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
this.elemStart = getOffsetRect(this.element)
|
2026-03-21 13:54:23 +01:00
|
|
|
|
|
|
|
|
this.direction = [...event.target.classList].find(c => ["nw","ne","sw","se"].includes(c));
|
|
|
|
|
|
|
|
|
|
this.element.classList.add('moving');
|
|
|
|
|
|
|
|
|
|
document.addEventListener('mousemove', dragMouseMove);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const neHandle = document.createElement('div');
|
|
|
|
|
neHandle.classList.add('handle', 'ne');
|
|
|
|
|
|
|
|
|
|
const nwHandle = document.createElement('div');
|
|
|
|
|
nwHandle.classList.add('handle', 'nw');
|
|
|
|
|
|
|
|
|
|
const seHandle = document.createElement('div');
|
|
|
|
|
seHandle.classList.add('handle', 'se');
|
|
|
|
|
|
|
|
|
|
const swHandle = document.createElement('div');
|
|
|
|
|
swHandle.classList.add('handle', 'sw');
|
|
|
|
|
|
|
|
|
|
this.element.appendChild(neHandle);
|
|
|
|
|
this.element.appendChild(nwHandle);
|
|
|
|
|
this.element.appendChild(seHandle);
|
|
|
|
|
this.element.appendChild(swHandle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Editor {
|
|
|
|
|
|
|
|
|
|
constructor(editorElement, aspectRatio) {
|
|
|
|
|
this._editor = editorElement;
|
|
|
|
|
this.aspectRatio = aspectRatio;
|
2026-03-21 20:46:17 +01:00
|
|
|
|
|
|
|
|
this._imgElement = undefined
|
2026-03-21 13:54:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadImage(url) {
|
2026-03-21 20:46:17 +01:00
|
|
|
this._imgElement = new Image()
|
|
|
|
|
this._imgElement.classList.add("editor-image");
|
|
|
|
|
this._imgElement.src = url;
|
2026-03-21 13:54:23 +01:00
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
this._editor.replaceChildren(this._imgElement);
|
2026-03-21 13:54:23 +01:00
|
|
|
|
|
|
|
|
const that = this;
|
2026-03-21 20:46:17 +01:00
|
|
|
this._imgElement.addEventListener('load', (_) => {
|
2026-03-21 13:54:23 +01:00
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
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);
|
2026-03-21 13:54:23 +01:00
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
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
|
2026-03-21 13:54:23 +01:00
|
|
|
} else {
|
2026-03-21 20:46:17 +01:00
|
|
|
selector_pos.height = selector_pos.width / that.aspectRatio;
|
|
|
|
|
selector_pos.x = (this._editor.clientWidth - selector_pos.width) / 2
|
|
|
|
|
selector_pos.y = this._imgElement.offsetTop
|
2026-03-21 13:54:23 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
console.log(selector_pos)
|
|
|
|
|
|
|
|
|
|
that._createSelectionBox(selector_pos);
|
2026-03-21 13:54:23 +01:00
|
|
|
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
_createSelectionBox(selector_pos) {
|
2026-03-21 13:54:23 +01:00
|
|
|
const selector = document.createElement('div');
|
|
|
|
|
selector.classList.add('selector');
|
|
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
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";
|
2026-03-21 13:54:23 +01:00
|
|
|
|
|
|
|
|
this._cutSelector = selector;
|
|
|
|
|
this._editor.appendChild(selector);
|
|
|
|
|
|
2026-03-21 20:46:17 +01:00
|
|
|
this._cutArea = new ResizableBox(selector, getOffsetRect(this._imgElement), selector_pos.width/selector_pos.height);
|
2026-03-21 13:54:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
jQuery(document).ready( ($) => {
|
|
|
|
|
let wp_file_selector_frame = null;
|
|
|
|
|
let editor = new Editor(document.querySelector("#theatergf-editor"), 4/3);
|
|
|
|
|
|
|
|
|
|
$("#theatergf-select-image").on("click", (event) => {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
|
|
if (wp_file_selector_frame) {
|
|
|
|
|
wp_file_selector_frame.open();
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wp_file_selector_frame = wp.media({
|
|
|
|
|
title: 'Select Image',
|
|
|
|
|
button: {
|
|
|
|
|
text: 'Use Image'
|
|
|
|
|
},
|
|
|
|
|
multiple: false
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
wp_file_selector_frame.on('select', () => {
|
|
|
|
|
const file = wp_file_selector_frame.state().get('selection').first().toJSON();
|
|
|
|
|
|
|
|
|
|
editor.loadImage(file.url);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
wp_file_selector_frame.open();
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
})
|