/** * 新版画布的导入/复制任务显示入口。订阅engine已发布状态,选择文件后启动runtime任务,并展示已加入前缀、等待项与失败原因。 * 面板不保存独立任务事实;暂停/继续/停止由imports实现,停止后已加入内容仍保留,不能把它当成整批回滚。 */ "use client"; import { useCallback, useRef, useState, useSyncExternalStore } from "react"; import { FolderInput } from "lucide-react"; import type { ProjectEngine } from "@/lib/canvas-runtime/workspace"; import { IconButton } from "@/components/ui/icon-button"; import { errorText } from "@/lib/api"; import styles from "./canvas.module.css"; /** 将记录级错误码转成可读说明,未识别的码使用通用提示;原始错误仍保存在任务记录中。 */ const reason = (code: string) => ({ import_asset_authorization_failed: "资源访问授权失败,此记录未加入", import_asset_capacity: "本组资源过大,无法一次准备", asset_depot_unavailable: "资源仓库暂不可用", import_resource_authorization_required: "资源需要重新授权,此记录尚未加入", invalid_document_import: "导出文档结构不合法", invalid_json_array: "JSON 记录数组格式错误", incomplete_json_array: "JSON 数组尾部不完整", json_nesting_limit: "JSON 嵌套层数过多", invalid_json: "记录格式错误", invalid_json_value: "记录包含不支持的数据", invalid_utf8: "文件编码不是 UTF-8", record_too_large: "单条记录过大", unresolved_dependency: "引用的记录未加入", invalid_native_import_record: "不是画布记录", import_component_unavailable: "元素类型暂不支持", invalid_geometry: "元素尺寸不合法", invalid_endpoint: "连线端点已不存在", duplicate_import_source_key: "记录标识重复", import_source_read_failed: "暂时无法读取源文件,可重试;已加入内容保留", import_source_verification_failed: "源文件校验失败,可重试;已加入内容保留", invalid_import_read_proof: "源文件读取证据不一致,任务已暂停", import_source_unavailable: "导入源文件不可用", import_source_changed: "源文件内容与保存时不一致,任务已暂停", import_source_integrity_unavailable: "旧任务缺少源内容校验,无法安全继续;已加入内容保留", import_source_integrity_missing: "源校验记录缺失,任务已暂停", import_source_integrity_interpreter_unavailable: "源校验版本暂不支持", import_target_missing: "目标分区已不存在", import_target_changed: "目标分区已改变", import_dependency_changed: "引用对象已删除或恢复为新版本", dependency_capacity: "等待引用过多,请调整文件顺序", copy_external_changed: "原图引用已删除、移动或恢复为新版本", invalid_duplicate_source: "复制源数据不可用", invalid_copy_snapshot: "复制快照不可用", invalid_copy_preparation: "复制准备记录不可用", copy_interpreter_changed: "复制规则已变化,需要重新复制", invalid_import_source: "文件过大或格式不支持" } as Record)[code] ?? "这条记录暂时无法加入"; /** 只显示目标canvas最近三个任务,倒序展示;任务数量与汇总仅覆盖这个显示窗口,不代表项目全部导入。 */ export function HeadlessImports({ engine, canvasId, viewId }: { engine: ProjectEngine; canvasId: string; viewId:string }) { // React订阅唯一发布根,不在组件里复制任务状态;同一个getSnapshot也作为服务端快照入口。 const subscribe = useCallback((notify: () => void) => engine.editor.subscribe(notify), [engine]); const snapshot = useSyncExternalStore(subscribe, engine.getSnapshot, engine.getSnapshot); const input = useRef(null), [open, setOpen] = useState(false), [error, setError] = useState(""); // 每次渲染更新engine/canvas身份,用于异步文件探测和控制回调完成后避免更新另一画布的错误显示。 const scope = useRef({ engine, canvasId }); scope.current = { engine, canvasId }; const jobs = [...(snapshot.runtime.imports?.values() ?? [])].filter(job => job.source?.targetCanvasId === canvasId).slice(-3).reverse(); /** 统一承接runtime控制Promise的成功/失败;这里只更新面板错误,不自行重排或重启任务。 */ const control = (work: Promise) => { void work.then(() => { if (scope.current.engine === engine && scope.current.canvasId === canvasId) setError(""); }).catch(error => { if (scope.current.engine === engine && scope.current.canvasId === canvasId) setError(errorText(error)); }); }; // canStart决定文件入口可用性;input每次先清value,允许再次选择同一个文件。 // .json读取前64KiB探测首个非空白字符:对象走文档导入,数组走记录数组;其它后缀走NDJSON。 // 探测前捕获canvas父关系及入口生命周期,await后重新核对,防止向已被替换/移走的目标启动任务。 // integrityVersion=1源校验未完成显示校验中;=2允许内容继续编辑,但提示校验完成后同步。 // 任务按钮按runtime状态显示;失败详情最多列前10项,停止提示明确已加入内容继续同步。 return ; }