760fd7b3a6
* feat: support arrow with text * render arrow -> clear rect-> render text * move bound text when linear elements move * fix centering cursor when linear element rotated * fix y coord when new line added and container has 3 points * update text position when 2nd point moved * support adding label on top of 2nd point when 3 points are present * change linear element editor shortcut to cmd+enter and fix tests * scale bound text points when resizing via bounding box * ohh yeah rotation works :) * fix coords when updating text properties * calculate new position after rotation always from original position * rotate the bound text by same angle as parent * don't rotate text and make sure dimensions and coords are always calculated from original point * hardcoding the text width for now * Move the linear element when bound text hit * Rotation working yaay * consider text element angle when editing * refactor * update x2 coords if needed when text updated * simplify * consider bound text to be part of bounding box when hit * show bounding box correctly when multiple element selected * fix typo * support rotating multiple elements * support multiple element resizing * shift bound text to mid point when odd points * Always render linear element handles inside editor after element rendered so point is visible for bound text * Delete bound text when point attached to it deleted * move bound to mid segement mid point when points are even * shift bound text when points nearby deleted and handle segment deletion * Resize working :) * more resize fixes * don't update cache-its breaking delete points, look for better soln * update mid point cache for bound elements when updated * introduce wrapping when resizing * wrap when resize for 2 pointer linear elements * support adding text for linear elements with more than 3 points * export to svg working :) * clip from nearest enclosing element with non transparent color if present when exporting and fill with correct color in canvas * fix snap * use visible elements * Make export to svg work with Mask :) * remove id * mask canvas linear element area where label is added * decide the position of bound text during render * fix coords when editing * fix multiple resize * update cache when bound text version changes * fix masking when rotated * render text in correct position in preview * remove unnecessary code * fix masking when rotating linear element * fix masking with zoom * fix mask in preview for export * fix offsets in export view * fix coords on svg export * fix mask when element rotated in svg * enable double-click to enter text * fix hint * Position cursor correctly and text dimensiosn when height of element is negative * don't allow 2 pointer linear element with bound text width to go beyond min width * code cleanup * fix freedraw * Add padding * don't show vertical align action for linear element containers * Add specs for getBoundTextElementPosition * more specs * move some utils to linearElementEditor.ts * remove only :p * check absoulte coods in test * Add test to hide vertical align for linear eleemnt with bound text * improve export preview * support labels only for arrows * spec * fix large texts * fix tests * fix zooming * enter line editor with cmd+double click * Allow points to move beyond min width/height for 2 pointer arrow with bound text * fix hint for line editing * attempt to fix arrow getting deselected * fix hint and shortcut * Add padding of 5px when creating bound text and add spec * Wrap bound text when arrow binding containers moved * Add spec * remove * set boundTextElementVersion to null if not present * dont use cache when version mismatch * Add a padding of 5px vertically when creating text * Add box sizing content box * Set bound elements when text element created to fix the padding * fix zooming in editor * fix zoom in export * remove globalCompositeOperation and use clearRect instead of fillRect
172 lines
4.2 KiB
TypeScript
172 lines
4.2 KiB
TypeScript
import {
|
|
ExcalidrawElement,
|
|
PointerType,
|
|
NonDeletedExcalidrawElement,
|
|
} from "./types";
|
|
|
|
import {
|
|
OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
|
|
getTransformHandlesFromCoords,
|
|
getTransformHandles,
|
|
TransformHandleType,
|
|
TransformHandle,
|
|
MaybeTransformHandleType,
|
|
} from "./transformHandles";
|
|
import { AppState, Zoom } from "../types";
|
|
|
|
const isInsideTransformHandle = (
|
|
transformHandle: TransformHandle,
|
|
x: number,
|
|
y: number,
|
|
) =>
|
|
x >= transformHandle[0] &&
|
|
x <= transformHandle[0] + transformHandle[2] &&
|
|
y >= transformHandle[1] &&
|
|
y <= transformHandle[1] + transformHandle[3];
|
|
|
|
export const resizeTest = (
|
|
element: NonDeletedExcalidrawElement,
|
|
appState: AppState,
|
|
x: number,
|
|
y: number,
|
|
zoom: Zoom,
|
|
pointerType: PointerType,
|
|
): MaybeTransformHandleType => {
|
|
if (!appState.selectedElementIds[element.id]) {
|
|
return false;
|
|
}
|
|
|
|
const { rotation: rotationTransformHandle, ...transformHandles } =
|
|
getTransformHandles(element, zoom, pointerType);
|
|
|
|
if (
|
|
rotationTransformHandle &&
|
|
isInsideTransformHandle(rotationTransformHandle, x, y)
|
|
) {
|
|
return "rotation" as TransformHandleType;
|
|
}
|
|
|
|
const filter = Object.keys(transformHandles).filter((key) => {
|
|
const transformHandle =
|
|
transformHandles[key as Exclude<TransformHandleType, "rotation">]!;
|
|
if (!transformHandle) {
|
|
return false;
|
|
}
|
|
return isInsideTransformHandle(transformHandle, x, y);
|
|
});
|
|
|
|
if (filter.length > 0) {
|
|
return filter[0] as TransformHandleType;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
export const getElementWithTransformHandleType = (
|
|
elements: readonly NonDeletedExcalidrawElement[],
|
|
appState: AppState,
|
|
scenePointerX: number,
|
|
scenePointerY: number,
|
|
zoom: Zoom,
|
|
pointerType: PointerType,
|
|
) => {
|
|
return elements.reduce((result, element) => {
|
|
if (result) {
|
|
return result;
|
|
}
|
|
const transformHandleType = resizeTest(
|
|
element,
|
|
appState,
|
|
scenePointerX,
|
|
scenePointerY,
|
|
zoom,
|
|
pointerType,
|
|
);
|
|
return transformHandleType ? { element, transformHandleType } : null;
|
|
}, null as { element: NonDeletedExcalidrawElement; transformHandleType: MaybeTransformHandleType } | null);
|
|
};
|
|
|
|
export const getTransformHandleTypeFromCoords = (
|
|
[x1, y1, x2, y2]: readonly [number, number, number, number],
|
|
scenePointerX: number,
|
|
scenePointerY: number,
|
|
zoom: Zoom,
|
|
pointerType: PointerType,
|
|
): MaybeTransformHandleType => {
|
|
const transformHandles = getTransformHandlesFromCoords(
|
|
[x1, y1, x2, y2, (x1 + x2) / 2, (y1 + y2) / 2],
|
|
0,
|
|
zoom,
|
|
pointerType,
|
|
OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
|
|
);
|
|
|
|
const found = Object.keys(transformHandles).find((key) => {
|
|
const transformHandle =
|
|
transformHandles[key as Exclude<TransformHandleType, "rotation">]!;
|
|
return (
|
|
transformHandle &&
|
|
isInsideTransformHandle(transformHandle, scenePointerX, scenePointerY)
|
|
);
|
|
});
|
|
return (found || false) as MaybeTransformHandleType;
|
|
};
|
|
|
|
const RESIZE_CURSORS = ["ns", "nesw", "ew", "nwse"];
|
|
const rotateResizeCursor = (cursor: string, angle: number) => {
|
|
const index = RESIZE_CURSORS.indexOf(cursor);
|
|
if (index >= 0) {
|
|
const a = Math.round(angle / (Math.PI / 4));
|
|
cursor = RESIZE_CURSORS[(index + a) % RESIZE_CURSORS.length];
|
|
}
|
|
return cursor;
|
|
};
|
|
|
|
/*
|
|
* Returns bi-directional cursor for the element being resized
|
|
*/
|
|
export const getCursorForResizingElement = (resizingElement: {
|
|
element?: ExcalidrawElement;
|
|
transformHandleType: MaybeTransformHandleType;
|
|
}): string => {
|
|
const { element, transformHandleType } = resizingElement;
|
|
const shouldSwapCursors =
|
|
element && Math.sign(element.height) * Math.sign(element.width) === -1;
|
|
let cursor = null;
|
|
|
|
switch (transformHandleType) {
|
|
case "n":
|
|
case "s":
|
|
cursor = "ns";
|
|
break;
|
|
case "w":
|
|
case "e":
|
|
cursor = "ew";
|
|
break;
|
|
case "nw":
|
|
case "se":
|
|
if (shouldSwapCursors) {
|
|
cursor = "nesw";
|
|
} else {
|
|
cursor = "nwse";
|
|
}
|
|
break;
|
|
case "ne":
|
|
case "sw":
|
|
if (shouldSwapCursors) {
|
|
cursor = "nwse";
|
|
} else {
|
|
cursor = "nesw";
|
|
}
|
|
break;
|
|
case "rotation":
|
|
return "grab";
|
|
}
|
|
|
|
if (cursor && element) {
|
|
cursor = rotateResizeCursor(cursor, element.angle);
|
|
}
|
|
|
|
return cursor ? `${cursor}-resize` : "";
|
|
};
|