tldraw
Version:
A tiny little drawing editor.
282 lines (281 loc) • 9.13 kB
JavaScript
import { jsx } from "react/jsx-runtime";
import {
BaseBoxShapeUtil,
HTMLContainer,
Rectangle2d,
embedShapeMigrations,
embedShapeProps,
lerp,
resizeBox,
toDomPrecision,
useIsEditing,
useSvgExportContext,
useValue
} from "@tldraw/editor";
import {
DEFAULT_EMBED_DEFINITIONS,
embedShapePermissionDefaults
} from "../../defaultEmbedDefinitions.mjs";
import { getEmbedInfo } from "../../utils/embeds/embeds.mjs";
import { BookmarkIndicatorComponent, BookmarkShapeComponent } from "../bookmark/BookmarkShapeUtil.mjs";
import { BOOKMARK_JUST_URL_HEIGHT, BOOKMARK_WIDTH } from "../bookmark/bookmarks.mjs";
import { getRotatedBoxShadow } from "../shared/rotated-box-shadow.mjs";
const getSandboxPermissions = (permissions) => {
return Object.entries(permissions).filter(([_perm, isEnabled]) => isEnabled).map(([perm]) => perm).join(" ");
};
class EmbedShapeUtil extends BaseBoxShapeUtil {
static type = "embed";
static props = embedShapeProps;
static migrations = embedShapeMigrations;
options = {
embedDefinitions: DEFAULT_EMBED_DEFINITIONS
};
canEditWhileLocked(shape) {
const result = this.getEmbedDefinition(shape.props.url);
if (!result) return true;
return result.definition.canEditWhileLocked ?? true;
}
static legacyEmbedDefinitions = null;
/** @deprecated - Use `EmbedShapeUtil.configure({ embedDefinitions: [...] })` instead. */
static setEmbedDefinitions(embedDefinitions) {
EmbedShapeUtil.legacyEmbedDefinitions = embedDefinitions;
}
getEmbedDefs() {
return EmbedShapeUtil.legacyEmbedDefinitions ?? this.options.embedDefinitions;
}
getEmbedDefinitions() {
return this.getEmbedDefs();
}
getEmbedDefinition(url) {
return getEmbedInfo(this.getEmbedDefs(), url);
}
getText(shape) {
return shape.props.url;
}
getAriaDescriptor(shape) {
const embedInfo = this.getEmbedDefinition(shape.props.url);
return embedInfo?.definition.title;
}
hideSelectionBoundsFg(shape) {
return !this.canResize(shape);
}
canEdit() {
return true;
}
canResize(shape) {
return !!this.getEmbedDefinition(shape.props.url)?.definition?.doesResize;
}
canEditInReadonly() {
return true;
}
getDefaultProps() {
return {
w: 300,
h: 300,
url: ""
};
}
getGeometry(shape) {
const embedInfo = this.getEmbedDefinition(shape.props.url);
if (!embedInfo?.definition) {
return new Rectangle2d({
width: BOOKMARK_WIDTH,
height: BOOKMARK_JUST_URL_HEIGHT,
isFilled: true
});
}
return super.getGeometry(shape);
}
isAspectRatioLocked(shape) {
const embedInfo = this.getEmbedDefinition(shape.props.url);
return embedInfo?.definition.isAspectRatioLocked ?? false;
}
onResize(shape, info) {
const isAspectRatioLocked = this.isAspectRatioLocked(shape);
const embedInfo = this.getEmbedDefinition(shape.props.url);
let minWidth = embedInfo?.definition.minWidth ?? 200;
let minHeight = embedInfo?.definition.minHeight ?? 200;
if (isAspectRatioLocked) {
const aspectRatio = shape.props.w / shape.props.h;
if (aspectRatio > 1) {
minWidth *= aspectRatio;
} else {
minHeight /= aspectRatio;
}
}
return resizeBox(shape, info, { minWidth, minHeight });
}
component(shape) {
const svgExport = useSvgExportContext();
const { w, h, url } = shape.props;
const isEditing = useIsEditing(shape.id);
const embedInfo = this.getEmbedDefinition(url);
const isHoveringWhileEditingSameShape = useValue(
"is hovering",
() => {
const { editingShapeId, hoveredShapeId } = this.editor.getCurrentPageState();
if (editingShapeId && hoveredShapeId !== editingShapeId) {
const editingShape = this.editor.getShape(editingShapeId);
if (editingShape && this.editor.isShapeOfType(editingShape, "embed")) {
return true;
}
}
return false;
},
[]
);
const pageRotation = this.editor.getShapePageTransform(shape).rotation();
if (svgExport) {
return /* @__PURE__ */ jsx(HTMLContainer, { className: "tl-embed-container", id: shape.id, children: /* @__PURE__ */ jsx(
"div",
{
className: "tl-embed",
style: {
border: 0,
boxShadow: getRotatedBoxShadow(pageRotation),
borderRadius: embedInfo?.definition.overrideOutlineRadius ?? 8,
background: embedInfo?.definition.backgroundColor ?? "var(--tl-color-background)",
width: w,
height: h
}
}
) });
}
const isInteractive = isEditing || isHoveringWhileEditingSameShape;
const isIframe = typeof window !== "undefined" && (window !== window.top || window.self !== window.parent);
if (isIframe && embedInfo?.definition.type === "tldraw") return null;
if (embedInfo?.definition.type === "github_gist") {
const idFromGistUrl = embedInfo.url.split("/").pop();
if (!idFromGistUrl) throw Error("No gist id!");
return /* @__PURE__ */ jsx(HTMLContainer, { className: "tl-embed-container", id: shape.id, children: /* @__PURE__ */ jsx(
Gist,
{
id: idFromGistUrl,
width: toDomPrecision(w),
height: toDomPrecision(h),
isInteractive,
pageRotation
}
) });
}
const sandbox = getSandboxPermissions({
...embedShapePermissionDefaults,
...(embedInfo?.definition.overridePermissions ?? {})
});
return /* @__PURE__ */ jsx(HTMLContainer, { className: "tl-embed-container", id: shape.id, children: embedInfo?.definition ? /* @__PURE__ */ jsx(
"iframe",
{
className: "tl-embed",
sandbox,
src: embedInfo.embedUrl,
width: toDomPrecision(w),
height: toDomPrecision(h),
draggable: false,
frameBorder: "0",
referrerPolicy: "no-referrer-when-downgrade",
tabIndex: isEditing ? 0 : -1,
allowFullScreen: true,
style: {
border: 0,
pointerEvents: isInteractive ? "auto" : "none",
// Fix for safari <https://stackoverflow.com/a/49150908>
zIndex: isInteractive ? "" : "-1",
boxShadow: getRotatedBoxShadow(pageRotation),
borderRadius: embedInfo?.definition.overrideOutlineRadius ?? 8,
background: embedInfo?.definition.backgroundColor
}
}
) : /* @__PURE__ */ jsx(
BookmarkShapeComponent,
{
url,
h,
rotation: pageRotation,
assetId: null,
showImageContainer: false
}
) });
}
indicator(shape) {
const embedInfo = this.getEmbedDefinition(shape.props.url);
return embedInfo?.definition ? /* @__PURE__ */ jsx(
"rect",
{
width: toDomPrecision(shape.props.w),
height: toDomPrecision(shape.props.h),
rx: embedInfo?.definition.overrideOutlineRadius ?? 8,
ry: embedInfo?.definition.overrideOutlineRadius ?? 8
}
) : /* @__PURE__ */ jsx(BookmarkIndicatorComponent, { w: BOOKMARK_WIDTH, h: BOOKMARK_JUST_URL_HEIGHT });
}
useLegacyIndicator() {
return false;
}
getIndicatorPath(shape) {
const path = new Path2D();
const embedInfo = this.getEmbedDefinition(shape.props.url);
if (embedInfo?.definition) {
const radius = embedInfo.definition.overrideOutlineRadius ?? 8;
path.roundRect(0, 0, shape.props.w, shape.props.h, radius);
} else {
path.roundRect(0, 0, BOOKMARK_WIDTH, BOOKMARK_JUST_URL_HEIGHT, 6);
}
return path;
}
getInterpolatedProps(startShape, endShape, t) {
return {
...(t > 0.5 ? endShape.props : startShape.props),
w: lerp(startShape.props.w, endShape.props.w, t),
h: lerp(startShape.props.h, endShape.props.h, t)
};
}
}
function Gist({
id,
isInteractive,
width,
height,
style,
pageRotation
}) {
if (!id.match(/^[0-9a-f]+$/)) throw Error("No gist id!");
return /* @__PURE__ */ jsx(
"iframe",
{
className: "tl-embed",
draggable: false,
width: toDomPrecision(width),
height: toDomPrecision(height),
frameBorder: "0",
scrolling: "no",
referrerPolicy: "no-referrer-when-downgrade",
tabIndex: isInteractive ? 0 : -1,
style: {
...style,
pointerEvents: isInteractive ? "all" : "none",
// Fix for safari <https://stackoverflow.com/a/49150908>
zIndex: isInteractive ? "" : "-1",
boxShadow: getRotatedBoxShadow(pageRotation)
},
srcDoc: `
<html>
<head>
<base target="_blank">
</head>
<body>
<script src=${`https://gist.github.com/${id}.js`}></script>
<style type="text/css">
* { margin: 0px; }
table { height: 100%; background-color: red; }
.gist { background-color: none; height: 100%; }
.gist .gist-file { height: calc(100vh - 2px); padding: 0px; display: grid; grid-template-rows: 1fr auto; }
</style>
</body>
</html>`
}
);
}
export {
EmbedShapeUtil
};
//# sourceMappingURL=EmbedShapeUtil.mjs.map