From 59936631ecfe1cfd1a987a91f6b550124df35396 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 25 Jun 2024 18:33:29 +0100 Subject: [PATCH] Lexical: Extracted mouse drag tracking to new helper --- resources/js/wysiwyg/ui/decorators/image.ts | 118 ++++++++---------- .../framework/helpers/mouse-drag-tracker.ts | 76 +++++++++++ .../ui/framework/helpers/table-resizer.ts | 21 +++- 3 files changed, 149 insertions(+), 66 deletions(-) create mode 100644 resources/js/wysiwyg/ui/framework/helpers/mouse-drag-tracker.ts diff --git a/resources/js/wysiwyg/ui/decorators/image.ts b/resources/js/wysiwyg/ui/decorators/image.ts index 1692d078d..1e8bfd165 100644 --- a/resources/js/wysiwyg/ui/decorators/image.ts +++ b/resources/js/wysiwyg/ui/decorators/image.ts @@ -3,6 +3,7 @@ import {el} from "../../helpers"; import {$createNodeSelection, $setSelection} from "lexical"; import {EditorUiContext} from "../framework/core"; import {ImageNode} from "../../nodes/image"; +import {MouseDragTracker, MouseDragTrackerDistance} from "../framework/helpers/mouse-drag-tracker"; export class ImageDecorator extends EditorDecorator { @@ -15,6 +16,7 @@ export class ImageDecorator extends EditorDecorator { class: 'editor-image-decorator', }, []); let selected = false; + let tracker: MouseDragTracker|null = null; const windowClick = (event: MouseEvent) => { if (!decorateEl.contains(event.target as Node) && (Date.now() - this.dragLastMouseUp > 100)) { @@ -22,14 +24,6 @@ export class ImageDecorator extends EditorDecorator { } }; - 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; @@ -44,7 +38,7 @@ export class ImageDecorator extends EditorDecorator { return el('div', {class: `editor-image-decorator-handle ${c}`}); }); decorateEl.append(...handleElems); - decorateEl.addEventListener('mousedown', mouseDown); + tracker = this.setupTracker(decorateEl, context); context.editor.update(() => { const nodeSelection = $createNodeSelection(); @@ -55,10 +49,9 @@ export class ImageDecorator extends EditorDecorator { const unselect = () => { selected = false; - // handlingResize = false; decorateEl.classList.remove('selected'); window.removeEventListener('click', windowClick); - decorateEl.removeEventListener('mousedown', mouseDown); + tracker?.teardown(); for (const el of handleElems) { el.remove(); } @@ -80,62 +73,61 @@ export class ImageDecorator extends EditorDecorator { return this.dom; } - 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; + setupTracker(container: HTMLElement, context: EditorUiContext): MouseDragTracker { + let startingWidth: number = 0; + let startingHeight: number = 0; + let startingRatio: number = 0; let hasHeight = false; let firstChange = true; - context.editor.getEditorState().read(() => { - startingWidth = node.getWidth() || startingWidth; - startingHeight = node.getHeight() || startingHeight; - if (node.getHeight()) { - hasHeight = true; + let node: ImageNode = this.getNode() as ImageNode; + let _this = this; + let flipXChange: boolean = false; + let flipYChange: boolean = false; + + return new MouseDragTracker(container, '.editor-image-decorator-handle', { + down(event: MouseEvent, handle: HTMLElement) { + context.editor.getEditorState().read(() => { + startingWidth = node.getWidth() || startingWidth; + startingHeight = node.getHeight() || startingHeight; + if (node.getHeight()) { + hasHeight = true; + } + startingRatio = startingWidth / startingHeight; + }); + + flipXChange = handle.classList.contains('nw') || handle.classList.contains('sw'); + flipYChange = handle.classList.contains('nw') || handle.classList.contains('ne'); + }, + move(event: MouseEvent, handle: HTMLElement, distance: MouseDragTrackerDistance) { + let xChange = distance.x; + if (flipXChange) { + xChange = 0 - xChange; + } + let yChange = distance.y; + if (flipYChange) { + yChange = 0 - yChange; + } + const balancedChange = Math.sqrt(Math.pow(Math.abs(xChange), 2) + Math.pow(Math.abs(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; + } + + const updateOptions = firstChange ? {} : {tag: 'history-merge'}; + context.editor.update(() => { + const node = _this.getNode() as ImageNode; + node.setWidth(newWidth); + node.setHeight(newHeight); + }, updateOptions); + firstChange = false; + }, + up() { + _this.dragLastMouseUp = Date.now(); } - 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) => { - 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(Math.abs(xChange), 2) + Math.pow(Math.abs(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; - } - - const updateOptions = firstChange ? {} : {tag: 'history-merge'}; - context.editor.update(() => { - const node = this.getNode() as ImageNode; - node.setWidth(newWidth); - node.setHeight(newHeight); - }, updateOptions); - firstChange = false; - }; - - const mouseUpListener = (event: MouseEvent) => { - window.removeEventListener('mousemove', mouseMoveListener); - window.removeEventListener('mouseup', mouseUpListener); - this.dragLastMouseUp = Date.now(); - }; - - window.addEventListener('mousemove', mouseMoveListener); - window.addEventListener('mouseup', mouseUpListener); } } \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/framework/helpers/mouse-drag-tracker.ts b/resources/js/wysiwyg/ui/framework/helpers/mouse-drag-tracker.ts new file mode 100644 index 000000000..141f9b20f --- /dev/null +++ b/resources/js/wysiwyg/ui/framework/helpers/mouse-drag-tracker.ts @@ -0,0 +1,76 @@ + +export type MouseDragTrackerDistance = { + x: number; + y: number; +} + +export type MouseDragTrackerOptions = { + down?: (event: MouseEvent, element: HTMLElement) => any; + move?: (event: MouseEvent, element: HTMLElement, distance: MouseDragTrackerDistance) => any; + up?: (event: MouseEvent, element: HTMLElement, distance: MouseDragTrackerDistance) => any; +} + +export class MouseDragTracker { + protected container: HTMLElement; + protected dragTargetSelector: string; + protected options: MouseDragTrackerOptions; + + protected startX: number = 0; + protected startY: number = 0; + protected target: HTMLElement|null = null; + + constructor(container: HTMLElement, dragTargetSelector: string, options: MouseDragTrackerOptions) { + this.container = container; + this.dragTargetSelector = dragTargetSelector; + this.options = options; + + this.onMouseDown = this.onMouseDown.bind(this); + this.onMouseMove = this.onMouseMove.bind(this); + this.onMouseUp = this.onMouseUp.bind(this); + this.container.addEventListener('mousedown', this.onMouseDown); + } + + teardown() { + this.container.removeEventListener('mousedown', this.onMouseDown); + this.container.removeEventListener('mouseup', this.onMouseUp); + this.container.removeEventListener('mousemove', this.onMouseMove); + } + + protected onMouseDown(event: MouseEvent) { + this.target = (event.target as HTMLElement).closest(this.dragTargetSelector); + if (!this.target) { + return; + } + + this.startX = event.screenX; + this.startY = event.screenY; + + window.addEventListener('mousemove', this.onMouseMove); + window.addEventListener('mouseup', this.onMouseUp); + if (this.options.down) { + this.options.down(event, this.target); + } + } + + protected onMouseMove(event: MouseEvent) { + if (this.options.move && this.target) { + this.options.move(event, this.target, { + x: event.screenX - this.startX, + y: event.screenY - this.startY, + }); + } + } + + protected onMouseUp(event: MouseEvent) { + window.removeEventListener('mousemove', this.onMouseMove); + window.removeEventListener('mouseup', this.onMouseUp); + + if (this.options.up && this.target) { + this.options.up(event, this.target, { + x: event.screenX - this.startX, + y: event.screenY - this.startY, + }); + } + } + +} \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts b/resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts index 53017e93b..ccf269daa 100644 --- a/resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts +++ b/resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts @@ -1,5 +1,6 @@ import {LexicalEditor} from "lexical"; import {el} from "../../../helpers"; +import {MouseDragTracker, MouseDragTrackerDistance} from "./mouse-drag-tracker"; type MarkerDomRecord = {x: HTMLElement, y: HTMLElement}; @@ -7,6 +8,7 @@ class TableResizer { protected editor: LexicalEditor; protected editArea: HTMLElement; protected markerDom: MarkerDomRecord|null = null; + protected mouseTracker: MouseDragTracker|null = null; constructor(editor: LexicalEditor, editArea: HTMLElement) { this.editor = editor; @@ -49,14 +51,27 @@ class TableResizer { getMarkers(): MarkerDomRecord { if (!this.markerDom) { this.markerDom = { - x: el('div', {class: 'editor-table-marker-column'}), - y: el('div', {class: 'editor-table-marker-row'}), + x: el('div', {class: 'editor-table-marker editor-table-marker-column'}), + y: el('div', {class: 'editor-table-marker editor-table-marker-row'}), } - this.editArea.after(this.markerDom.x, this.markerDom.y); + const wrapper = el('div', { + class: 'editor-table-marker-wrap', + }, [this.markerDom.x, this.markerDom.y]); + this.editArea.after(wrapper); + this.watchMarkerMouseDrags(wrapper); } return this.markerDom; } + + watchMarkerMouseDrags(wrapper: HTMLElement) { + this.mouseTracker = new MouseDragTracker(wrapper, '.editor-table-marker', { + up(event: MouseEvent, marker: HTMLElement, distance: MouseDragTrackerDistance) { + console.log('up', distance, marker); + // TODO - Update row/column for distance + } + }); + } }