mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: introducing Web-Embeds (alias iframe element) (#6691)
Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
744e5b2ab3
commit
b57b3b573d
48 changed files with 1923 additions and 234 deletions
|
@ -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,
|
||||
|
@ -460,48 +468,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.frameRendering.enabled &&
|
||||
appState.frameRendering.clip))
|
||||
) {
|
||||
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(
|
||||
|
@ -640,10 +702,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);
|
||||
|
@ -659,6 +724,7 @@ export const _renderScene = ({
|
|||
dashed: true,
|
||||
cx: elementX1 + (elementX2 - elementX1) / 2,
|
||||
cy: elementY1 + (elementY2 - elementY1) / 2,
|
||||
activeEmbeddable: false,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1000,6 +1066,7 @@ const renderSelectionBorder = (
|
|||
dashed?: boolean;
|
||||
cx: number;
|
||||
cy: number;
|
||||
activeEmbeddable: boolean;
|
||||
},
|
||||
padding = DEFAULT_SPACING * 2,
|
||||
) => {
|
||||
|
@ -1013,6 +1080,7 @@ const renderSelectionBorder = (
|
|||
cx,
|
||||
cy,
|
||||
dashed,
|
||||
activeEmbeddable,
|
||||
} = elementProperties;
|
||||
const elementWidth = elementX2 - elementX1;
|
||||
const elementHeight = elementY2 - elementY1;
|
||||
|
@ -1023,7 +1091,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) {
|
||||
|
@ -1084,6 +1152,7 @@ const renderBindingHighlightForBindableElement = (
|
|||
case "rectangle":
|
||||
case "text":
|
||||
case "image":
|
||||
case "embeddable":
|
||||
case "frame":
|
||||
strokeRectWithRotation(
|
||||
context,
|
||||
|
@ -1178,6 +1247,7 @@ const renderElementsBoxHighlight = (
|
|||
dashed: false,
|
||||
cx: elementX1 + (elementX2 - elementX1) / 2,
|
||||
cy: elementY1 + (elementY2 - elementY1) / 2,
|
||||
activeEmbeddable: false,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1326,11 +1396,13 @@ export const renderSceneToSvg = (
|
|||
offsetY = 0,
|
||||
exportWithDarkMode = false,
|
||||
exportingFrameId = null,
|
||||
renderEmbeddables,
|
||||
}: {
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
exportWithDarkMode?: boolean;
|
||||
exportingFrameId?: string | null;
|
||||
renderEmbeddables?: boolean;
|
||||
} = {},
|
||||
) => {
|
||||
if (!svgRoot) {
|
||||
|
@ -1338,22 +1410,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);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue