const devicePixelRatio = window.devicePixelRatio || 1; function clamp(n, min, max) { return Math.max(Math.min(n, max), min) } function getOffsetRect(element) { return new DOMRect(element.offsetLeft, element.offsetTop, element.offsetWidth, element.offsetHeight); } class ResizableBox { constructor(element, bounding, aspectRatio = null) { this.element = element; this.bounding = bounding; this.aspectRatio = aspectRatio; this.mouseStart = { x: 0, y: 0 } this.elemStart = new DOMRect() 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) { 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) } 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 document.removeEventListener('mousemove', dragMouseMove); document.removeEventListener('mouseup', dragMouseUp); }; const dragMouseDown = (event) => { event.preventDefault(); this.mouseStart.x = event.clientX; this.mouseStart.y = event.clientY; this.elemStart = getOffsetRect(this.element) 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; this._imgElement = undefined } loadImage(url) { this._imgElement = new Image() this._imgElement.classList.add("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); }); } _createSelectionBox(selector_pos) { const selector = document.createElement('div'); selector.classList.add('selector'); 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"; this._cutSelector = selector; this._editor.appendChild(selector); this._cutArea = new ResizableBox(selector, getOffsetRect(this._imgElement), selector_pos.width/selector_pos.height); } } 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(); }) })