mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-04-14 16:40:58 -04:00
fix: remove scene hack from export.ts & remove pass elementsMap to getContainingFrame (#7713)
* fix: remove scene hack from export.ts as its not needed anymore * remove * pass elementsMap to getContainingFrame * remove unused `mapElementIds` param --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
parent
2e719ff671
commit
361a9449bb
7 changed files with 38 additions and 92 deletions
|
@ -16,7 +16,7 @@ import {
|
||||||
import { deepCopyElement } from "./element/newElement";
|
import { deepCopyElement } from "./element/newElement";
|
||||||
import { mutateElement } from "./element/mutateElement";
|
import { mutateElement } from "./element/mutateElement";
|
||||||
import { getContainingFrame } from "./frame";
|
import { getContainingFrame } from "./frame";
|
||||||
import { isMemberOf, isPromiseLike } from "./utils";
|
import { arrayToMap, isMemberOf, isPromiseLike } from "./utils";
|
||||||
import { t } from "./i18n";
|
import { t } from "./i18n";
|
||||||
|
|
||||||
type ElementsClipboard = {
|
type ElementsClipboard = {
|
||||||
|
@ -126,6 +126,7 @@ export const serializeAsClipboardJSON = ({
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
files: BinaryFiles | null;
|
files: BinaryFiles | null;
|
||||||
}) => {
|
}) => {
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
const framesToCopy = new Set(
|
const framesToCopy = new Set(
|
||||||
elements.filter((element) => isFrameLikeElement(element)),
|
elements.filter((element) => isFrameLikeElement(element)),
|
||||||
);
|
);
|
||||||
|
@ -152,8 +153,8 @@ export const serializeAsClipboardJSON = ({
|
||||||
type: EXPORT_DATA_TYPES.excalidrawClipboard,
|
type: EXPORT_DATA_TYPES.excalidrawClipboard,
|
||||||
elements: elements.map((element) => {
|
elements: elements.map((element) => {
|
||||||
if (
|
if (
|
||||||
getContainingFrame(element) &&
|
getContainingFrame(element, elementsMap) &&
|
||||||
!framesToCopy.has(getContainingFrame(element)!)
|
!framesToCopy.has(getContainingFrame(element, elementsMap)!)
|
||||||
) {
|
) {
|
||||||
const copiedElement = deepCopyElement(element);
|
const copiedElement = deepCopyElement(element);
|
||||||
mutateElement(copiedElement, {
|
mutateElement(copiedElement, {
|
||||||
|
|
|
@ -1131,7 +1131,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
display: isVisible ? "block" : "none",
|
display: isVisible ? "block" : "none",
|
||||||
opacity: getRenderOpacity(
|
opacity: getRenderOpacity(
|
||||||
el,
|
el,
|
||||||
getContainingFrame(el),
|
getContainingFrame(el, this.scene.getNonDeletedElementsMap()),
|
||||||
this.elementsPendingErasure,
|
this.elementsPendingErasure,
|
||||||
),
|
),
|
||||||
["--embeddable-radius" as string]: `${getCornerRadius(
|
["--embeddable-radius" as string]: `${getCornerRadius(
|
||||||
|
@ -4399,7 +4399,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
),
|
),
|
||||||
).filter((element) => {
|
).filter((element) => {
|
||||||
// hitting a frame's element from outside the frame is not considered a hit
|
// hitting a frame's element from outside the frame is not considered a hit
|
||||||
const containingFrame = getContainingFrame(element);
|
const containingFrame = getContainingFrame(element, elementsMap);
|
||||||
return containingFrame &&
|
return containingFrame &&
|
||||||
this.state.frameRendering.enabled &&
|
this.state.frameRendering.enabled &&
|
||||||
this.state.frameRendering.clip
|
this.state.frameRendering.clip
|
||||||
|
@ -7789,7 +7789,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (linearElement?.frameId) {
|
if (linearElement?.frameId) {
|
||||||
const frame = getContainingFrame(linearElement);
|
const frame = getContainingFrame(linearElement, elementsMap);
|
||||||
|
|
||||||
if (frame && linearElement) {
|
if (frame && linearElement) {
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { mutateElement } from "./element/mutateElement";
|
||||||
import { AppClassProperties, AppState, StaticCanvasAppState } from "./types";
|
import { AppClassProperties, AppState, StaticCanvasAppState } from "./types";
|
||||||
import { getElementsWithinSelection, getSelectedElements } from "./scene";
|
import { getElementsWithinSelection, getSelectedElements } from "./scene";
|
||||||
import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
|
import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
|
||||||
import Scene, { ExcalidrawElementsIncludingDeleted } from "./scene/Scene";
|
import type { ExcalidrawElementsIncludingDeleted } from "./scene/Scene";
|
||||||
import { getElementLineSegments } from "./element/bounds";
|
import { getElementLineSegments } from "./element/bounds";
|
||||||
import {
|
import {
|
||||||
doLineSegmentsIntersect,
|
doLineSegmentsIntersect,
|
||||||
|
@ -377,25 +377,13 @@ export const getElementsInNewFrame = (
|
||||||
|
|
||||||
export const getContainingFrame = (
|
export const getContainingFrame = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
/**
|
elementsMap: ElementsMap,
|
||||||
* Optionally an elements map, in case the elements aren't in the Scene yet.
|
|
||||||
* Takes precedence over Scene elements, even if the element exists
|
|
||||||
* in Scene elements and not the supplied elements map.
|
|
||||||
*/
|
|
||||||
elementsMap?: Map<string, ExcalidrawElement>,
|
|
||||||
) => {
|
) => {
|
||||||
if (element.frameId) {
|
if (!element.frameId) {
|
||||||
if (elementsMap) {
|
return null;
|
||||||
|
}
|
||||||
return (elementsMap.get(element.frameId) ||
|
return (elementsMap.get(element.frameId) ||
|
||||||
null) as null | ExcalidrawFrameLikeElement;
|
null) as null | ExcalidrawFrameLikeElement;
|
||||||
}
|
|
||||||
return (
|
|
||||||
(Scene.getScene(element)?.getElement(
|
|
||||||
element.frameId,
|
|
||||||
) as ExcalidrawFrameLikeElement) || null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// --------------------------- Frame Operations -------------------------------
|
// --------------------------- Frame Operations -------------------------------
|
||||||
|
@ -697,7 +685,7 @@ export const getTargetFrame = (
|
||||||
return appState.selectedElementIds[_element.id] &&
|
return appState.selectedElementIds[_element.id] &&
|
||||||
appState.selectedElementsAreBeingDragged
|
appState.selectedElementsAreBeingDragged
|
||||||
? appState.frameToHighlight
|
? appState.frameToHighlight
|
||||||
: getContainingFrame(_element);
|
: getContainingFrame(_element, elementsMap);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: this a huge bottleneck for large scenes, optimise
|
// TODO: this a huge bottleneck for large scenes, optimise
|
||||||
|
|
|
@ -257,7 +257,8 @@ const generateElementCanvas = (
|
||||||
canvasOffsetY,
|
canvasOffsetY,
|
||||||
boundTextElementVersion:
|
boundTextElementVersion:
|
||||||
getBoundTextElement(element, elementsMap)?.version || null,
|
getBoundTextElement(element, elementsMap)?.version || null,
|
||||||
containingFrameOpacity: getContainingFrame(element)?.opacity || 100,
|
containingFrameOpacity:
|
||||||
|
getContainingFrame(element, elementsMap)?.opacity || 100,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -440,7 +441,8 @@ const generateElementWithCanvas = (
|
||||||
const boundTextElementVersion =
|
const boundTextElementVersion =
|
||||||
getBoundTextElement(element, elementsMap)?.version || null;
|
getBoundTextElement(element, elementsMap)?.version || null;
|
||||||
|
|
||||||
const containingFrameOpacity = getContainingFrame(element)?.opacity || 100;
|
const containingFrameOpacity =
|
||||||
|
getContainingFrame(element, elementsMap)?.opacity || 100;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!prevElementWithCanvas ||
|
!prevElementWithCanvas ||
|
||||||
|
@ -652,7 +654,7 @@ export const renderElement = (
|
||||||
) => {
|
) => {
|
||||||
context.globalAlpha = getRenderOpacity(
|
context.globalAlpha = getRenderOpacity(
|
||||||
element,
|
element,
|
||||||
getContainingFrame(element),
|
getContainingFrame(element, elementsMap),
|
||||||
renderConfig.elementsPendingErasure,
|
renderConfig.elementsPendingErasure,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -924,11 +926,12 @@ const maybeWrapNodesInFrameClipPath = (
|
||||||
root: SVGElement,
|
root: SVGElement,
|
||||||
nodes: SVGElement[],
|
nodes: SVGElement[],
|
||||||
frameRendering: AppState["frameRendering"],
|
frameRendering: AppState["frameRendering"],
|
||||||
|
elementsMap: RenderableElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (!frameRendering.enabled || !frameRendering.clip) {
|
if (!frameRendering.enabled || !frameRendering.clip) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const frame = getContainingFrame(element);
|
const frame = getContainingFrame(element, elementsMap);
|
||||||
if (frame) {
|
if (frame) {
|
||||||
const g = root.ownerDocument!.createElementNS(SVG_NS, "g");
|
const g = root.ownerDocument!.createElementNS(SVG_NS, "g");
|
||||||
g.setAttributeNS(SVG_NS, "clip-path", `url(#${frame.id})`);
|
g.setAttributeNS(SVG_NS, "clip-path", `url(#${frame.id})`);
|
||||||
|
@ -990,7 +993,9 @@ export const renderElementToSvg = (
|
||||||
};
|
};
|
||||||
|
|
||||||
const opacity =
|
const opacity =
|
||||||
((getContainingFrame(element)?.opacity ?? 100) * element.opacity) / 10000;
|
((getContainingFrame(element, elementsMap)?.opacity ?? 100) *
|
||||||
|
element.opacity) /
|
||||||
|
10000;
|
||||||
|
|
||||||
switch (element.type) {
|
switch (element.type) {
|
||||||
case "selection": {
|
case "selection": {
|
||||||
|
@ -1024,6 +1029,7 @@ export const renderElementToSvg = (
|
||||||
root,
|
root,
|
||||||
[node],
|
[node],
|
||||||
renderConfig.frameRendering,
|
renderConfig.frameRendering,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
addToRoot(g || node, element);
|
addToRoot(g || node, element);
|
||||||
|
@ -1215,6 +1221,7 @@ export const renderElementToSvg = (
|
||||||
root,
|
root,
|
||||||
[group, maskPath],
|
[group, maskPath],
|
||||||
renderConfig.frameRendering,
|
renderConfig.frameRendering,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
if (g) {
|
if (g) {
|
||||||
addToRoot(g, element);
|
addToRoot(g, element);
|
||||||
|
@ -1258,6 +1265,7 @@ export const renderElementToSvg = (
|
||||||
root,
|
root,
|
||||||
[node],
|
[node],
|
||||||
renderConfig.frameRendering,
|
renderConfig.frameRendering,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
addToRoot(g || node, element);
|
addToRoot(g || node, element);
|
||||||
|
@ -1355,6 +1363,7 @@ export const renderElementToSvg = (
|
||||||
root,
|
root,
|
||||||
[g],
|
[g],
|
||||||
renderConfig.frameRendering,
|
renderConfig.frameRendering,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
addToRoot(clipG || g, element);
|
addToRoot(clipG || g, element);
|
||||||
}
|
}
|
||||||
|
@ -1442,6 +1451,7 @@ export const renderElementToSvg = (
|
||||||
root,
|
root,
|
||||||
[node],
|
[node],
|
||||||
renderConfig.frameRendering,
|
renderConfig.frameRendering,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
addToRoot(g || node, element);
|
addToRoot(g || node, element);
|
||||||
|
|
|
@ -80,31 +80,18 @@ class Scene {
|
||||||
private static sceneMapByElement = new WeakMap<ExcalidrawElement, Scene>();
|
private static sceneMapByElement = new WeakMap<ExcalidrawElement, Scene>();
|
||||||
private static sceneMapById = new Map<string, Scene>();
|
private static sceneMapById = new Map<string, Scene>();
|
||||||
|
|
||||||
static mapElementToScene(
|
static mapElementToScene(elementKey: ElementKey, scene: Scene) {
|
||||||
elementKey: ElementKey,
|
|
||||||
scene: Scene,
|
|
||||||
/**
|
|
||||||
* needed because of frame exporting hack.
|
|
||||||
* elementId:Scene mapping will be removed completely, soon.
|
|
||||||
*/
|
|
||||||
mapElementIds = true,
|
|
||||||
) {
|
|
||||||
if (isIdKey(elementKey)) {
|
if (isIdKey(elementKey)) {
|
||||||
if (!mapElementIds) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// for cases where we don't have access to the element object
|
// for cases where we don't have access to the element object
|
||||||
// (e.g. restore serialized appState with id references)
|
// (e.g. restore serialized appState with id references)
|
||||||
this.sceneMapById.set(elementKey, scene);
|
this.sceneMapById.set(elementKey, scene);
|
||||||
} else {
|
} else {
|
||||||
this.sceneMapByElement.set(elementKey, scene);
|
this.sceneMapByElement.set(elementKey, scene);
|
||||||
if (!mapElementIds) {
|
|
||||||
// if mapping element objects, also cache the id string when later
|
// if mapping element objects, also cache the id string when later
|
||||||
// looking up by id alone
|
// looking up by id alone
|
||||||
this.sceneMapById.set(elementKey.id, scene);
|
this.sceneMapById.set(elementKey.id, scene);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static getScene(elementKey: ElementKey): Scene | null {
|
static getScene(elementKey: ElementKey): Scene | null {
|
||||||
if (isIdKey(elementKey)) {
|
if (isIdKey(elementKey)) {
|
||||||
|
@ -256,7 +243,7 @@ class Scene {
|
||||||
return didChange;
|
return didChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceAllElements(nextElements: ElementsMapOrArray, mapElementIds = true) {
|
replaceAllElements(nextElements: ElementsMapOrArray) {
|
||||||
this.elements =
|
this.elements =
|
||||||
// ts doesn't like `Array.isArray` of `instanceof Map`
|
// ts doesn't like `Array.isArray` of `instanceof Map`
|
||||||
nextElements instanceof Array
|
nextElements instanceof Array
|
||||||
|
@ -269,7 +256,7 @@ class Scene {
|
||||||
nextFrameLikes.push(element);
|
nextFrameLikes.push(element);
|
||||||
}
|
}
|
||||||
this.elementsMap.set(element.id, element);
|
this.elementsMap.set(element.id, element);
|
||||||
Scene.mapElementToScene(element, this, mapElementIds);
|
Scene.mapElementToScene(element, this);
|
||||||
});
|
});
|
||||||
const nonDeletedElements = getNonDeletedElements(this.elements);
|
const nonDeletedElements = getNonDeletedElements(this.elements);
|
||||||
this.nonDeletedElements = nonDeletedElements.elements;
|
this.nonDeletedElements = nonDeletedElements.elements;
|
||||||
|
|
|
@ -12,13 +12,7 @@ import {
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
} from "../element/bounds";
|
} from "../element/bounds";
|
||||||
import { renderSceneToSvg, renderStaticScene } from "../renderer/renderScene";
|
import { renderSceneToSvg, renderStaticScene } from "../renderer/renderScene";
|
||||||
import {
|
import { arrayToMap, distance, getFontString, toBrandedType } from "../utils";
|
||||||
arrayToMap,
|
|
||||||
cloneJSON,
|
|
||||||
distance,
|
|
||||||
getFontString,
|
|
||||||
toBrandedType,
|
|
||||||
} from "../utils";
|
|
||||||
import { AppState, BinaryFiles } from "../types";
|
import { AppState, BinaryFiles } from "../types";
|
||||||
import {
|
import {
|
||||||
DEFAULT_EXPORT_PADDING,
|
DEFAULT_EXPORT_PADDING,
|
||||||
|
@ -42,35 +36,11 @@ import {
|
||||||
import { newTextElement } from "../element";
|
import { newTextElement } from "../element";
|
||||||
import { Mutable } from "../utility-types";
|
import { Mutable } from "../utility-types";
|
||||||
import { newElementWith } from "../element/mutateElement";
|
import { newElementWith } from "../element/mutateElement";
|
||||||
import Scene from "./Scene";
|
|
||||||
import { isFrameElement, isFrameLikeElement } from "../element/typeChecks";
|
import { isFrameElement, isFrameLikeElement } from "../element/typeChecks";
|
||||||
import { RenderableElementsMap } from "./types";
|
import { RenderableElementsMap } from "./types";
|
||||||
|
|
||||||
const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
|
const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
|
||||||
|
|
||||||
// getContainerElement and getBoundTextElement and potentially other helpers
|
|
||||||
// depend on `Scene` which will not be available when these pure utils are
|
|
||||||
// called outside initialized Excalidraw editor instance or even if called
|
|
||||||
// from inside Excalidraw if the elements were never cached by Scene (e.g.
|
|
||||||
// for library elements).
|
|
||||||
//
|
|
||||||
// As such, before passing the elements down, we need to initialize a custom
|
|
||||||
// Scene instance and assign them to it.
|
|
||||||
//
|
|
||||||
// FIXME This is a super hacky workaround and we'll need to rewrite this soon.
|
|
||||||
const __createSceneForElementsHack__ = (
|
|
||||||
elements: readonly ExcalidrawElement[],
|
|
||||||
) => {
|
|
||||||
const scene = new Scene();
|
|
||||||
// we can't duplicate elements to regenerate ids because we need the
|
|
||||||
// orig ids when embedding. So we do another hack of not mapping element
|
|
||||||
// ids to Scene instances so that we don't override the editor elements
|
|
||||||
// mapping.
|
|
||||||
// We still need to clone the objects themselves to regen references.
|
|
||||||
scene.replaceAllElements(cloneJSON(elements), false);
|
|
||||||
return scene;
|
|
||||||
};
|
|
||||||
|
|
||||||
const truncateText = (element: ExcalidrawTextElement, maxWidth: number) => {
|
const truncateText = (element: ExcalidrawTextElement, maxWidth: number) => {
|
||||||
if (element.width <= maxWidth) {
|
if (element.width <= maxWidth) {
|
||||||
return element;
|
return element;
|
||||||
|
@ -213,9 +183,6 @@ export const exportToCanvas = async (
|
||||||
return { canvas, scale: appState.exportScale };
|
return { canvas, scale: appState.exportScale };
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const tempScene = __createSceneForElementsHack__(elements);
|
|
||||||
elements = tempScene.getNonDeletedElements();
|
|
||||||
|
|
||||||
const frameRendering = getFrameRenderingConfig(
|
const frameRendering = getFrameRenderingConfig(
|
||||||
exportingFrame ?? null,
|
exportingFrame ?? null,
|
||||||
appState.frameRendering ?? null,
|
appState.frameRendering ?? null,
|
||||||
|
@ -281,8 +248,6 @@ export const exportToCanvas = async (
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
tempScene.destroy();
|
|
||||||
|
|
||||||
return canvas;
|
return canvas;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -306,9 +271,6 @@ export const exportToSvg = async (
|
||||||
exportingFrame?: ExcalidrawFrameLikeElement | null;
|
exportingFrame?: ExcalidrawFrameLikeElement | null;
|
||||||
},
|
},
|
||||||
): Promise<SVGSVGElement> => {
|
): Promise<SVGSVGElement> => {
|
||||||
const tempScene = __createSceneForElementsHack__(elements);
|
|
||||||
elements = tempScene.getNonDeletedElements();
|
|
||||||
|
|
||||||
const frameRendering = getFrameRenderingConfig(
|
const frameRendering = getFrameRenderingConfig(
|
||||||
opts?.exportingFrame ?? null,
|
opts?.exportingFrame ?? null,
|
||||||
appState.frameRendering ?? null,
|
appState.frameRendering ?? null,
|
||||||
|
@ -470,8 +432,6 @@ export const exportToSvg = async (
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
tempScene.destroy();
|
|
||||||
|
|
||||||
return svgRoot;
|
return svgRoot;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ export const getElementsWithinSelection = (
|
||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const containingFrame = getContainingFrame(element);
|
const containingFrame = getContainingFrame(element, elementsMap);
|
||||||
if (containingFrame) {
|
if (containingFrame) {
|
||||||
const [fx1, fy1, fx2, fy2] = getElementBounds(
|
const [fx1, fy1, fx2, fy2] = getElementBounds(
|
||||||
containingFrame,
|
containingFrame,
|
||||||
|
@ -86,7 +86,7 @@ export const getElementsWithinSelection = (
|
||||||
: elementsInSelection;
|
: elementsInSelection;
|
||||||
|
|
||||||
elementsInSelection = elementsInSelection.filter((element) => {
|
elementsInSelection = elementsInSelection.filter((element) => {
|
||||||
const containingFrame = getContainingFrame(element);
|
const containingFrame = getContainingFrame(element, elementsMap);
|
||||||
|
|
||||||
if (containingFrame) {
|
if (containingFrame) {
|
||||||
return elementOverlapsWithFrame(element, containingFrame, elementsMap);
|
return elementOverlapsWithFrame(element, containingFrame, elementsMap);
|
||||||
|
|
Loading…
Add table
Reference in a new issue