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 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(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.maxDelta = { x: 0, y: 0 }; this.minDelta = { x: 0, y: 0 }; this.elemStart = new DOMRect(); this.direction = undefined; this._constructElement(parent); this.res_scale = { x: resolution.x / bounding.width, y: resolution.y / bounding.height }; const dragMouseMove = (event) => { event.preventDefault(); const offset = { x: event.clientX - this.mouseStart.x, y: event.clientY - this.mouseStart.y }; if (this.direction) { this.resize(offset, this.direction, this.aspectRatio); } else { this.move(offset); } } 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 = 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); }; this.element.addEventListener('mousedown', dragMouseDown); } 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'); 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); 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) }; } } class Editor { constructor(editorElement, aspectRatio) { this._editor = editorElement; this.aspectRatio = aspectRatio; this._imgElement = undefined; } loadImage(url) { this._imgElement = this._editor.querySelector(".editor-image"); this._imgElement.src = url; 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"; this._cutArea = new ResizableBox(this._editor, getRelativeClientRect(this._imgElement), {}, 4/3); document.querySelector("#theatergf-edit-save").style.visibility = "visible"; }); } save() { 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 } 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 } 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)); } } jQuery(document).ready( ($) => { let wp_file_selector_frame = null; let editor = new Editor(document.querySelector("#theatergf-editor"), 2/1); $("#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(); console.log(file) editor.loadImage(file.url); }); wp_file_selector_frame.open(); }) $("#theatergf-edit-save").on("click", (event) => { event.preventDefault(); editor.save(); }) })