mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
* lasso without 'real' shape detection * select a single linear el * improve ux * feed segments to worker * simplify path threshold adaptive to zoom * add a tiny threshold for checks * refactor code * lasso tests * fix: ts * do not capture lasso tool * try worker-loader in next config * update config * refactor * lint * feat: show active tool when using "more tools" * keep lasso if selected from toolbar * fix incorrect checks for resetting to selection * shift for additive selection * bound text related fixes * lint * keep alt toggled lasso selection if shift pressed * fix regression * fix 'dead' lassos * lint * use workerpool and polyfill * fix worker bundled with window related code * refactor * add file extension for worker constructor error * another attempt at constructor error * attempt at build issue * attempt with dynamic import * test not importing from math * narrow down imports * Reusing existing workers infrastructure (fallback to the main thread, type-safety) * Points on curve inside the shared chunk * Give up on experimental code splitting * Remove potentially unnecessary optimisation * Removing workers as the complexit is much worse, while perf. does not seem to be much better * fix selecting text containers and containing frames together * render fill directly from animated trail * do not re-render static when setting selected element ids in lasso * remove unnecessary property * tweak trail animation * slice points to remove notch * always start alt-lasso from initial point * revert build & worker changes (unused) * remove `lasso` from `hasStrokeColor` * label change * remove unused props * remove unsafe optimization * snaps --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> Co-authored-by: Marcel Mraz <marcel@excalidraw.com>
147 lines
4.6 KiB
TypeScript
147 lines
4.6 KiB
TypeScript
import React, { useEffect, useRef } from "react";
|
|
|
|
import { isShallowEqual } from "@excalidraw/common";
|
|
|
|
import type {
|
|
NonDeletedExcalidrawElement,
|
|
NonDeletedSceneElementsMap,
|
|
} from "@excalidraw/element/types";
|
|
|
|
import { isRenderThrottlingEnabled } from "../../reactUtils";
|
|
import { renderStaticScene } from "../../renderer/staticScene";
|
|
|
|
import type {
|
|
RenderableElementsMap,
|
|
StaticCanvasRenderConfig,
|
|
} from "../../scene/types";
|
|
import type { AppState, StaticCanvasAppState } from "../../types";
|
|
import type { RoughCanvas } from "roughjs/bin/canvas";
|
|
|
|
type StaticCanvasProps = {
|
|
canvas: HTMLCanvasElement;
|
|
rc: RoughCanvas;
|
|
elementsMap: RenderableElementsMap;
|
|
allElementsMap: NonDeletedSceneElementsMap;
|
|
visibleElements: readonly NonDeletedExcalidrawElement[];
|
|
sceneNonce: 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");
|
|
}
|
|
|
|
const widthString = `${props.appState.width}px`;
|
|
const heightString = `${props.appState.height}px`;
|
|
if (canvas.style.width !== widthString) {
|
|
canvas.style.width = widthString;
|
|
}
|
|
if (canvas.style.height !== heightString) {
|
|
canvas.style.height = heightString;
|
|
}
|
|
|
|
const scaledWidth = props.appState.width * props.scale;
|
|
const scaledHeight = props.appState.height * props.scale;
|
|
// setting width/height resets the canvas even if dimensions not changed,
|
|
// which would cause flicker when we skip frame (due to throttling)
|
|
if (canvas.width !== scaledWidth) {
|
|
canvas.width = scaledWidth;
|
|
}
|
|
if (canvas.height !== scaledHeight) {
|
|
canvas.height = scaledHeight;
|
|
}
|
|
|
|
renderStaticScene(
|
|
{
|
|
canvas,
|
|
rc: props.rc,
|
|
scale: props.scale,
|
|
elementsMap: props.elementsMap,
|
|
allElementsMap: props.allElementsMap,
|
|
visibleElements: props.visibleElements,
|
|
appState: props.appState,
|
|
renderConfig: props.renderConfig,
|
|
},
|
|
isRenderThrottlingEnabled(),
|
|
);
|
|
});
|
|
|
|
return <div className="excalidraw__canvas-wrapper" ref={wrapperRef} />;
|
|
};
|
|
|
|
const getRelevantAppStateProps = (appState: AppState): StaticCanvasAppState => {
|
|
const relevantAppStateProps = {
|
|
zoom: appState.zoom,
|
|
scrollX: appState.scrollX,
|
|
scrollY: appState.scrollY,
|
|
width: appState.width,
|
|
height: appState.height,
|
|
viewModeEnabled: appState.viewModeEnabled,
|
|
openDialog: appState.openDialog,
|
|
hoveredElementIds: appState.hoveredElementIds,
|
|
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,
|
|
gridStep: appState.gridStep,
|
|
frameRendering: appState.frameRendering,
|
|
selectedElementIds: appState.selectedElementIds,
|
|
frameToHighlight: appState.frameToHighlight,
|
|
editingGroupId: appState.editingGroupId,
|
|
currentHoveredFontFamily: appState.currentHoveredFontFamily,
|
|
croppingElementId: appState.croppingElementId,
|
|
};
|
|
|
|
return relevantAppStateProps;
|
|
};
|
|
|
|
const areEqual = (
|
|
prevProps: StaticCanvasProps,
|
|
nextProps: StaticCanvasProps,
|
|
) => {
|
|
if (
|
|
prevProps.sceneNonce !== nextProps.sceneNonce ||
|
|
prevProps.scale !== nextProps.scale ||
|
|
// we need to memoize on elementsMap because they may have renewed
|
|
// even if sceneNonce didn't change (e.g. we filter elements out based
|
|
// on appState)
|
|
prevProps.elementsMap !== nextProps.elementsMap ||
|
|
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),
|
|
) && isShallowEqual(prevProps.renderConfig, nextProps.renderConfig)
|
|
);
|
|
};
|
|
|
|
export default React.memo(StaticCanvas, areEqual);
|