"use client"; import { useEffect, useRef, useState, type MouseEvent, } from "react"; import { Handle, Position, type Node, type NodeProps } from "@xyflow/react"; import { useMutation } from "convex/react"; import { ExternalLink, ImageIcon } from "lucide-react"; import BaseNodeWrapper from "./base-node-wrapper"; import { AssetBrowserPanel, type AssetBrowserSessionState, } from "@/components/canvas/asset-browser-panel"; import { api } from "@/convex/_generated/api"; import type { Id } from "@/convex/_generated/dataModel"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { computeMediaNodeSize, resolveMediaAspectRatio } from "@/lib/canvas-utils"; type AssetNodeData = { assetId?: number; assetType?: "photo" | "vector" | "icon"; title?: string; previewUrl?: string; intrinsicWidth?: number; intrinsicHeight?: number; url?: string; sourceUrl?: string; license?: "freemium" | "premium"; authorName?: string; orientation?: string; canvasId?: string; _status?: string; _statusMessage?: string; }; export type AssetNodeType = Node; export default function AssetNode({ id, data, selected, width, height }: NodeProps) { const [panelOpen, setPanelOpen] = useState(false); const [loadedPreviewUrl, setLoadedPreviewUrl] = useState(null); const [failedPreviewUrl, setFailedPreviewUrl] = useState(null); const [browserState, setBrowserState] = useState({ term: "", assetType: "photo", results: [], page: 1, totalPages: 1, }); const resizeNode = useMutation(api.nodes.resize); const hasAsset = typeof data.assetId === "number"; const previewUrl = data.url ?? data.previewUrl; const isPreviewLoading = Boolean( previewUrl && previewUrl !== loadedPreviewUrl && previewUrl !== failedPreviewUrl, ); const previewLoadError = Boolean(previewUrl && previewUrl === failedPreviewUrl); const aspectRatio = resolveMediaAspectRatio( data.intrinsicWidth, data.intrinsicHeight, data.orientation, ); const hasAutoSizedRef = useRef(false); useEffect(() => { if (!hasAsset) return; if (hasAutoSizedRef.current) return; hasAutoSizedRef.current = true; const targetSize = computeMediaNodeSize("asset", { intrinsicWidth: data.intrinsicWidth, intrinsicHeight: data.intrinsicHeight, orientation: data.orientation, }); if (width === targetSize.width && height === targetSize.height) { return; } void resizeNode({ nodeId: id as Id<"nodes">, width: targetSize.width, height: targetSize.height, }); }, [ data.intrinsicHeight, data.intrinsicWidth, data.orientation, hasAsset, height, id, resizeNode, width, ]); const stopNodeClickPropagation = (event: MouseEvent) => { event.stopPropagation(); }; return (
Asset
{hasAsset && previewUrl ? (
{isPreviewLoading ? (
Loading preview...
) : null} {previewLoadError ? (
Preview unavailable
) : null} {/* eslint-disable-next-line @next/next/no-img-element */} {data.title { setLoadedPreviewUrl(previewUrl ?? null); setFailedPreviewUrl((current) => current === (previewUrl ?? null) ? null : current, ); }} onError={() => { setFailedPreviewUrl(previewUrl ?? null); }} /> {data.assetType ?? "asset"} {data.license ? ( {data.license} ) : null}

{data.title ?? "Untitled"}

by {data.authorName ?? "Freepik"} {data.sourceUrl ? ( freepik.com ) : null}
) : (

No asset selected

Browse millions of Freepik resources

)}
{panelOpen && data.canvasId ? ( setPanelOpen(false)} /> ) : null}
); }