diff --git a/apps/web/src/features/canvas/AppMenu.tsx b/apps/web/src/features/canvas/AppMenu.tsx index 068d58e..e18d8bc 100644 --- a/apps/web/src/features/canvas/AppMenu.tsx +++ b/apps/web/src/features/canvas/AppMenu.tsx @@ -73,6 +73,9 @@ export function AppMenu({ boardId, client, owned }: AppMenuProps) { const [pagesOpen, setPagesOpen] = useState(false); const [snapshotsOpen, setSnapshotsOpen] = useState(false); const [exportOpen, setExportOpen] = useState(false); + const [embedOpen, setEmbedOpen] = useState(false); + const [embedUrl, setEmbedUrl] = useState(""); + const [embedError, setEmbedError] = useState(null); const [exporting, setExporting] = useState(false); const [dragIdx, setDragIdx] = useState(null); const [overIdx, setOverIdx] = useState(null); @@ -141,6 +144,17 @@ export function AppMenu({ boardId, client, owned }: AppMenuProps) { } } + function submitEmbed() { + const err = useAssetStore.getState().insertEmbed(embedUrl); + if (err) { + setEmbedError(err); + return; + } + setEmbedUrl(""); + setEmbedError(null); + setEmbedOpen(false); + } + function commitDrop() { if (dragIdx !== null && overIdx !== null && dragIdx !== overIdx) { reorderPage(dragIdx, overIdx); @@ -212,10 +226,20 @@ export function AppMenu({ boardId, client, owned }: AppMenuProps) { /> run(() => fileRef.current?.click())} /> + { + setMenuOpen(false); + setEmbedError(null); + setEmbedOpen(true); + }} + /> + {/* Embed-by-URL (YouTube / Google Drive) */} + setEmbedOpen(false)} + anchorRef={menuBtnRef} + placement="bottom" + className="menu-popover" + > +
+
Embed a link
+ { + setEmbedUrl(e.target.value); + if (embedError) setEmbedError(null); + }} + onKeyDown={(e) => { + if (e.key === "Enter") submitEmbed(); + }} + style={{ + width: "100%", + boxSizing: "border-box", + padding: "8px 10px", + borderRadius: 8, + border: "1px solid rgba(255,255,255,0.18)", + background: "rgba(0,0,0,0.25)", + color: "inherit", + outline: "none", + marginTop: 6, + }} + /> + {embedError && ( +
+ {embedError} +
+ )} +
+ Google Drive files must be shared “anyone with the link”. +
+ +
+
+ setSnapshotsOpen(false)} @@ -394,7 +471,7 @@ export function AppMenu({ boardId, client, owned }: AppMenuProps) { { diff --git a/apps/web/src/features/canvas/SelectionInspector.tsx b/apps/web/src/features/canvas/SelectionInspector.tsx index 7569e64..3e97b49 100644 --- a/apps/web/src/features/canvas/SelectionInspector.tsx +++ b/apps/web/src/features/canvas/SelectionInspector.tsx @@ -21,6 +21,7 @@ function shapeColor(s: YShape): string | undefined { case "sticky": return s.color; case "asset": + case "embed": return undefined; } } @@ -38,6 +39,7 @@ function colorPatch(s: YShape, color: string): Partial | null { case "sticky": return { color }; case "asset": + case "embed": return null; } } diff --git a/packages/canvas/src/CanvasStage.tsx b/packages/canvas/src/CanvasStage.tsx index 96280dd..7727833 100644 --- a/packages/canvas/src/CanvasStage.tsx +++ b/packages/canvas/src/CanvasStage.tsx @@ -18,6 +18,7 @@ import { useShapeStore } from "./store/shapeStore"; import { useTextEditStore } from "./store/textEditStore"; import { useToolStore } from "./store/toolStore"; import { TextEditorOverlay } from "./TextEditorOverlay"; +import { MediaOverlayLayer } from "./MediaOverlayLayer"; import { makeTool } from "./tools/registry"; import { boundsIntersect, shapeBounds } from "./tools/shapeOps"; import type { Tool, ToolContext, ToolEventPoint } from "./tools/types"; @@ -523,6 +524,11 @@ export function CanvasStage({ + ; + viewport: ViewportState; +} + +// HTML players floated over the Konva stage, one per `embed` shape. Konva's +// can't host