From e959c468f664b937925d39d0188dd94db546d2cb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 5 Jun 2024 17:18:58 +0100 Subject: [PATCH] Lexical: Made image resize handles functional --- resources/js/wysiwyg/nodes/image.ts | 1 - resources/js/wysiwyg/ui/decorators/image.ts | 101 ++++++++++++++------ resources/sass/_editor.scss | 9 +- 3 files changed, 80 insertions(+), 31 deletions(-) diff --git a/resources/js/wysiwyg/nodes/image.ts b/resources/js/wysiwyg/nodes/image.ts index 289e36c4b..9f017b5fe 100644 --- a/resources/js/wysiwyg/nodes/image.ts +++ b/resources/js/wysiwyg/nodes/image.ts @@ -80,7 +80,6 @@ export class ImageNode extends DecoratorNode { setWidth(width: number): void { const self = this.getWritable(); self.__width = width; - console.log('widrg', width) } getWidth(): number { diff --git a/resources/js/wysiwyg/ui/decorators/image.ts b/resources/js/wysiwyg/ui/decorators/image.ts index fd333fa54..b3ceee65f 100644 --- a/resources/js/wysiwyg/ui/decorators/image.ts +++ b/resources/js/wysiwyg/ui/decorators/image.ts @@ -7,48 +7,65 @@ import {ImageNode} from "../../nodes/image"; export class ImageDecorator extends EditorDecorator { protected dom: HTMLElement|null = null; + protected dragLastMouseUp: number = 0; buildDOM(context: EditorUiContext) { - const handleClasses = ['nw', 'ne', 'se', 'sw']; - const handleEls = handleClasses.map(c => { - return el('div', {class: `editor-image-decorator-handle ${c}`}); - }); - + let handleElems: HTMLElement[] = []; const decorateEl = el('div', { class: 'editor-image-decorator', - }, handleEls); + }, []); + let selected = false; const windowClick = (event: MouseEvent) => { - if (!decorateEl.contains(event.target as Node)) { + if (!decorateEl.contains(event.target as Node) && (Date.now() - this.dragLastMouseUp > 100)) { unselect(); } }; + const mouseDown = (event: MouseEvent) => { + const handle = (event.target as HTMLElement).closest('.editor-image-decorator-handle') as HTMLElement|null; + if (handle) { + // handlingResize = true; + this.startHandlingResize(handle, event, context); + } + }; + const select = () => { + if (selected) { + return; + } + + selected = true; decorateEl.classList.add('selected'); window.addEventListener('click', windowClick); - }; - const unselect = () => { - decorateEl.classList.remove('selected'); - window.removeEventListener('click', windowClick); - }; + const handleClasses = ['nw', 'ne', 'se', 'sw']; + handleElems = handleClasses.map(c => { + return el('div', {class: `editor-image-decorator-handle ${c}`}); + }); + decorateEl.append(...handleElems); + decorateEl.addEventListener('mousedown', mouseDown); - decorateEl.addEventListener('click', (event) => { context.editor.update(() => { const nodeSelection = $createNodeSelection(); nodeSelection.add(this.getNode().getKey()); $setSelection(nodeSelection); }); + }; - select(); - }); - - decorateEl.addEventListener('mousedown', (event: MouseEvent) => { - const handle = (event.target as Element).closest('.editor-image-decorator-handle'); - if (handle) { - this.startHandlingResize(handle, event, context); + const unselect = () => { + selected = false; + // handlingResize = false; + decorateEl.classList.remove('selected'); + window.removeEventListener('click', windowClick); + decorateEl.removeEventListener('mousedown', mouseDown); + for (const el of handleElems) { + el.remove(); } + }; + + decorateEl.addEventListener('click', (event) => { + select(); }); return decorateEl; @@ -63,26 +80,56 @@ export class ImageDecorator extends EditorDecorator { return this.dom; } - startHandlingResize(element: Node, event: MouseEvent, context: EditorUiContext) { + startHandlingResize(element: HTMLElement, event: MouseEvent, context: EditorUiContext) { const startingX = event.screenX; const startingY = event.screenY; + const node = this.getNode() as ImageNode; + let startingWidth = element.clientWidth; + let startingHeight = element.clientHeight; + let startingRatio = startingWidth / startingHeight; + let hasHeight = false; + context.editor.getEditorState().read(() => { + startingWidth = node.getWidth() || startingWidth; + startingHeight = node.getHeight() || startingHeight; + if (node.getHeight()) { + hasHeight = true; + } + startingRatio = startingWidth / startingHeight; + }); + + const flipXChange = element.classList.contains('nw') || element.classList.contains('sw'); + const flipYChange = element.classList.contains('nw') || element.classList.contains('ne'); const mouseMoveListener = (event: MouseEvent) => { - const xChange = event.screenX - startingX; - const yChange = event.screenY - startingY; - console.log({ xChange, yChange }); + let xChange = event.screenX - startingX; + if (flipXChange) { + xChange = 0 - xChange; + } + let yChange = event.screenY - startingY; + if (flipYChange) { + yChange = 0 - yChange; + } + const balancedChange = Math.sqrt(Math.pow(xChange, 2) + Math.pow(yChange, 2)); + const increase = xChange + yChange > 0; + const directedChange = increase ? balancedChange : 0-balancedChange; + const newWidth = Math.max(5, Math.round(startingWidth + directedChange)); + let newHeight = 0; + if (hasHeight) { + newHeight = newWidth * startingRatio; + } context.editor.update(() => { const node = this.getNode() as ImageNode; - node.setWidth(node.getWidth() + xChange); - node.setHeight(node.getHeight() + yChange); + node.setWidth(newWidth); + node.setHeight(newHeight); }); }; const mouseUpListener = (event: MouseEvent) => { window.removeEventListener('mousemove', mouseMoveListener); window.removeEventListener('mouseup', mouseUpListener); - } + this.dragLastMouseUp = Date.now(); + }; window.addEventListener('mousemove', mouseMoveListener); window.addEventListener('mouseup', mouseUpListener); diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss index 94fe2c756..87cc70c9b 100644 --- a/resources/sass/_editor.scss +++ b/resources/sass/_editor.scss @@ -85,20 +85,23 @@ display: inline-flex; } .editor-image-decorator { - display: inline-block; position: absolute; - border: 1px solid var(--editor-color-primary); left: 0; right: 0; width: 100%; height: 100%; + display: inline-block; + &.selected { + border: 1px dashed var(--editor-color-primary); + } } .editor-image-decorator-handle { position: absolute; display: block; width: 10px; height: 10px; - background-color: var(--editor-color-primary); + border: 2px solid var(--editor-color-primary); + background-color: #FFF; user-select: none; &.nw { inset-inline-start: -5px;