1 import {BaseSelection,} from "lexical";
2 import {MouseDragTracker, MouseDragTrackerDistance} from "./mouse-drag-tracker";
3 import {el} from "../../../utils/dom";
4 import {$isImageNode, ImageNode} from "../../../nodes/image";
5 import {EditorUiContext} from "../core";
8 protected context: EditorUiContext;
9 protected dom: HTMLElement|null = null;
10 protected scrollContainer: HTMLElement;
12 protected mouseTracker: MouseDragTracker|null = null;
13 protected activeSelection: string = '';
15 constructor(context: EditorUiContext) {
16 this.context = context;
17 this.scrollContainer = context.scrollDOM;
19 this.onSelectionChange = this.onSelectionChange.bind(this);
20 context.manager.onSelectionChange(this.onSelectionChange);
23 onSelectionChange(selection: BaseSelection|null) {
24 const nodes = selection?.getNodes() || [];
25 if (this.activeSelection) {
29 if (nodes.length === 1 && $isImageNode(nodes[0])) {
30 const imageNode = nodes[0];
31 const nodeKey = imageNode.getKey();
32 const imageDOM = this.context.editor.getElementByKey(nodeKey);
35 this.showForImage(imageNode, imageDOM);
41 this.context.manager.offSelectionChange(this.onSelectionChange);
45 protected showForImage(node: ImageNode, dom: HTMLElement) {
46 this.dom = this.buildDOM();
48 const ghost = el('img', {src: dom.getAttribute('src'), class: 'editor-image-resizer-ghost'});
49 this.dom.append(ghost);
51 this.context.scrollDOM.append(this.dom);
52 this.updateDOMPosition(dom);
54 this.mouseTracker = this.setupTracker(this.dom, node, dom);
55 this.activeSelection = node.getKey();
58 protected updateDOMPosition(imageDOM: HTMLElement) {
63 const imageBounds = imageDOM.getBoundingClientRect();
64 this.dom.style.left = imageDOM.offsetLeft + 'px';
65 this.dom.style.top = imageDOM.offsetTop + 'px';
66 this.dom.style.width = imageBounds.width + 'px';
67 this.dom.style.height = imageBounds.height + 'px';
70 protected updateDOMSize(width: number, height: number): void {
75 this.dom.style.width = width + 'px';
76 this.dom.style.height = height + 'px';
80 this.mouseTracker?.teardown();
82 this.activeSelection = '';
85 protected buildDOM() {
86 const handleClasses = ['nw', 'ne', 'se', 'sw'];
87 const handleElems = handleClasses.map(c => {
88 return el('div', {class: `editor-image-resizer-handle ${c}`});
92 class: 'editor-image-resizer',
96 setupTracker(container: HTMLElement, node: ImageNode, imageDOM: HTMLElement): MouseDragTracker {
97 let startingWidth: number = 0;
98 let startingHeight: number = 0;
99 let startingRatio: number = 0;
100 let hasHeight = false;
102 let flipXChange: boolean = false;
103 let flipYChange: boolean = false;
105 const calculateSize = (distance: MouseDragTrackerDistance): {width: number, height: number} => {
106 let xChange = distance.x;
108 xChange = 0 - xChange;
110 let yChange = distance.y;
112 yChange = 0 - yChange;
115 const balancedChange = Math.sqrt(Math.pow(Math.abs(xChange), 2) + Math.pow(Math.abs(yChange), 2));
116 const increase = xChange + yChange > 0;
117 const directedChange = increase ? balancedChange : 0-balancedChange;
118 const newWidth = Math.max(5, Math.round(startingWidth + directedChange));
119 const newHeight = newWidth * startingRatio;
121 return {width: newWidth, height: newHeight};
124 return new MouseDragTracker(container, '.editor-image-resizer-handle', {
125 down(event: MouseEvent, handle: HTMLElement) {
126 _this.dom?.classList.add('active');
127 _this.context.editor.getEditorState().read(() => {
128 const imageRect = imageDOM.getBoundingClientRect();
129 startingWidth = node.getWidth() || imageRect.width;
130 startingHeight = node.getHeight() || imageRect.height;
131 if (node.getHeight()) {
134 startingRatio = startingWidth / startingHeight;
137 flipXChange = handle.classList.contains('nw') || handle.classList.contains('sw');
138 flipYChange = handle.classList.contains('nw') || handle.classList.contains('ne');
140 move(event: MouseEvent, handle: HTMLElement, distance: MouseDragTrackerDistance) {
141 const size = calculateSize(distance);
142 _this.updateDOMSize(size.width, size.height);
144 up(event: MouseEvent, handle: HTMLElement, distance: MouseDragTrackerDistance) {
145 const size = calculateSize(distance);
146 _this.context.editor.update(() => {
147 node.setWidth(size.width);
148 node.setHeight(hasHeight ? size.height : 0);
149 _this.context.manager.triggerLayoutUpdate();
150 requestAnimationFrame(() => {
151 _this.updateDOMPosition(imageDOM);
154 _this.dom?.classList.remove('active');
161 export function registerImageResizer(context: EditorUiContext): (() => void) {
162 const resizer = new ImageResizer(context);