UNPKG

tldraw

Version:

A tiny little drawing editor.

282 lines (281 loc) • 9.13 kB
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