"use client"; import { useEffect, useId, useRef, useState } from "react"; import { ImageIcon } from "lucide-react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { useCanvasRuntimeHost } from "@/components/workspace/canvas-runtime-host"; import type { ProjectEngine } from "@/lib/canvas-runtime/workspace"; import type { Id, Point } from "@/lib/canvas-core/types"; import { fault } from "@/lib/canvas-core/values"; import { errorText } from "@/lib/api"; function Preview({src}:{src:string}){const [failed,setFailed]=useState(false);return failed?:setFailed(true)}/>;} type Candidate = { id: Id; title: string }; export function HeadlessResourcePicker({engine,workspaceId,canvasId,position,close,onAdded}:{engine:ProjectEngine;workspaceId:Id;canvasId:Id;position:Point;close():void;onAdded(id:Id):void}) { const host=useCanvasRuntimeHost(),[items,setItems]=useState([]),[loading,setLoading]=useState(true),[busy,setBusy]=useState(),[error,setError]=useState(""); const lifecycle=useRef(null),requesting=useRef(false),descriptionId=useId(); const dismiss=()=>{lifecycle.current?.abort();close();}; const [target]=useState(()=>{const document=engine.getSnapshot().document,canvas=document.canvases.get(canvasId),entry=canvas?.parent?document.elements.get(canvas.parent.entryElementId):undefined;return {canvas,entry};}); useEffect(()=>{ const controller=new AbortController();lifecycle.current=controller; const identity=host.identity(),workspace=host.getWorkspaces().get(workspaceId); void (async()=>{ if(!workspace?.isCurrentIdentity()||workspace.accountId!==identity.accountId)throw fault('identity_changed'); const results=await Promise.allSettled([...workspace.getProjects()].filter(([id])=>id!==engine.getSnapshot().document.projectId).map(([,project])=>project)); if(controller.signal.aborted)return; if(!workspace.isCurrentIdentity()||host.identity().authEpoch!==identity.authEpoch)throw fault('identity_changed'); const candidates=new Map(),owned=engine.getSnapshot().runtime.resources; for(const result of results)if(result.status==='fulfilled'){ const snapshot=result.value.getSnapshot(); for(const element of snapshot.document.elements.values()){ const id=element.data.assetId; if(element.structure!=='node'||typeof id!=='string'||!id||owned.has(id)||candidates.has(id)||snapshot.runtime.resources.has(id)&&!snapshot.runtime.resources.get(id)!.stored)continue; if(!result.value.editor.registry.components.get(element.componentId)?.resourceFields?.includes('assetId'))continue; candidates.set(id,{id,title:typeof element.data.title==='string'&&element.data.title.trim()?element.data.title:'未命名图片'}); } } setItems([...candidates.values()]);setLoading(false); })().catch(error=>{if(!controller.signal.aborted){setLoading(false);setError(errorText(error));}}); return()=>{controller.abort();if(lifecycle.current===controller)lifecycle.current=null;}; },[engine,host,workspaceId]); async function insert(item:Candidate){ const signal=lifecycle.current?.signal;if(!signal||signal.aborted||requesting.current)return; requesting.current=true; setBusy(item.id);setError(""); try{ const id=crypto.randomUUID(),result=await engine.adoptResource(item.id,(resource,tx)=>{ const document=tx.getDocument(),canvas=document.canvases.get(canvasId),entry=canvas?.parent?document.elements.get(canvas.parent.entryElementId):undefined; if(!canvas||canvas.parent?.entryElementId!==target.canvas?.parent?.entryElementId||target.entry&&(entry?.id!==target.entry.id||entry.incarnation!==target.entry.incarnation))throw fault('asset_target_changed'); const scale=Math.min(260/resource.asset.width,200/resource.asset.height),width=resource.asset.width*scale,height=resource.asset.height*scale; tx.dispatch({type:'element.create',element:{id,incarnation:1,structure:'node',componentId:'image',dataVersion:1,ownerCanvasId:canvasId,geometry:{x:position.x-width/2,y:position.y-height/2,width,height,rotation:0},data:{title:item.title,text:'',assetId:resource.id,src:resource.src}}}); },{signal}); if(result.status==='rejected')throw fault(result.code,result.targets); if(!signal.aborted){onAdded(id);close();} }catch(error){if(!signal.aborted)setError(error instanceof Error&&error.message==='not_found'?'无法读取这张图片,请确认仍有原画布的访问权限。':errorText(error));} finally{requesting.current=false;if(!signal.aborted)setBusy(undefined);} } return {if(!open)dismiss();}}> 从其它画布添加图片选择同一工作区已打开画布中的图片,无需再次上传。 {loading?正在查找图片…:items.length===0?暂无可复用的图片。先打开有已上传图片的画布,再回到这里。:{items.map(item=>void insert(item)} className="flex min-w-0 flex-col items-center gap-2 rounded-lg border border-border p-3 text-sm hover:bg-muted disabled:opacity-50"> {item.title}{busy===item.id&&正在添加…} )}} {error&&{error}} ; }
正在查找图片…
暂无可复用的图片。先打开有已上传图片的画布,再回到这里。
{error}