Merge remote-tracking branch 'origin/release' into danieljgeiger-mathjax
This commit is contained in:
+164
-14
@@ -27,7 +27,13 @@ import { RoughSVG } from "roughjs/bin/svg";
|
||||
import { RoughGenerator } from "roughjs/bin/generator";
|
||||
|
||||
import { RenderConfig } from "../scene/types";
|
||||
import { distance, getFontString, getFontFamilyString, isRTL } from "../utils";
|
||||
import {
|
||||
distance,
|
||||
getFontString,
|
||||
getFontFamilyString,
|
||||
isRTL,
|
||||
isTransparent,
|
||||
} from "../utils";
|
||||
import { getCornerRadius, isPathALoop, isRightAngle } from "../math";
|
||||
import rough from "roughjs/bin/rough";
|
||||
import { AppState, BinaryFiles, Zoom } from "../types";
|
||||
@@ -50,7 +56,12 @@ import {
|
||||
getBoundTextMaxWidth,
|
||||
} from "../element/textElement";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
import {
|
||||
createPlaceholderEmbeddableLabel,
|
||||
getEmbedLink,
|
||||
} from "../element/embeddable";
|
||||
import { getContainingFrame } from "../frame";
|
||||
import { normalizeLink, toValidURL } from "../data/url";
|
||||
|
||||
// using a stronger invert (100% vs our regular 93%) and saturate
|
||||
// as a temp hack to make images in dark theme look closer to original
|
||||
@@ -268,6 +279,7 @@ const drawElementOnCanvas = (
|
||||
}
|
||||
switch (element.type) {
|
||||
case "rectangle":
|
||||
case "embeddable":
|
||||
case "diamond":
|
||||
case "ellipse": {
|
||||
context.lineJoin = "round";
|
||||
@@ -433,13 +445,13 @@ export const generateRoughOptions = (
|
||||
|
||||
switch (element.type) {
|
||||
case "rectangle":
|
||||
case "embeddable":
|
||||
case "diamond":
|
||||
case "ellipse": {
|
||||
options.fillStyle = element.fillStyle;
|
||||
options.fill =
|
||||
element.backgroundColor === "transparent"
|
||||
? undefined
|
||||
: element.backgroundColor;
|
||||
options.fill = isTransparent(element.backgroundColor)
|
||||
? undefined
|
||||
: element.backgroundColor;
|
||||
if (element.type === "ellipse") {
|
||||
options.curveFitting = 1;
|
||||
}
|
||||
@@ -464,6 +476,26 @@ export const generateRoughOptions = (
|
||||
}
|
||||
};
|
||||
|
||||
const modifyEmbeddableForRoughOptions = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
isExporting: boolean,
|
||||
) => {
|
||||
if (
|
||||
element.type === "embeddable" &&
|
||||
(isExporting || !element.validated) &&
|
||||
isTransparent(element.backgroundColor) &&
|
||||
isTransparent(element.strokeColor)
|
||||
) {
|
||||
return {
|
||||
...element,
|
||||
roughness: 0,
|
||||
backgroundColor: "#d3d3d3",
|
||||
fillStyle: "solid",
|
||||
} as const;
|
||||
}
|
||||
return element;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the element's shape and puts it into the cache.
|
||||
* @param element
|
||||
@@ -472,8 +504,9 @@ export const generateRoughOptions = (
|
||||
const generateElementShape = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
generator: RoughGenerator,
|
||||
isExporting: boolean = false,
|
||||
) => {
|
||||
let shape = shapeCache.get(element);
|
||||
let shape = isExporting ? undefined : shapeCache.get(element);
|
||||
|
||||
// `null` indicates no rc shape applicable for this element type
|
||||
// (= do not generate anything)
|
||||
@@ -481,7 +514,11 @@ const generateElementShape = (
|
||||
elementWithCanvasCache.delete(element);
|
||||
|
||||
switch (element.type) {
|
||||
case "rectangle": {
|
||||
case "rectangle":
|
||||
case "embeddable": {
|
||||
// this is for rendering the stroke/bg of the embeddable, especially
|
||||
// when the src url is not set
|
||||
|
||||
if (element.roundness) {
|
||||
const w = element.width;
|
||||
const h = element.height;
|
||||
@@ -492,7 +529,10 @@ const generateElementShape = (
|
||||
} Q ${w} ${h}, ${w - r} ${h} L ${r} ${h} Q 0 ${h}, 0 ${
|
||||
h - r
|
||||
} L 0 ${r} Q 0 0, ${r} 0`,
|
||||
generateRoughOptions(element, true),
|
||||
generateRoughOptions(
|
||||
modifyEmbeddableForRoughOptions(element, isExporting),
|
||||
true,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
shape = generator.rectangle(
|
||||
@@ -500,7 +540,10 @@ const generateElementShape = (
|
||||
0,
|
||||
element.width,
|
||||
element.height,
|
||||
generateRoughOptions(element),
|
||||
generateRoughOptions(
|
||||
modifyEmbeddableForRoughOptions(element, isExporting),
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
setShapeForElement(element, shape);
|
||||
@@ -879,7 +922,7 @@ const drawElementFromCanvas = (
|
||||
);
|
||||
|
||||
if (
|
||||
process.env.REACT_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX ===
|
||||
import.meta.env.VITE_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX ===
|
||||
"true" &&
|
||||
hasBoundTextElement(element)
|
||||
) {
|
||||
@@ -937,7 +980,11 @@ export const renderElement = (
|
||||
break;
|
||||
}
|
||||
case "frame": {
|
||||
if (!renderConfig.isExporting && appState.shouldRenderFrames) {
|
||||
if (
|
||||
!renderConfig.isExporting &&
|
||||
appState.frameRendering.enabled &&
|
||||
appState.frameRendering.outline
|
||||
) {
|
||||
context.save();
|
||||
context.translate(
|
||||
element.x + renderConfig.scrollX,
|
||||
@@ -998,8 +1045,9 @@ export const renderElement = (
|
||||
case "line":
|
||||
case "arrow":
|
||||
case "image":
|
||||
case "text": {
|
||||
generateElementShape(element, generator);
|
||||
case "text":
|
||||
case "embeddable": {
|
||||
generateElementShape(element, generator, renderConfig.isExporting);
|
||||
if (renderConfig.isExporting) {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const cx = (x1 + x2) / 2 + renderConfig.scrollX;
|
||||
@@ -1182,7 +1230,9 @@ export const renderElementToSvg = (
|
||||
offsetY: number,
|
||||
exportWithDarkMode?: boolean,
|
||||
exportingFrameId?: string | null,
|
||||
renderEmbeddables?: boolean,
|
||||
) => {
|
||||
const offset = { x: offsetX, y: offsetY };
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
let cx = (x2 - x1) / 2 - (element.x - x1);
|
||||
let cy = (y2 - y1) / 2 - (element.y - y1);
|
||||
@@ -1210,7 +1260,7 @@ export const renderElementToSvg = (
|
||||
// if the element has a link, create an anchor tag and make that the new root
|
||||
if (element.link) {
|
||||
const anchorTag = svgRoot.ownerDocument!.createElementNS(SVG_NS, "a");
|
||||
anchorTag.setAttribute("href", element.link);
|
||||
anchorTag.setAttribute("href", normalizeLink(element.link));
|
||||
root.appendChild(anchorTag);
|
||||
root = anchorTag;
|
||||
}
|
||||
@@ -1260,6 +1310,106 @@ export const renderElementToSvg = (
|
||||
g ? root.appendChild(g) : root.appendChild(node);
|
||||
break;
|
||||
}
|
||||
case "embeddable": {
|
||||
// render placeholder rectangle
|
||||
generateElementShape(element, generator, true);
|
||||
const node = roughSVGDrawWithPrecision(
|
||||
rsvg,
|
||||
getShapeForElement(element)!,
|
||||
MAX_DECIMALS_FOR_SVG_EXPORT,
|
||||
);
|
||||
const opacity = element.opacity / 100;
|
||||
if (opacity !== 1) {
|
||||
node.setAttribute("stroke-opacity", `${opacity}`);
|
||||
node.setAttribute("fill-opacity", `${opacity}`);
|
||||
}
|
||||
node.setAttribute("stroke-linecap", "round");
|
||||
node.setAttribute(
|
||||
"transform",
|
||||
`translate(${offsetX || 0} ${
|
||||
offsetY || 0
|
||||
}) rotate(${degree} ${cx} ${cy})`,
|
||||
);
|
||||
root.appendChild(node);
|
||||
|
||||
const label: ExcalidrawElement =
|
||||
createPlaceholderEmbeddableLabel(element);
|
||||
renderElementToSvg(
|
||||
label,
|
||||
rsvg,
|
||||
root,
|
||||
files,
|
||||
label.x + offset.x - element.x,
|
||||
label.y + offset.y - element.y,
|
||||
exportWithDarkMode,
|
||||
exportingFrameId,
|
||||
renderEmbeddables,
|
||||
);
|
||||
|
||||
// render embeddable element + iframe
|
||||
const embeddableNode = roughSVGDrawWithPrecision(
|
||||
rsvg,
|
||||
getShapeForElement(element)!,
|
||||
MAX_DECIMALS_FOR_SVG_EXPORT,
|
||||
);
|
||||
embeddableNode.setAttribute("stroke-linecap", "round");
|
||||
embeddableNode.setAttribute(
|
||||
"transform",
|
||||
`translate(${offsetX || 0} ${
|
||||
offsetY || 0
|
||||
}) rotate(${degree} ${cx} ${cy})`,
|
||||
);
|
||||
while (embeddableNode.firstChild) {
|
||||
embeddableNode.removeChild(embeddableNode.firstChild);
|
||||
}
|
||||
const radius = getCornerRadius(
|
||||
Math.min(element.width, element.height),
|
||||
element,
|
||||
);
|
||||
|
||||
const embedLink = getEmbedLink(toValidURL(element.link || ""));
|
||||
|
||||
// if rendering embeddables explicitly disabled or
|
||||
// embedding documents via srcdoc (which doesn't seem to work for SVGs)
|
||||
// replace with a link instead
|
||||
if (renderEmbeddables === false || embedLink?.type === "document") {
|
||||
const anchorTag = svgRoot.ownerDocument!.createElementNS(SVG_NS, "a");
|
||||
anchorTag.setAttribute("href", normalizeLink(element.link || ""));
|
||||
anchorTag.setAttribute("target", "_blank");
|
||||
anchorTag.setAttribute("rel", "noopener noreferrer");
|
||||
anchorTag.style.borderRadius = `${radius}px`;
|
||||
|
||||
embeddableNode.appendChild(anchorTag);
|
||||
} else {
|
||||
const foreignObject = svgRoot.ownerDocument!.createElementNS(
|
||||
SVG_NS,
|
||||
"foreignObject",
|
||||
);
|
||||
foreignObject.style.width = `${element.width}px`;
|
||||
foreignObject.style.height = `${element.height}px`;
|
||||
foreignObject.style.border = "none";
|
||||
const div = foreignObject.ownerDocument!.createElementNS(SVG_NS, "div");
|
||||
div.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
||||
div.style.width = "100%";
|
||||
div.style.height = "100%";
|
||||
const iframe = div.ownerDocument!.createElement("iframe");
|
||||
iframe.src = embedLink?.link ?? "";
|
||||
iframe.style.width = "100%";
|
||||
iframe.style.height = "100%";
|
||||
iframe.style.border = "none";
|
||||
iframe.style.borderRadius = `${radius}px`;
|
||||
iframe.style.top = "0";
|
||||
iframe.style.left = "0";
|
||||
iframe.allowFullscreen = true;
|
||||
div.appendChild(iframe);
|
||||
foreignObject.appendChild(div);
|
||||
|
||||
embeddableNode.appendChild(foreignObject);
|
||||
}
|
||||
|
||||
root.appendChild(embeddableNode);
|
||||
break;
|
||||
}
|
||||
case "line":
|
||||
case "arrow": {
|
||||
const boundText = getBoundTextElement(element);
|
||||
|
||||
+191
-65
@@ -62,7 +62,15 @@ import {
|
||||
EXTERNAL_LINK_IMG,
|
||||
getLinkHandleFromCoords,
|
||||
} from "../element/Hyperlink";
|
||||
import { isFrameElement, isLinearElement } from "../element/typeChecks";
|
||||
import {
|
||||
isEmbeddableElement,
|
||||
isFrameElement,
|
||||
isLinearElement,
|
||||
} from "../element/typeChecks";
|
||||
import {
|
||||
isEmbeddableOrFrameLabel,
|
||||
createPlaceholderEmbeddableLabel,
|
||||
} from "../element/embeddable";
|
||||
import {
|
||||
elementOverlapsWithFrame,
|
||||
getTargetFrame,
|
||||
@@ -154,23 +162,52 @@ const fillCircle = (
|
||||
const strokeGrid = (
|
||||
context: CanvasRenderingContext2D,
|
||||
gridSize: number,
|
||||
offsetX: number,
|
||||
offsetY: number,
|
||||
scrollX: number,
|
||||
scrollY: number,
|
||||
zoom: Zoom,
|
||||
width: number,
|
||||
height: number,
|
||||
) => {
|
||||
const BOLD_LINE_FREQUENCY = 5;
|
||||
|
||||
enum GridLineColor {
|
||||
Bold = "#cccccc",
|
||||
Regular = "#e5e5e5",
|
||||
}
|
||||
|
||||
const offsetX =
|
||||
-Math.round(zoom.value / gridSize) * gridSize + (scrollX % gridSize);
|
||||
const offsetY =
|
||||
-Math.round(zoom.value / gridSize) * gridSize + (scrollY % gridSize);
|
||||
|
||||
const lineWidth = Math.min(1 / zoom.value, 1);
|
||||
|
||||
const spaceWidth = 1 / zoom.value;
|
||||
const lineDash = [lineWidth * 3, spaceWidth + (lineWidth + spaceWidth)];
|
||||
|
||||
context.save();
|
||||
context.strokeStyle = "rgba(0,0,0,0.1)";
|
||||
context.beginPath();
|
||||
context.lineWidth = lineWidth;
|
||||
|
||||
for (let x = offsetX; x < offsetX + width + gridSize * 2; x += gridSize) {
|
||||
const isBold =
|
||||
Math.round(x - scrollX) % (BOLD_LINE_FREQUENCY * gridSize) === 0;
|
||||
context.beginPath();
|
||||
context.setLineDash(isBold ? [] : lineDash);
|
||||
context.strokeStyle = isBold ? GridLineColor.Bold : GridLineColor.Regular;
|
||||
context.moveTo(x, offsetY - gridSize);
|
||||
context.lineTo(x, offsetY + height + gridSize * 2);
|
||||
context.stroke();
|
||||
}
|
||||
for (let y = offsetY; y < offsetY + height + gridSize * 2; y += gridSize) {
|
||||
const isBold =
|
||||
Math.round(y - scrollY) % (BOLD_LINE_FREQUENCY * gridSize) === 0;
|
||||
context.beginPath();
|
||||
context.setLineDash(isBold ? [] : lineDash);
|
||||
context.strokeStyle = isBold ? GridLineColor.Bold : GridLineColor.Regular;
|
||||
context.moveTo(offsetX - gridSize, y);
|
||||
context.lineTo(offsetX + width + gridSize * 2, y);
|
||||
context.stroke();
|
||||
}
|
||||
context.stroke();
|
||||
context.restore();
|
||||
};
|
||||
|
||||
@@ -369,7 +406,7 @@ export const _renderScene = ({
|
||||
return { atLeastOneVisibleElement: false };
|
||||
}
|
||||
const {
|
||||
renderScrollbars = true,
|
||||
renderScrollbars = false,
|
||||
renderSelection = true,
|
||||
renderGrid = true,
|
||||
isExporting,
|
||||
@@ -417,12 +454,9 @@ export const _renderScene = ({
|
||||
strokeGrid(
|
||||
context,
|
||||
appState.gridSize,
|
||||
-Math.ceil(renderConfig.zoom.value / appState.gridSize) *
|
||||
appState.gridSize +
|
||||
(renderConfig.scrollX % appState.gridSize),
|
||||
-Math.ceil(renderConfig.zoom.value / appState.gridSize) *
|
||||
appState.gridSize +
|
||||
(renderConfig.scrollY % appState.gridSize),
|
||||
renderConfig.scrollX,
|
||||
renderConfig.scrollY,
|
||||
renderConfig.zoom,
|
||||
normalizedCanvasWidth / renderConfig.zoom.value,
|
||||
normalizedCanvasHeight / renderConfig.zoom.value,
|
||||
);
|
||||
@@ -460,46 +494,102 @@ export const _renderScene = ({
|
||||
let editingLinearElement: NonDeleted<ExcalidrawLinearElement> | undefined =
|
||||
undefined;
|
||||
|
||||
visibleElements.forEach((element) => {
|
||||
try {
|
||||
// - when exporting the whole canvas, we DO NOT apply clipping
|
||||
// - when we are exporting a particular frame, apply clipping
|
||||
// if the containing frame is not selected, apply clipping
|
||||
const frameId = element.frameId || appState.frameToHighlight?.id;
|
||||
visibleElements
|
||||
.filter((el) => !isEmbeddableOrFrameLabel(el))
|
||||
.forEach((element) => {
|
||||
try {
|
||||
// - when exporting the whole canvas, we DO NOT apply clipping
|
||||
// - when we are exporting a particular frame, apply clipping
|
||||
// if the containing frame is not selected, apply clipping
|
||||
const frameId = element.frameId || appState.frameToHighlight?.id;
|
||||
|
||||
if (
|
||||
frameId &&
|
||||
((renderConfig.isExporting && isOnlyExportingSingleFrame(elements)) ||
|
||||
(!renderConfig.isExporting && appState.shouldRenderFrames))
|
||||
) {
|
||||
context.save();
|
||||
if (
|
||||
frameId &&
|
||||
((renderConfig.isExporting &&
|
||||
isOnlyExportingSingleFrame(elements)) ||
|
||||
(!renderConfig.isExporting &&
|
||||
appState.frameRendering.enabled &&
|
||||
appState.frameRendering.clip))
|
||||
) {
|
||||
context.save();
|
||||
|
||||
const frame = getTargetFrame(element, appState);
|
||||
const frame = getTargetFrame(element, appState);
|
||||
|
||||
if (frame && isElementInFrame(element, elements, appState)) {
|
||||
frameClip(frame, context, renderConfig);
|
||||
if (frame && isElementInFrame(element, elements, appState)) {
|
||||
frameClip(frame, context, renderConfig);
|
||||
}
|
||||
renderElement(element, rc, context, renderConfig, appState);
|
||||
context.restore();
|
||||
} else {
|
||||
renderElement(element, rc, context, renderConfig, appState);
|
||||
}
|
||||
renderElement(element, rc, context, renderConfig, appState);
|
||||
context.restore();
|
||||
} else {
|
||||
renderElement(element, rc, context, renderConfig, appState);
|
||||
}
|
||||
// Getting the element using LinearElementEditor during collab mismatches version - being one head of visible elements due to
|
||||
// ShapeCache returns empty hence making sure that we get the
|
||||
// correct element from visible elements
|
||||
if (appState.editingLinearElement?.elementId === element.id) {
|
||||
if (element) {
|
||||
editingLinearElement =
|
||||
element as NonDeleted<ExcalidrawLinearElement>;
|
||||
// Getting the element using LinearElementEditor during collab mismatches version - being one head of visible elements due to
|
||||
// ShapeCache returns empty hence making sure that we get the
|
||||
// correct element from visible elements
|
||||
if (appState.editingLinearElement?.elementId === element.id) {
|
||||
if (element) {
|
||||
editingLinearElement =
|
||||
element as NonDeleted<ExcalidrawLinearElement>;
|
||||
}
|
||||
}
|
||||
if (!isExporting) {
|
||||
renderLinkIcon(element, context, appState);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
}
|
||||
if (!isExporting) {
|
||||
renderLinkIcon(element, context, appState);
|
||||
});
|
||||
|
||||
// render embeddables on top
|
||||
visibleElements
|
||||
.filter((el) => isEmbeddableOrFrameLabel(el))
|
||||
.forEach((element) => {
|
||||
try {
|
||||
const render = () => {
|
||||
renderElement(element, rc, context, renderConfig, appState);
|
||||
|
||||
if (
|
||||
isEmbeddableElement(element) &&
|
||||
(isExporting || !element.validated) &&
|
||||
element.width &&
|
||||
element.height
|
||||
) {
|
||||
const label = createPlaceholderEmbeddableLabel(element);
|
||||
renderElement(label, rc, context, renderConfig, appState);
|
||||
}
|
||||
if (!isExporting) {
|
||||
renderLinkIcon(element, context, appState);
|
||||
}
|
||||
};
|
||||
// - when exporting the whole canvas, we DO NOT apply clipping
|
||||
// - when we are exporting a particular frame, apply clipping
|
||||
// if the containing frame is not selected, apply clipping
|
||||
const frameId = element.frameId || appState.frameToHighlight?.id;
|
||||
|
||||
if (
|
||||
frameId &&
|
||||
((renderConfig.isExporting &&
|
||||
isOnlyExportingSingleFrame(elements)) ||
|
||||
(!renderConfig.isExporting &&
|
||||
appState.frameRendering.enabled &&
|
||||
appState.frameRendering.clip))
|
||||
) {
|
||||
context.save();
|
||||
|
||||
const frame = getTargetFrame(element, appState);
|
||||
|
||||
if (frame && isElementInFrame(element, elements, appState)) {
|
||||
frameClip(frame, context, renderConfig);
|
||||
}
|
||||
render();
|
||||
context.restore();
|
||||
} else {
|
||||
render();
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (editingLinearElement) {
|
||||
renderLinearPointHandles(
|
||||
@@ -638,10 +728,13 @@ export const _renderScene = ({
|
||||
dashed: !!renderConfig.remoteSelectedElementIds[element.id],
|
||||
cx,
|
||||
cy,
|
||||
activeEmbeddable:
|
||||
appState.activeEmbeddable?.element === element &&
|
||||
appState.activeEmbeddable.state === "active",
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, [] as { angle: number; elementX1: number; elementY1: number; elementX2: number; elementY2: number; selectionColors: string[]; dashed?: boolean; cx: number; cy: number }[]);
|
||||
}, [] as { angle: number; elementX1: number; elementY1: number; elementX2: number; elementY2: number; selectionColors: string[]; dashed?: boolean; cx: number; cy: number; activeEmbeddable: boolean }[]);
|
||||
|
||||
const addSelectionForGroupId = (groupId: GroupId) => {
|
||||
const groupElements = getElementsInGroup(elements, groupId);
|
||||
@@ -657,6 +750,7 @@ export const _renderScene = ({
|
||||
dashed: true,
|
||||
cx: elementX1 + (elementX2 - elementX1) / 2,
|
||||
cy: elementY1 + (elementY2 - elementY1) / 2,
|
||||
activeEmbeddable: false,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -998,6 +1092,7 @@ const renderSelectionBorder = (
|
||||
dashed?: boolean;
|
||||
cx: number;
|
||||
cy: number;
|
||||
activeEmbeddable: boolean;
|
||||
},
|
||||
padding = DEFAULT_SPACING * 2,
|
||||
) => {
|
||||
@@ -1011,6 +1106,7 @@ const renderSelectionBorder = (
|
||||
cx,
|
||||
cy,
|
||||
dashed,
|
||||
activeEmbeddable,
|
||||
} = elementProperties;
|
||||
const elementWidth = elementX2 - elementX1;
|
||||
const elementHeight = elementY2 - elementY1;
|
||||
@@ -1021,7 +1117,7 @@ const renderSelectionBorder = (
|
||||
|
||||
context.save();
|
||||
context.translate(renderConfig.scrollX, renderConfig.scrollY);
|
||||
context.lineWidth = 1 / renderConfig.zoom.value;
|
||||
context.lineWidth = (activeEmbeddable ? 4 : 1) / renderConfig.zoom.value;
|
||||
|
||||
const count = selectionColors.length;
|
||||
for (let index = 0; index < count; ++index) {
|
||||
@@ -1082,6 +1178,7 @@ const renderBindingHighlightForBindableElement = (
|
||||
case "rectangle":
|
||||
case "text":
|
||||
case "image":
|
||||
case "embeddable":
|
||||
case "frame":
|
||||
strokeRectWithRotation(
|
||||
context,
|
||||
@@ -1176,6 +1273,7 @@ const renderElementsBoxHighlight = (
|
||||
dashed: false,
|
||||
cx: elementX1 + (elementX2 - elementX1) / 2,
|
||||
cy: elementY1 + (elementY2 - elementY1) / 2,
|
||||
activeEmbeddable: false,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1324,11 +1422,13 @@ export const renderSceneToSvg = (
|
||||
offsetY = 0,
|
||||
exportWithDarkMode = false,
|
||||
exportingFrameId = null,
|
||||
renderEmbeddables,
|
||||
}: {
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
exportWithDarkMode?: boolean;
|
||||
exportingFrameId?: string | null;
|
||||
renderEmbeddables?: boolean;
|
||||
} = {},
|
||||
) => {
|
||||
if (!svgRoot) {
|
||||
@@ -1336,22 +1436,48 @@ export const renderSceneToSvg = (
|
||||
}
|
||||
|
||||
// render elements
|
||||
elements.forEach((element) => {
|
||||
if (!element.isDeleted) {
|
||||
try {
|
||||
renderElementToSvg(
|
||||
element,
|
||||
rsvg,
|
||||
svgRoot,
|
||||
files,
|
||||
element.x + offsetX,
|
||||
element.y + offsetY,
|
||||
exportWithDarkMode,
|
||||
exportingFrameId,
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
elements
|
||||
.filter((el) => !isEmbeddableOrFrameLabel(el))
|
||||
.forEach((element) => {
|
||||
if (!element.isDeleted) {
|
||||
try {
|
||||
renderElementToSvg(
|
||||
element,
|
||||
rsvg,
|
||||
svgRoot,
|
||||
files,
|
||||
element.x + offsetX,
|
||||
element.y + offsetY,
|
||||
exportWithDarkMode,
|
||||
exportingFrameId,
|
||||
renderEmbeddables,
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// render embeddables on top
|
||||
elements
|
||||
.filter((el) => isEmbeddableElement(el))
|
||||
.forEach((element) => {
|
||||
if (!element.isDeleted) {
|
||||
try {
|
||||
renderElementToSvg(
|
||||
element,
|
||||
rsvg,
|
||||
svgRoot,
|
||||
files,
|
||||
element.x + offsetX,
|
||||
element.y + offsetY,
|
||||
exportWithDarkMode,
|
||||
exportingFrameId,
|
||||
renderEmbeddables,
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user