feat: partition main canvas vertically (#6759)
Co-authored-by: Marcel Mraz <marcel.mraz@adacta-fintech.com> Co-authored-by: dwelle <luzar.david@gmail.com>pull/6877/head
parent
3ea07076ad
commit
a376bd9495
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,222 @@
|
|||||||
|
import React, { useEffect, useRef } from "react";
|
||||||
|
import { renderInteractiveScene } from "../../renderer/renderScene";
|
||||||
|
import {
|
||||||
|
isRenderThrottlingEnabled,
|
||||||
|
isShallowEqual,
|
||||||
|
sceneCoordsToViewportCoords,
|
||||||
|
} from "../../utils";
|
||||||
|
import { CURSOR_TYPE } from "../../constants";
|
||||||
|
import { t } from "../../i18n";
|
||||||
|
import type { DOMAttributes } from "react";
|
||||||
|
import type { AppState, InteractiveCanvasAppState } from "../../types";
|
||||||
|
import type {
|
||||||
|
InteractiveCanvasRenderConfig,
|
||||||
|
RenderInteractiveSceneCallback,
|
||||||
|
} from "../../scene/types";
|
||||||
|
import type { NonDeletedExcalidrawElement } from "../../element/types";
|
||||||
|
|
||||||
|
type InteractiveCanvasProps = {
|
||||||
|
canvas: HTMLCanvasElement | null;
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
|
visibleElements: readonly NonDeletedExcalidrawElement[];
|
||||||
|
selectedElements: readonly NonDeletedExcalidrawElement[];
|
||||||
|
versionNonce: number | undefined;
|
||||||
|
selectionNonce: number | undefined;
|
||||||
|
scale: number;
|
||||||
|
appState: InteractiveCanvasAppState;
|
||||||
|
renderInteractiveSceneCallback: (
|
||||||
|
data: RenderInteractiveSceneCallback,
|
||||||
|
) => void;
|
||||||
|
handleCanvasRef: (canvas: HTMLCanvasElement | null) => void;
|
||||||
|
onContextMenu: Exclude<
|
||||||
|
DOMAttributes<HTMLCanvasElement | HTMLDivElement>["onContextMenu"],
|
||||||
|
undefined
|
||||||
|
>;
|
||||||
|
onPointerMove: Exclude<
|
||||||
|
DOMAttributes<HTMLCanvasElement>["onPointerMove"],
|
||||||
|
undefined
|
||||||
|
>;
|
||||||
|
onPointerUp: Exclude<
|
||||||
|
DOMAttributes<HTMLCanvasElement>["onPointerUp"],
|
||||||
|
undefined
|
||||||
|
>;
|
||||||
|
onPointerCancel: Exclude<
|
||||||
|
DOMAttributes<HTMLCanvasElement>["onPointerCancel"],
|
||||||
|
undefined
|
||||||
|
>;
|
||||||
|
onTouchMove: Exclude<
|
||||||
|
DOMAttributes<HTMLCanvasElement>["onTouchMove"],
|
||||||
|
undefined
|
||||||
|
>;
|
||||||
|
onPointerDown: Exclude<
|
||||||
|
DOMAttributes<HTMLCanvasElement>["onPointerDown"],
|
||||||
|
undefined
|
||||||
|
>;
|
||||||
|
onDoubleClick: Exclude<
|
||||||
|
DOMAttributes<HTMLCanvasElement>["onDoubleClick"],
|
||||||
|
undefined
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const InteractiveCanvas = (props: InteractiveCanvasProps) => {
|
||||||
|
const isComponentMounted = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isComponentMounted.current) {
|
||||||
|
isComponentMounted.current = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cursorButton: {
|
||||||
|
[id: string]: string | undefined;
|
||||||
|
} = {};
|
||||||
|
const pointerViewportCoords: InteractiveCanvasRenderConfig["remotePointerViewportCoords"] =
|
||||||
|
{};
|
||||||
|
const remoteSelectedElementIds: InteractiveCanvasRenderConfig["remoteSelectedElementIds"] =
|
||||||
|
{};
|
||||||
|
const pointerUsernames: { [id: string]: string } = {};
|
||||||
|
const pointerUserStates: { [id: string]: string } = {};
|
||||||
|
|
||||||
|
props.appState.collaborators.forEach((user, socketId) => {
|
||||||
|
if (user.selectedElementIds) {
|
||||||
|
for (const id of Object.keys(user.selectedElementIds)) {
|
||||||
|
if (!(id in remoteSelectedElementIds)) {
|
||||||
|
remoteSelectedElementIds[id] = [];
|
||||||
|
}
|
||||||
|
remoteSelectedElementIds[id].push(socketId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!user.pointer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (user.username) {
|
||||||
|
pointerUsernames[socketId] = user.username;
|
||||||
|
}
|
||||||
|
if (user.userState) {
|
||||||
|
pointerUserStates[socketId] = user.userState;
|
||||||
|
}
|
||||||
|
pointerViewportCoords[socketId] = sceneCoordsToViewportCoords(
|
||||||
|
{
|
||||||
|
sceneX: user.pointer.x,
|
||||||
|
sceneY: user.pointer.y,
|
||||||
|
},
|
||||||
|
props.appState,
|
||||||
|
);
|
||||||
|
cursorButton[socketId] = user.button;
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectionColor = getComputedStyle(
|
||||||
|
document.querySelector(".excalidraw")!,
|
||||||
|
).getPropertyValue("--color-selection");
|
||||||
|
|
||||||
|
renderInteractiveScene(
|
||||||
|
{
|
||||||
|
canvas: props.canvas,
|
||||||
|
elements: props.elements,
|
||||||
|
visibleElements: props.visibleElements,
|
||||||
|
selectedElements: props.selectedElements,
|
||||||
|
scale: window.devicePixelRatio,
|
||||||
|
appState: props.appState,
|
||||||
|
renderConfig: {
|
||||||
|
remotePointerViewportCoords: pointerViewportCoords,
|
||||||
|
remotePointerButton: cursorButton,
|
||||||
|
remoteSelectedElementIds,
|
||||||
|
remotePointerUsernames: pointerUsernames,
|
||||||
|
remotePointerUserStates: pointerUserStates,
|
||||||
|
selectionColor,
|
||||||
|
renderScrollbars: false,
|
||||||
|
},
|
||||||
|
callback: props.renderInteractiveSceneCallback,
|
||||||
|
},
|
||||||
|
isRenderThrottlingEnabled(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<canvas
|
||||||
|
className="excalidraw__canvas interactive"
|
||||||
|
style={{
|
||||||
|
width: props.appState.width,
|
||||||
|
height: props.appState.height,
|
||||||
|
cursor: props.appState.viewModeEnabled
|
||||||
|
? CURSOR_TYPE.GRAB
|
||||||
|
: CURSOR_TYPE.AUTO,
|
||||||
|
}}
|
||||||
|
width={props.appState.width * props.scale}
|
||||||
|
height={props.appState.height * props.scale}
|
||||||
|
ref={props.handleCanvasRef}
|
||||||
|
onContextMenu={props.onContextMenu}
|
||||||
|
onPointerMove={props.onPointerMove}
|
||||||
|
onPointerUp={props.onPointerUp}
|
||||||
|
onPointerCancel={props.onPointerCancel}
|
||||||
|
onTouchMove={props.onTouchMove}
|
||||||
|
onPointerDown={props.onPointerDown}
|
||||||
|
onDoubleClick={
|
||||||
|
props.appState.viewModeEnabled ? undefined : props.onDoubleClick
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("labels.drawingCanvas")}
|
||||||
|
</canvas>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRelevantAppStateProps = (
|
||||||
|
appState: AppState,
|
||||||
|
): Omit<InteractiveCanvasAppState, "editingElement"> => ({
|
||||||
|
zoom: appState.zoom,
|
||||||
|
scrollX: appState.scrollX,
|
||||||
|
scrollY: appState.scrollY,
|
||||||
|
width: appState.width,
|
||||||
|
height: appState.height,
|
||||||
|
viewModeEnabled: appState.viewModeEnabled,
|
||||||
|
editingGroupId: appState.editingGroupId,
|
||||||
|
editingLinearElement: appState.editingLinearElement,
|
||||||
|
selectedElementIds: appState.selectedElementIds,
|
||||||
|
frameToHighlight: appState.frameToHighlight,
|
||||||
|
offsetLeft: appState.offsetLeft,
|
||||||
|
offsetTop: appState.offsetTop,
|
||||||
|
theme: appState.theme,
|
||||||
|
pendingImageElementId: appState.pendingImageElementId,
|
||||||
|
selectionElement: appState.selectionElement,
|
||||||
|
selectedGroupIds: appState.selectedGroupIds,
|
||||||
|
selectedLinearElement: appState.selectedLinearElement,
|
||||||
|
multiElement: appState.multiElement,
|
||||||
|
isBindingEnabled: appState.isBindingEnabled,
|
||||||
|
suggestedBindings: appState.suggestedBindings,
|
||||||
|
isRotating: appState.isRotating,
|
||||||
|
elementsToHighlight: appState.elementsToHighlight,
|
||||||
|
openSidebar: appState.openSidebar,
|
||||||
|
showHyperlinkPopup: appState.showHyperlinkPopup,
|
||||||
|
collaborators: appState.collaborators, // Necessary for collab. sessions
|
||||||
|
activeEmbeddable: appState.activeEmbeddable,
|
||||||
|
});
|
||||||
|
|
||||||
|
const areEqual = (
|
||||||
|
prevProps: InteractiveCanvasProps,
|
||||||
|
nextProps: InteractiveCanvasProps,
|
||||||
|
) => {
|
||||||
|
// This could be further optimised if needed, as we don't have to render interactive canvas on each scene mutation
|
||||||
|
if (
|
||||||
|
prevProps.selectionNonce !== nextProps.selectionNonce ||
|
||||||
|
prevProps.versionNonce !== nextProps.versionNonce ||
|
||||||
|
prevProps.scale !== nextProps.scale ||
|
||||||
|
// we need to memoize on element arrays because they may have renewed
|
||||||
|
// even if versionNonce didn't change (e.g. we filter elements out based
|
||||||
|
// on appState)
|
||||||
|
prevProps.elements !== nextProps.elements ||
|
||||||
|
prevProps.visibleElements !== nextProps.visibleElements ||
|
||||||
|
prevProps.selectedElements !== nextProps.selectedElements
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparing the interactive appState for changes in case of some edge cases
|
||||||
|
return isShallowEqual(
|
||||||
|
// asserting AppState because we're being passed the whole AppState
|
||||||
|
// but resolve to only the InteractiveCanvas-relevant props
|
||||||
|
getRelevantAppStateProps(prevProps.appState as AppState),
|
||||||
|
getRelevantAppStateProps(nextProps.appState as AppState),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(InteractiveCanvas, areEqual);
|
@ -0,0 +1,113 @@
|
|||||||
|
import React, { useEffect, useRef } from "react";
|
||||||
|
import { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
|
import { renderStaticScene } from "../../renderer/renderScene";
|
||||||
|
import { isRenderThrottlingEnabled, isShallowEqual } from "../../utils";
|
||||||
|
import type { AppState, StaticCanvasAppState } from "../../types";
|
||||||
|
import type { StaticCanvasRenderConfig } from "../../scene/types";
|
||||||
|
import type { NonDeletedExcalidrawElement } from "../../element/types";
|
||||||
|
|
||||||
|
type StaticCanvasProps = {
|
||||||
|
canvas: HTMLCanvasElement;
|
||||||
|
rc: RoughCanvas;
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
|
visibleElements: readonly NonDeletedExcalidrawElement[];
|
||||||
|
versionNonce: number | undefined;
|
||||||
|
selectionNonce: number | undefined;
|
||||||
|
scale: number;
|
||||||
|
appState: StaticCanvasAppState;
|
||||||
|
renderConfig: StaticCanvasRenderConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StaticCanvas = (props: StaticCanvasProps) => {
|
||||||
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
const isComponentMounted = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const wrapper = wrapperRef.current;
|
||||||
|
if (!wrapper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = props.canvas;
|
||||||
|
|
||||||
|
if (!isComponentMounted.current) {
|
||||||
|
isComponentMounted.current = true;
|
||||||
|
|
||||||
|
wrapper.replaceChildren(canvas);
|
||||||
|
canvas.classList.add("excalidraw__canvas", "static");
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.style.width = `${props.appState.width}px`;
|
||||||
|
canvas.style.height = `${props.appState.height}px`;
|
||||||
|
canvas.width = props.appState.width * props.scale;
|
||||||
|
canvas.height = props.appState.height * props.scale;
|
||||||
|
|
||||||
|
renderStaticScene(
|
||||||
|
{
|
||||||
|
canvas,
|
||||||
|
rc: props.rc,
|
||||||
|
scale: props.scale,
|
||||||
|
elements: props.elements,
|
||||||
|
visibleElements: props.visibleElements,
|
||||||
|
appState: props.appState,
|
||||||
|
renderConfig: props.renderConfig,
|
||||||
|
},
|
||||||
|
isRenderThrottlingEnabled(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div className="excalidraw__canvas-wrapper" ref={wrapperRef} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRelevantAppStateProps = (
|
||||||
|
appState: AppState,
|
||||||
|
): Omit<
|
||||||
|
StaticCanvasAppState,
|
||||||
|
| "editingElement"
|
||||||
|
| "selectedElementIds"
|
||||||
|
| "editingGroupId"
|
||||||
|
| "frameToHighlight"
|
||||||
|
> => ({
|
||||||
|
zoom: appState.zoom,
|
||||||
|
scrollX: appState.scrollX,
|
||||||
|
scrollY: appState.scrollY,
|
||||||
|
width: appState.width,
|
||||||
|
height: appState.height,
|
||||||
|
viewModeEnabled: appState.viewModeEnabled,
|
||||||
|
offsetLeft: appState.offsetLeft,
|
||||||
|
offsetTop: appState.offsetTop,
|
||||||
|
theme: appState.theme,
|
||||||
|
pendingImageElementId: appState.pendingImageElementId,
|
||||||
|
shouldCacheIgnoreZoom: appState.shouldCacheIgnoreZoom,
|
||||||
|
viewBackgroundColor: appState.viewBackgroundColor,
|
||||||
|
exportScale: appState.exportScale,
|
||||||
|
selectedElementsAreBeingDragged: appState.selectedElementsAreBeingDragged,
|
||||||
|
gridSize: appState.gridSize,
|
||||||
|
frameRendering: appState.frameRendering,
|
||||||
|
});
|
||||||
|
|
||||||
|
const areEqual = (
|
||||||
|
prevProps: StaticCanvasProps,
|
||||||
|
nextProps: StaticCanvasProps,
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
prevProps.versionNonce !== nextProps.versionNonce ||
|
||||||
|
prevProps.scale !== nextProps.scale ||
|
||||||
|
// we need to memoize on element arrays because they may have renewed
|
||||||
|
// even if versionNonce didn't change (e.g. we filter elements out based
|
||||||
|
// on appState)
|
||||||
|
prevProps.elements !== nextProps.elements ||
|
||||||
|
prevProps.visibleElements !== nextProps.visibleElements
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isShallowEqual(
|
||||||
|
// asserting AppState because we're being passed the whole AppState
|
||||||
|
// but resolve to only the StaticCanvas-relevant props
|
||||||
|
getRelevantAppStateProps(prevProps.appState as AppState),
|
||||||
|
getRelevantAppStateProps(nextProps.appState as AppState),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(StaticCanvas, areEqual);
|
@ -0,0 +1,4 @@
|
|||||||
|
import InteractiveCanvas from "./InteractiveCanvas";
|
||||||
|
import StaticCanvas from "./StaticCanvas";
|
||||||
|
|
||||||
|
export { InteractiveCanvas, StaticCanvas };
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,131 @@
|
|||||||
|
import { isElementInViewport } from "../element/sizeHelpers";
|
||||||
|
import { isImageElement } from "../element/typeChecks";
|
||||||
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
|
import { cancelRender } from "../renderer/renderScene";
|
||||||
|
import { AppState } from "../types";
|
||||||
|
import { memoize } from "../utils";
|
||||||
|
import Scene from "./Scene";
|
||||||
|
|
||||||
|
export class Renderer {
|
||||||
|
private scene: Scene;
|
||||||
|
|
||||||
|
constructor(scene: Scene) {
|
||||||
|
this.scene = scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRenderableElements = (() => {
|
||||||
|
const getVisibleCanvasElements = ({
|
||||||
|
elements,
|
||||||
|
zoom,
|
||||||
|
offsetLeft,
|
||||||
|
offsetTop,
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
}: {
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
|
zoom: AppState["zoom"];
|
||||||
|
offsetLeft: AppState["offsetLeft"];
|
||||||
|
offsetTop: AppState["offsetTop"];
|
||||||
|
scrollX: AppState["scrollX"];
|
||||||
|
scrollY: AppState["scrollY"];
|
||||||
|
height: AppState["height"];
|
||||||
|
width: AppState["width"];
|
||||||
|
}): readonly NonDeletedExcalidrawElement[] => {
|
||||||
|
return elements.filter((element) =>
|
||||||
|
isElementInViewport(element, width, height, {
|
||||||
|
zoom,
|
||||||
|
offsetLeft,
|
||||||
|
offsetTop,
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCanvasElements = ({
|
||||||
|
editingElement,
|
||||||
|
elements,
|
||||||
|
pendingImageElementId,
|
||||||
|
}: {
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
|
editingElement: AppState["editingElement"];
|
||||||
|
pendingImageElementId: AppState["pendingImageElementId"];
|
||||||
|
}) => {
|
||||||
|
return elements.filter((element) => {
|
||||||
|
if (isImageElement(element)) {
|
||||||
|
if (
|
||||||
|
// => not placed on canvas yet (but in elements array)
|
||||||
|
pendingImageElementId === element.id
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we don't want to render text element that's being currently edited
|
||||||
|
// (it's rendered on remote only)
|
||||||
|
return (
|
||||||
|
!editingElement ||
|
||||||
|
editingElement.type !== "text" ||
|
||||||
|
element.id !== editingElement.id
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return memoize(
|
||||||
|
({
|
||||||
|
zoom,
|
||||||
|
offsetLeft,
|
||||||
|
offsetTop,
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
editingElement,
|
||||||
|
pendingImageElementId,
|
||||||
|
// unused but serves we cache on it to invalidate elements if they
|
||||||
|
// get mutated
|
||||||
|
versionNonce: _versionNonce,
|
||||||
|
}: {
|
||||||
|
zoom: AppState["zoom"];
|
||||||
|
offsetLeft: AppState["offsetLeft"];
|
||||||
|
offsetTop: AppState["offsetTop"];
|
||||||
|
scrollX: AppState["scrollX"];
|
||||||
|
scrollY: AppState["scrollY"];
|
||||||
|
height: AppState["height"];
|
||||||
|
width: AppState["width"];
|
||||||
|
editingElement: AppState["editingElement"];
|
||||||
|
pendingImageElementId: AppState["pendingImageElementId"];
|
||||||
|
versionNonce: ReturnType<InstanceType<typeof Scene>["getVersionNonce"]>;
|
||||||
|
}) => {
|
||||||
|
const elements = this.scene.getNonDeletedElements();
|
||||||
|
|
||||||
|
const canvasElements = getCanvasElements({
|
||||||
|
elements,
|
||||||
|
editingElement,
|
||||||
|
pendingImageElementId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const visibleElements = getVisibleCanvasElements({
|
||||||
|
elements: canvasElements,
|
||||||
|
zoom,
|
||||||
|
offsetLeft,
|
||||||
|
offsetTop,
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { canvasElements, visibleElements };
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
// NOTE Doesn't destroy everything (scene, rc, etc.) because it may not be
|
||||||
|
// safe to break TS contract here (for upstream cases)
|
||||||
|
public destroy() {
|
||||||
|
cancelRender();
|
||||||
|
this.getRenderableElements.clear();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
import { Drawable } from "roughjs/bin/core";
|
||||||
|
import { RoughGenerator } from "roughjs/bin/generator";
|
||||||
|
import { ExcalidrawElement } from "../element/types";
|
||||||
|
import { generateElementShape } from "../renderer/renderElement";
|
||||||
|
|
||||||
|
type ElementShape = Drawable | Drawable[] | null;
|
||||||
|
|
||||||
|
type ElementShapes = {
|
||||||
|
freedraw: Drawable | null;
|
||||||
|
arrow: Drawable[];
|
||||||
|
line: Drawable[];
|
||||||
|
text: null;
|
||||||
|
image: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ShapeCache {
|
||||||
|
private static rg = new RoughGenerator();
|
||||||
|
private static cache = new WeakMap<ExcalidrawElement, ElementShape>();
|
||||||
|
|
||||||
|
public static get = <T extends ExcalidrawElement>(element: T) => {
|
||||||
|
return ShapeCache.cache.get(
|
||||||
|
element,
|
||||||
|
) as T["type"] extends keyof ElementShapes
|
||||||
|
? ElementShapes[T["type"]] | undefined
|
||||||
|
: Drawable | null | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
public static set = <T extends ExcalidrawElement>(
|
||||||
|
element: T,
|
||||||
|
shape: T["type"] extends keyof ElementShapes
|
||||||
|
? ElementShapes[T["type"]]
|
||||||
|
: Drawable,
|
||||||
|
) => ShapeCache.cache.set(element, shape);
|
||||||
|
|
||||||
|
public static delete = (element: ExcalidrawElement) =>
|
||||||
|
ShapeCache.cache.delete(element);
|
||||||
|
|
||||||
|
public static destroy = () => {
|
||||||
|
ShapeCache.cache = new WeakMap();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates & caches shape for element if not already cached, otherwise
|
||||||
|
* return cached shape.
|
||||||
|
*/
|
||||||
|
public static generateElementShape = <T extends ExcalidrawElement>(
|
||||||
|
element: T,
|
||||||
|
) => {
|
||||||
|
const shape = generateElementShape(
|
||||||
|
element,
|
||||||
|
ShapeCache.rg,
|
||||||
|
/* so it prefers cache */ false,
|
||||||
|
) as T["type"] extends keyof ElementShapes
|
||||||
|
? ElementShapes[T["type"]]
|
||||||
|
: Drawable | null;
|
||||||
|
|
||||||
|
ShapeCache.cache.set(element, shape);
|
||||||
|
|
||||||
|
return shape;
|
||||||
|
};
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue