feat: introducing Web-Embeds (alias iframe element) (#6691)

Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
zsviczian 2023-07-24 16:51:53 +02:00 committed by GitHub
parent 744e5b2ab3
commit b57b3b573d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 1923 additions and 234 deletions

View file

@ -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);
}
}
});
};