@lokvis/ui-react 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/LICENSE +21 -0
  2. package/dist/components/AssetPanel.d.ts +2 -2
  3. package/dist/components/AssetPanel.d.ts.map +1 -1
  4. package/dist/components/AssetPanel.js +80 -7
  5. package/dist/components/AssetPanel.js.map +1 -1
  6. package/dist/components/Canvas.d.ts +9 -1
  7. package/dist/components/Canvas.d.ts.map +1 -1
  8. package/dist/components/Canvas.js +41 -3
  9. package/dist/components/Canvas.js.map +1 -1
  10. package/dist/components/CommandPalette.d.ts +48 -0
  11. package/dist/components/CommandPalette.d.ts.map +1 -0
  12. package/dist/components/CommandPalette.js +154 -0
  13. package/dist/components/CommandPalette.js.map +1 -0
  14. package/dist/components/CompareSlider.d.ts +25 -0
  15. package/dist/components/CompareSlider.d.ts.map +1 -0
  16. package/dist/components/CompareSlider.js +106 -0
  17. package/dist/components/CompareSlider.js.map +1 -0
  18. package/dist/components/DownloadPanel.d.ts +20 -0
  19. package/dist/components/DownloadPanel.d.ts.map +1 -0
  20. package/dist/components/DownloadPanel.js +169 -0
  21. package/dist/components/DownloadPanel.js.map +1 -0
  22. package/dist/components/ErrorBanner.d.ts +22 -0
  23. package/dist/components/ErrorBanner.d.ts.map +1 -0
  24. package/dist/components/ErrorBanner.js +47 -0
  25. package/dist/components/ErrorBanner.js.map +1 -0
  26. package/dist/components/ExifPanel.d.ts +26 -0
  27. package/dist/components/ExifPanel.d.ts.map +1 -0
  28. package/dist/components/ExifPanel.js +104 -0
  29. package/dist/components/ExifPanel.js.map +1 -0
  30. package/dist/components/GlobalDropzone.d.ts +37 -0
  31. package/dist/components/GlobalDropzone.d.ts.map +1 -0
  32. package/dist/components/GlobalDropzone.js +154 -0
  33. package/dist/components/GlobalDropzone.js.map +1 -0
  34. package/dist/components/HistoryPanel.d.ts +17 -7
  35. package/dist/components/HistoryPanel.d.ts.map +1 -1
  36. package/dist/components/HistoryPanel.js +74 -62
  37. package/dist/components/HistoryPanel.js.map +1 -1
  38. package/dist/components/Inspector.d.ts.map +1 -1
  39. package/dist/components/Inspector.js +3 -1
  40. package/dist/components/Inspector.js.map +1 -1
  41. package/dist/components/PipelineBar.d.ts.map +1 -1
  42. package/dist/components/PipelineBar.js +4 -3
  43. package/dist/components/PipelineBar.js.map +1 -1
  44. package/dist/components/ProgressBar.d.ts +18 -0
  45. package/dist/components/ProgressBar.d.ts.map +1 -0
  46. package/dist/components/ProgressBar.js +35 -0
  47. package/dist/components/ProgressBar.js.map +1 -0
  48. package/dist/components/StatusBar.d.ts +8 -2
  49. package/dist/components/StatusBar.d.ts.map +1 -1
  50. package/dist/components/StatusBar.js +59 -4
  51. package/dist/components/StatusBar.js.map +1 -1
  52. package/dist/components/ThemeToggle.d.ts +20 -0
  53. package/dist/components/ThemeToggle.d.ts.map +1 -0
  54. package/dist/components/ThemeToggle.js +60 -0
  55. package/dist/components/ThemeToggle.js.map +1 -0
  56. package/dist/components/WorkflowEditor.d.ts +24 -0
  57. package/dist/components/WorkflowEditor.d.ts.map +1 -0
  58. package/dist/components/WorkflowEditor.js +160 -0
  59. package/dist/components/WorkflowEditor.js.map +1 -0
  60. package/dist/components/WorkflowTemplates.d.ts +23 -0
  61. package/dist/components/WorkflowTemplates.d.ts.map +1 -0
  62. package/dist/components/WorkflowTemplates.js +32 -0
  63. package/dist/components/WorkflowTemplates.js.map +1 -0
  64. package/dist/components/Workspace.d.ts +48 -2
  65. package/dist/components/Workspace.d.ts.map +1 -1
  66. package/dist/components/Workspace.js +80 -5
  67. package/dist/components/Workspace.js.map +1 -1
  68. package/dist/data/workflow-templates.d.ts +42 -0
  69. package/dist/data/workflow-templates.d.ts.map +1 -0
  70. package/dist/data/workflow-templates.js +112 -0
  71. package/dist/data/workflow-templates.js.map +1 -0
  72. package/dist/hooks/useDebouncedRun.d.ts +49 -0
  73. package/dist/hooks/useDebouncedRun.d.ts.map +1 -0
  74. package/dist/hooks/useDebouncedRun.js +117 -0
  75. package/dist/hooks/useDebouncedRun.js.map +1 -0
  76. package/dist/hooks/useLokvis.js +1 -1
  77. package/dist/hooks/useMediaQuery.d.ts +27 -0
  78. package/dist/hooks/useMediaQuery.d.ts.map +1 -0
  79. package/dist/hooks/useMediaQuery.js +41 -0
  80. package/dist/hooks/useMediaQuery.js.map +1 -0
  81. package/dist/hooks/useShareLink.d.ts +58 -0
  82. package/dist/hooks/useShareLink.d.ts.map +1 -0
  83. package/dist/hooks/useShareLink.js +120 -0
  84. package/dist/hooks/useShareLink.js.map +1 -0
  85. package/dist/hooks/useTheme.d.ts +30 -0
  86. package/dist/hooks/useTheme.d.ts.map +1 -0
  87. package/dist/hooks/useTheme.js +105 -0
  88. package/dist/hooks/useTheme.js.map +1 -0
  89. package/dist/hooks/useWorkflowProgress.d.ts +28 -0
  90. package/dist/hooks/useWorkflowProgress.d.ts.map +1 -0
  91. package/dist/hooks/useWorkflowProgress.js +25 -0
  92. package/dist/hooks/useWorkflowProgress.js.map +1 -0
  93. package/dist/hooks/useWorkflows.d.ts +86 -0
  94. package/dist/hooks/useWorkflows.d.ts.map +1 -0
  95. package/dist/hooks/useWorkflows.js +247 -0
  96. package/dist/hooks/useWorkflows.js.map +1 -0
  97. package/dist/index.d.ts +26 -0
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +26 -0
  100. package/dist/index.js.map +1 -1
  101. package/dist/store/assets-slice.d.ts.map +1 -1
  102. package/dist/store/assets-slice.js +19 -0
  103. package/dist/store/assets-slice.js.map +1 -1
  104. package/dist/store/history-slice.d.ts +18 -0
  105. package/dist/store/history-slice.d.ts.map +1 -0
  106. package/dist/store/history-slice.js +42 -0
  107. package/dist/store/history-slice.js.map +1 -0
  108. package/dist/store/index.d.ts +1 -0
  109. package/dist/store/index.d.ts.map +1 -1
  110. package/dist/store/index.js +3 -0
  111. package/dist/store/index.js.map +1 -1
  112. package/dist/store/runtime-slice.d.ts +10 -2
  113. package/dist/store/runtime-slice.d.ts.map +1 -1
  114. package/dist/store/runtime-slice.js +77 -28
  115. package/dist/store/runtime-slice.js.map +1 -1
  116. package/dist/store/types.d.ts +70 -1
  117. package/dist/store/types.d.ts.map +1 -1
  118. package/dist/store/types.js +10 -0
  119. package/dist/store/types.js.map +1 -1
  120. package/dist/store/workflow-slice.d.ts +1 -1
  121. package/dist/store/workflow-slice.d.ts.map +1 -1
  122. package/dist/store/workflow-slice.js +167 -20
  123. package/dist/store/workflow-slice.js.map +1 -1
  124. package/dist/store.d.ts +76 -0
  125. package/dist/store.d.ts.map +1 -0
  126. package/dist/store.js +217 -0
  127. package/dist/store.js.map +1 -0
  128. package/package.json +9 -6
  129. package/src/components/AssetPanel.tsx +154 -12
  130. package/src/components/Canvas.tsx +85 -7
  131. package/src/components/CommandPalette.tsx +300 -0
  132. package/src/components/CompareSlider.tsx +186 -0
  133. package/src/components/DownloadPanel.tsx +252 -0
  134. package/src/components/ErrorBanner.tsx +95 -0
  135. package/src/components/ExifPanel.tsx +187 -0
  136. package/src/components/GlobalDropzone.tsx +220 -0
  137. package/src/components/HistoryPanel.tsx +231 -104
  138. package/src/components/Inspector.tsx +7 -2
  139. package/src/components/PipelineBar.tsx +13 -12
  140. package/src/components/ProgressBar.tsx +104 -0
  141. package/src/components/StatusBar.tsx +124 -5
  142. package/src/components/ThemeToggle.tsx +122 -0
  143. package/src/components/WorkflowEditor.tsx +382 -0
  144. package/src/components/WorkflowTemplates.tsx +101 -0
  145. package/src/components/Workspace.tsx +207 -9
  146. package/src/data/workflow-templates.ts +137 -0
  147. package/src/hooks/useDebouncedRun.ts +151 -0
  148. package/src/hooks/useLokvis.ts +1 -1
  149. package/src/hooks/useMediaQuery.ts +53 -0
  150. package/src/hooks/useShareLink.ts +158 -0
  151. package/src/hooks/useTheme.ts +116 -0
  152. package/src/hooks/useWorkflowProgress.ts +41 -0
  153. package/src/hooks/useWorkflows.ts +326 -0
  154. package/src/index.ts +32 -0
  155. package/src/store/assets-slice.ts +19 -0
  156. package/src/store/history-slice.ts +65 -0
  157. package/src/store/index.ts +3 -0
  158. package/src/store/runtime-slice.ts +89 -32
  159. package/src/store/types.ts +71 -1
  160. package/src/store/workflow-slice.ts +178 -20
@@ -0,0 +1,25 @@
1
+ /**
2
+ * CompareSlider - before/after 对比滑块(W9.4)
3
+ *
4
+ * 把输入资产(原)与输出资产(处理后)叠加展示,通过可拖拽的分隔条左右揭示
5
+ * before / after 两张图。用于直观展示工作流处理效果。
6
+ *
7
+ * 交互:
8
+ * - 鼠标拖拽分隔条(或点击容器任意位置)调整揭示比例
9
+ * - 触摸:touchmove 同步
10
+ * - 键盘:← → 调整 5% 步长(聚焦分隔条时)
11
+ *
12
+ * 数据来源:
13
+ * - before: `store.selectedAssetId` 对应的 thumbnails(由 AssetPanel 自动生成)
14
+ * - after: `store.selectedOutputId` 对应的 thumbnails
15
+ * - 任一缺失则不渲染(返回 null)
16
+ *
17
+ * 注:本组件只负责"展示对比",不负责生成缩略图。缩略图由 AssetPanel
18
+ * 的 effect 统一生成并存入 store.thumbnails,本组件按 assetId 取用。
19
+ */
20
+ import * as React from 'react';
21
+ export interface CompareSliderProps {
22
+ className?: string;
23
+ }
24
+ export declare function CompareSlider({ className }: CompareSliderProps): React.JSX.Element | null;
25
+ //# sourceMappingURL=CompareSlider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CompareSlider.d.ts","sourceRoot":"","sources":["../../src/components/CompareSlider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,aAAa,CAAC,EAAE,SAAc,EAAE,EAAE,kBAAkB,4BA6JnE"}
@@ -0,0 +1,106 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * CompareSlider - before/after 对比滑块(W9.4)
4
+ *
5
+ * 把输入资产(原)与输出资产(处理后)叠加展示,通过可拖拽的分隔条左右揭示
6
+ * before / after 两张图。用于直观展示工作流处理效果。
7
+ *
8
+ * 交互:
9
+ * - 鼠标拖拽分隔条(或点击容器任意位置)调整揭示比例
10
+ * - 触摸:touchmove 同步
11
+ * - 键盘:← → 调整 5% 步长(聚焦分隔条时)
12
+ *
13
+ * 数据来源:
14
+ * - before: `store.selectedAssetId` 对应的 thumbnails(由 AssetPanel 自动生成)
15
+ * - after: `store.selectedOutputId` 对应的 thumbnails
16
+ * - 任一缺失则不渲染(返回 null)
17
+ *
18
+ * 注:本组件只负责"展示对比",不负责生成缩略图。缩略图由 AssetPanel
19
+ * 的 effect 统一生成并存入 store.thumbnails,本组件按 assetId 取用。
20
+ */
21
+ import * as React from 'react';
22
+ import { Icon } from '@lokvis/ui-core';
23
+ import { useWorkspaceStore } from '../store/index.js';
24
+ export function CompareSlider({ className = '' }) {
25
+ const assets = useWorkspaceStore((s) => s.assets);
26
+ const thumbnails = useWorkspaceStore((s) => s.thumbnails);
27
+ const selectedAssetId = useWorkspaceStore((s) => s.selectedAssetId);
28
+ const selectedOutputId = useWorkspaceStore((s) => s.selectedOutputId);
29
+ const lastOutputIds = useWorkspaceStore((s) => s.lastOutputIds);
30
+ // before:用户选中的输入资产;after:用户选中的输出资产(默认第一个)
31
+ const beforeId = selectedAssetId;
32
+ const afterId = selectedOutputId ?? lastOutputIds[0] ?? null;
33
+ const beforeAsset = beforeId ? assets.find((a) => a.id === beforeId) : null;
34
+ const afterAsset = afterId ? assets.find((a) => a.id === afterId) : null;
35
+ const beforeUrl = beforeId ? thumbnails[beforeId] : undefined;
36
+ const afterUrl = afterId ? thumbnails[afterId] : undefined;
37
+ const containerRef = React.useRef(null);
38
+ const [pos, setPos] = React.useState(50); // 0-100,分隔条位置百分比
39
+ const [dragging, setDragging] = React.useState(false);
40
+ // 切换资产时重置位置到中央
41
+ React.useEffect(() => {
42
+ setPos(50);
43
+ }, [beforeId, afterId]);
44
+ const updatePos = React.useCallback((clientX) => {
45
+ const el = containerRef.current;
46
+ if (!el)
47
+ return;
48
+ const rect = el.getBoundingClientRect();
49
+ const ratio = (clientX - rect.left) / rect.width;
50
+ setPos(Math.max(0, Math.min(100, ratio * 100)));
51
+ }, []);
52
+ // 鼠标拖拽
53
+ const handleMouseDown = (e) => {
54
+ e.preventDefault();
55
+ setDragging(true);
56
+ updatePos(e.clientX);
57
+ };
58
+ React.useEffect(() => {
59
+ if (!dragging)
60
+ return;
61
+ const onMove = (e) => updatePos(e.clientX);
62
+ const onUp = () => setDragging(false);
63
+ document.addEventListener('mousemove', onMove);
64
+ document.addEventListener('mouseup', onUp);
65
+ return () => {
66
+ document.removeEventListener('mousemove', onMove);
67
+ document.removeEventListener('mouseup', onUp);
68
+ };
69
+ }, [dragging, updatePos]);
70
+ // 触摸拖拽
71
+ const handleTouchStart = (e) => {
72
+ setDragging(true);
73
+ if (e.touches[0])
74
+ updatePos(e.touches[0].clientX);
75
+ };
76
+ React.useEffect(() => {
77
+ if (!dragging)
78
+ return;
79
+ const onMove = (e) => {
80
+ if (e.touches[0])
81
+ updatePos(e.touches[0].clientX);
82
+ };
83
+ const onEnd = () => setDragging(false);
84
+ document.addEventListener('touchmove', onMove, { passive: true });
85
+ document.addEventListener('touchend', onEnd);
86
+ return () => {
87
+ document.removeEventListener('touchmove', onMove);
88
+ document.removeEventListener('touchend', onEnd);
89
+ };
90
+ }, [dragging, updatePos]);
91
+ // 键盘:← → 调整 5%
92
+ const handleKeyDown = (e) => {
93
+ if (e.key === 'ArrowLeft') {
94
+ e.preventDefault();
95
+ setPos((p) => Math.max(0, p - 5));
96
+ }
97
+ else if (e.key === 'ArrowRight') {
98
+ e.preventDefault();
99
+ setPos((p) => Math.min(100, p + 5));
100
+ }
101
+ };
102
+ if (!beforeUrl || !afterUrl)
103
+ return null;
104
+ return (_jsxs("div", { ref: containerRef, className: `relative select-none overflow-hidden rounded-lg ring-1 ring-black/5 dark:ring-black/30 ${className}`, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, children: [_jsx("img", { src: afterUrl, alt: "After", draggable: false, className: "block max-h-full max-w-full object-contain pointer-events-none" }), _jsx("div", { className: "absolute inset-0", style: { clipPath: `inset(0 ${100 - pos}% 0 0)` }, children: _jsx("img", { src: beforeUrl, alt: "Before", draggable: false, className: "block max-h-full max-w-full object-contain pointer-events-none" }) }), _jsx("span", { className: "absolute top-2 left-2 rounded bg-black/60 px-1.5 py-0.5 text-[10px] font-medium text-white", children: "Before" }), _jsx("span", { className: "absolute top-2 right-2 rounded bg-black/60 px-1.5 py-0.5 text-[10px] font-medium text-white", children: "After" }), _jsxs("div", { className: "absolute top-0 bottom-0 z-10 cursor-ew-resize", style: { left: `${pos}%`, transform: 'translateX(-50%)' }, role: "separator", "aria-valuenow": Math.round(pos), "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": "Comparison slider position", tabIndex: 0, onKeyDown: handleKeyDown, children: [_jsx("div", { className: "absolute inset-y-0 left-1/2 w-0.5 -translate-x-1/2 bg-white shadow-md" }), _jsx("div", { className: "absolute top-1/2 left-1/2 flex h-8 w-8 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-white shadow-lg ring-1 ring-black/10", children: _jsx(Icon, { size: 14, className: "text-zinc-600", children: _jsx("path", { d: "M9 5l-5 7 5 7M15 5l5 7-5 7" }) }) })] }), (beforeAsset || afterAsset) && (_jsxs("div", { className: "absolute bottom-2 inset-x-2 flex justify-between text-[10px] text-white", children: [beforeAsset?.metadata.dimensions && (_jsxs("span", { className: "rounded bg-black/60 px-1.5 py-0.5", children: [beforeAsset.metadata.dimensions.width, "\u00D7", beforeAsset.metadata.dimensions.height] })), afterAsset?.metadata.dimensions && (_jsxs("span", { className: "rounded bg-black/60 px-1.5 py-0.5", children: [afterAsset.metadata.dimensions.width, "\u00D7", afterAsset.metadata.dimensions.height] }))] }))] }));
105
+ }
106
+ //# sourceMappingURL=CompareSlider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CompareSlider.js","sourceRoot":"","sources":["../../src/components/CompareSlider.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * DownloadPanel - 工作流输出下载面板(W9.5)
3
+ *
4
+ * 展示上次工作流执行的输出资产,提供:
5
+ * - 单项下载(从 runtime.exportAsset 取 blob,触发浏览器下载)
6
+ * - 全部下载(逐项触发,BATCH_DOWNLOAD_DELAY_MS 间隔,避免浏览器拦截;长期方案见该常量注释)
7
+ * - 清空输出列表
8
+ *
9
+ * 与 apps/playground 的 DownloadManager(独立工具页)不同:本组件是 Workspace
10
+ * 内嵌的轻量面板,数据来自 store.lastOutputIds(由 workflow-slice.run 写入)。
11
+ *
12
+ * 下载命名:`lokvis-output-{assetId前8位}.{ext}`,简洁且避免覆盖。
13
+ * 不引入 JSZip(需新增依赖),用浏览器原生 downloadBlob 实现。
14
+ */
15
+ import * as React from 'react';
16
+ export interface DownloadPanelProps {
17
+ className?: string;
18
+ }
19
+ export declare function DownloadPanel({ className }: DownloadPanelProps): React.JSX.Element | null;
20
+ //# sourceMappingURL=DownloadPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DownloadPanel.d.ts","sourceRoot":"","sources":["../../src/components/DownloadPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4CD,wBAAgB,aAAa,CAAC,EAAE,SAAc,EAAE,EAAE,kBAAkB,4BA0LnE"}
@@ -0,0 +1,169 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * DownloadPanel - 工作流输出下载面板(W9.5)
4
+ *
5
+ * 展示上次工作流执行的输出资产,提供:
6
+ * - 单项下载(从 runtime.exportAsset 取 blob,触发浏览器下载)
7
+ * - 全部下载(逐项触发,BATCH_DOWNLOAD_DELAY_MS 间隔,避免浏览器拦截;长期方案见该常量注释)
8
+ * - 清空输出列表
9
+ *
10
+ * 与 apps/playground 的 DownloadManager(独立工具页)不同:本组件是 Workspace
11
+ * 内嵌的轻量面板,数据来自 store.lastOutputIds(由 workflow-slice.run 写入)。
12
+ *
13
+ * 下载命名:`lokvis-output-{assetId前8位}.{ext}`,简洁且避免覆盖。
14
+ * 不引入 JSZip(需新增依赖),用浏览器原生 downloadBlob 实现。
15
+ */
16
+ import * as React from 'react';
17
+ import { Icon } from '@lokvis/ui-core';
18
+ import { useWorkspaceStore } from '../store/index.js';
19
+ /** 格式化字节数 */
20
+ function formatBytes(bytes) {
21
+ if (bytes < 1024)
22
+ return `${bytes} B`;
23
+ if (bytes < 1024 * 1024)
24
+ return `${(bytes / 1024).toFixed(1)} KB`;
25
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
26
+ }
27
+ /**
28
+ * 批量下载时每项之间的延迟毫秒数。
29
+ *
30
+ * 短期方案:浏览器(尤其 Chrome)会拦截同一 tick 内触发的多个 programmatic
31
+ * `<a download>` 点击,把第一个之后的视为"非用户手势"弹窗拦截。两次下载之间
32
+ * 插入小延迟可绕过该启发式判定。该值是经验值,不是规范要求 —— 不同浏览器
33
+ * 版本行为可能变化。
34
+ *
35
+ * 长期方案:用 JSZip 将多个输出打包成单个 ZIP 下载(只需一次用户手势)。
36
+ * 本组件暂未引入 JSZip 依赖(见文件头注释),待 M2 引入 archive capability 后
37
+ * 一并迁移,届时可移除此常量与 sleep 调用。
38
+ */
39
+ const BATCH_DOWNLOAD_DELAY_MS = 200;
40
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
41
+ /** 从扩展名或 mime 推断下载扩展名 */
42
+ function extFromMime(mime) {
43
+ const sub = mime.split('/')[1] ?? 'bin';
44
+ // image/svg+xml → svg+xml → svg(取 + 前部分)
45
+ return sub.split('+')[0] ?? sub;
46
+ }
47
+ /** 触发浏览器下载 */
48
+ function downloadBlob(blob, filename) {
49
+ const url = URL.createObjectURL(blob);
50
+ const a = document.createElement('a');
51
+ a.href = url;
52
+ a.download = filename;
53
+ document.body.appendChild(a);
54
+ a.click();
55
+ document.body.removeChild(a);
56
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
57
+ }
58
+ export function DownloadPanel({ className = '' }) {
59
+ const assets = useWorkspaceStore((s) => s.assets);
60
+ const lastOutputIds = useWorkspaceStore((s) => s.lastOutputIds);
61
+ const runtime = useWorkspaceStore((s) => s.runtime);
62
+ const clearOutputs = useWorkspaceStore((s) => s.clearOutputs);
63
+ const setStatus = useWorkspaceStore((s) => s.setStatus);
64
+ const [downloaded, setDownloaded] = React.useState(new Set());
65
+ const [batchDownloading, setBatchDownloading] = React.useState(false);
66
+ const [error, setError] = React.useState(null);
67
+ // 卸载标志:批量下载是异步循环,卸载后不应再 setState
68
+ const mountedRef = React.useRef(true);
69
+ React.useEffect(() => {
70
+ mountedRef.current = true;
71
+ return () => {
72
+ mountedRef.current = false;
73
+ };
74
+ }, []);
75
+ // 输出资产列表(从 store.assets 中按 lastOutputIds 顺序取)
76
+ const outputAssets = React.useMemo(() => {
77
+ return lastOutputIds
78
+ .map((id) => assets.find((a) => a.id === id))
79
+ .filter((a) => a !== undefined);
80
+ }, [lastOutputIds, assets]);
81
+ // outputs 变化时清空 downloaded 状态
82
+ React.useEffect(() => {
83
+ setDownloaded(new Set());
84
+ setError(null);
85
+ }, [lastOutputIds]);
86
+ const totalSize = outputAssets.reduce((sum, a) => sum + a.metadata.size, 0);
87
+ const downloadedCount = outputAssets.filter((a) => downloaded.has(a.id)).length;
88
+ const allDownloaded = outputAssets.length > 0 && downloadedCount === outputAssets.length;
89
+ async function handleDownloadOne(id) {
90
+ if (!runtime)
91
+ return;
92
+ const asset = outputAssets.find((a) => a.id === id);
93
+ if (!asset)
94
+ return;
95
+ try {
96
+ const blob = await runtime.exportAsset(id);
97
+ const ext = extFromMime(asset.metadata.mimeType);
98
+ const filename = `lokvis-output-${asset.id.slice(0, 8)}.${ext}`;
99
+ downloadBlob(blob, filename);
100
+ if (mountedRef.current) {
101
+ setDownloaded((prev) => new Set(prev).add(id));
102
+ }
103
+ }
104
+ catch (err) {
105
+ if (mountedRef.current) {
106
+ setError(err instanceof Error ? err.message : String(err));
107
+ }
108
+ }
109
+ }
110
+ async function handleDownloadAll() {
111
+ if (!runtime || batchDownloading)
112
+ return;
113
+ setBatchDownloading(true);
114
+ setError(null);
115
+ setStatus('Downloading outputs...');
116
+ // 用本地 Set 跟踪本次循环已下载 id,避免 downloaded 闭包快照与 state 不同步
117
+ // (review 反馈:循环中 setDownloaded 更新 state 但闭包 downloaded 不变,
118
+ // 虽有 batchDownloading guard 阻止并发,逻辑上仍应读取最新已下载集合)
119
+ const localDownloaded = new Set(downloaded);
120
+ try {
121
+ for (const asset of outputAssets) {
122
+ // 卸载后立即停止循环(避免对已卸载组件 setState)
123
+ if (!mountedRef.current)
124
+ return;
125
+ if (localDownloaded.has(asset.id))
126
+ continue;
127
+ try {
128
+ const blob = await runtime.exportAsset(asset.id);
129
+ const ext = extFromMime(asset.metadata.mimeType);
130
+ const filename = `lokvis-output-${asset.id.slice(0, 8)}.${ext}`;
131
+ downloadBlob(blob, filename);
132
+ localDownloaded.add(asset.id);
133
+ if (mountedRef.current) {
134
+ setDownloaded((prev) => new Set(prev).add(asset.id));
135
+ }
136
+ await sleep(BATCH_DOWNLOAD_DELAY_MS);
137
+ }
138
+ catch (err) {
139
+ if (mountedRef.current) {
140
+ setError(err instanceof Error ? err.message : String(err));
141
+ }
142
+ // 单项失败不阻断后续
143
+ }
144
+ }
145
+ if (mountedRef.current) {
146
+ setStatus('Downloads complete');
147
+ }
148
+ }
149
+ finally {
150
+ if (mountedRef.current) {
151
+ setBatchDownloading(false);
152
+ }
153
+ }
154
+ }
155
+ function handleClear() {
156
+ clearOutputs();
157
+ setDownloaded(new Set());
158
+ }
159
+ if (outputAssets.length === 0)
160
+ return null;
161
+ return (_jsxs("div", { className: `flex flex-col border-t border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900 ${className}`, "aria-label": "Outputs panel", children: [_jsxs("div", { className: "flex items-center justify-between px-3 h-9 shrink-0 border-b border-zinc-200 dark:border-zinc-800", children: [_jsx("span", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-400", children: "Outputs" }), _jsxs("span", { className: "text-[11px] tabular-nums text-zinc-400", children: [downloadedCount, "/", outputAssets.length, " \u00B7 ", formatBytes(totalSize)] })] }), _jsxs("div", { className: "flex items-center gap-2 px-3 py-2 overflow-x-auto", children: [outputAssets.map((asset) => {
162
+ const isDownloaded = downloaded.has(asset.id);
163
+ const dims = asset.metadata.dimensions;
164
+ return (_jsxs("div", { className: "flex shrink-0 flex-col gap-1 rounded-md border border-zinc-200 p-2 dark:border-zinc-700", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "rounded bg-zinc-100 px-1 py-0.5 text-[9px] font-medium text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400", children: asset.metadata.format.toUpperCase() }), dims && (_jsxs("span", { className: "text-[10px] text-zinc-500 dark:text-zinc-400 tabular-nums", children: [dims.width, "\u00D7", dims.height] })), _jsx("span", { className: "text-[10px] text-zinc-400 tabular-nums", children: formatBytes(asset.metadata.size) })] }), _jsx("div", { className: "flex items-center gap-1", children: _jsxs("button", { type: "button", onClick: () => void handleDownloadOne(asset.id), className: `flex items-center gap-1 rounded px-2 py-0.5 text-[10px] font-medium transition-colors ${isDownloaded
165
+ ? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-950/30 dark:text-emerald-400'
166
+ : 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-950/30 dark:text-indigo-300 dark:hover:bg-indigo-900/40'}`, children: [_jsx(Icon, { size: 10, strokeWidth: 2, children: _jsx("path", { d: "M12 3v12m0 0l-4-4m4 4l4-4M3 17v2a2 2 0 002 2h14a2 2 0 002-2v-2" }) }), isDownloaded ? 'Downloaded' : 'Download'] }) })] }, asset.id));
167
+ }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => void handleDownloadAll(), disabled: batchDownloading || allDownloaded, className: "rounded-md bg-indigo-600 px-2.5 py-1 text-[10px] font-medium text-white hover:bg-indigo-500 disabled:cursor-not-allowed disabled:opacity-50", children: batchDownloading ? 'Downloading...' : 'Download All' }), _jsx("button", { type: "button", onClick: handleClear, className: "rounded-md px-2 py-1 text-[10px] text-zinc-500 hover:bg-zinc-100 hover:text-zinc-700 dark:hover:bg-zinc-800 dark:hover:text-zinc-300", children: "Clear" })] })] }), error && (_jsx("p", { className: "px-3 pb-2 text-[10px] text-red-500", role: "alert", children: error }))] }));
168
+ }
169
+ //# sourceMappingURL=DownloadPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DownloadPanel.js","sourceRoot":"","sources":["../../src/components/DownloadPanel.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAMtD,aAAa;AACb,SAAS,WAAW,CAAC,KAAa;IAChC,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,IAAI,CAAC;IACtC,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAClE,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAEpC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAEnF,yBAAyB;AACzB,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC;IACxC,yCAAyC;IACzC,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;AAClC,CAAC;AAED,cAAc;AACd,SAAS,YAAY,CAAC,IAAU,EAAE,QAAgB;IAChD,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC;IACb,CAAC,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC,CAAC,KAAK,EAAE,CAAC;IACV,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAC7B,UAAU,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,EAAE,SAAS,GAAG,EAAE,EAAsB;IAClE,MAAM,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,aAAa,GAAG,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAChE,MAAM,OAAO,GAAG,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACpD,MAAM,YAAY,GAAG,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAExD,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAc,IAAI,GAAG,EAAE,CAAC,CAAC;IAC3E,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAE9D,iCAAiC;IACjC,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1B,OAAO,GAAG,EAAE;YACV,UAAU,CAAC,OAAO,GAAG,KAAK,CAAC;QAC7B,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,8CAA8C;IAC9C,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QACtC,OAAO,aAAa;aACjB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;aAC5C,MAAM,CAAC,CAAC,CAAC,EAA8B,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IAChE,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;IAE5B,8BAA8B;IAC9B,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,aAAa,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;QACzB,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAEpB,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC5E,MAAM,eAAe,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IAChF,MAAM,aAAa,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,IAAI,eAAe,KAAK,YAAY,CAAC,MAAM,CAAC;IAEzF,KAAK,UAAU,iBAAiB,CAAC,EAAU;QACzC,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAC3C,MAAM,GAAG,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACjD,MAAM,QAAQ,GAAG,iBAAiB,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;YAChE,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAC7B,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,aAAa,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,UAAU,iBAAiB;QAC9B,IAAI,CAAC,OAAO,IAAI,gBAAgB;YAAE,OAAO;QACzC,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC1B,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,SAAS,CAAC,wBAAwB,CAAC,CAAC;QACpC,qDAAqD;QACrD,2DAA2D;QAC3D,kDAAkD;QAClD,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,CAAC;YACH,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;gBACjC,+BAA+B;gBAC/B,IAAI,CAAC,UAAU,CAAC,OAAO;oBAAE,OAAO;gBAChC,IAAI,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;oBAAE,SAAS;gBAC5C,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;oBACjD,MAAM,GAAG,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;oBACjD,MAAM,QAAQ,GAAG,iBAAiB,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;oBAChE,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;oBAC7B,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;oBAC9B,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;wBACvB,aAAa,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;oBACvD,CAAC;oBACD,MAAM,KAAK,CAAC,uBAAuB,CAAC,CAAC;gBACvC,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;wBACvB,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;oBAC7D,CAAC;oBACD,YAAY;gBACd,CAAC;YACH,CAAC;YACD,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,SAAS,CAAC,oBAAoB,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,mBAAmB,CAAC,KAAK,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;IAED,SAAS,WAAW;QAClB,YAAY,EAAE,CAAC;QACf,aAAa,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,OAAO,CACL,eACE,SAAS,EAAE,yFAAyF,SAAS,EAAE,gBACpG,eAAe,aAE1B,eAAK,SAAS,EAAC,mGAAmG,aAChH,eAAM,SAAS,EAAC,kEAAkE,wBAE3E,EACP,gBAAM,SAAS,EAAC,wCAAwC,aACrD,eAAe,OAAG,YAAY,CAAC,MAAM,cAAK,WAAW,CAAC,SAAS,CAAC,IAC5D,IACH,EAEN,eAAK,SAAS,EAAC,mDAAmD,aAC/D,YAAY,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;wBAC1B,MAAM,YAAY,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;wBAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC;wBACvC,OAAO,CACL,eAEE,SAAS,EAAC,yFAAyF,aAEnG,eAAK,SAAS,EAAC,yBAAyB,aACtC,eAAM,SAAS,EAAC,0GAA0G,YACvH,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,GAC/B,EACN,IAAI,IAAI,CACP,gBAAM,SAAS,EAAC,2DAA2D,aACxE,IAAI,CAAC,KAAK,YAAG,IAAI,CAAC,MAAM,IACpB,CACR,EACD,eAAM,SAAS,EAAC,wCAAwC,YACrD,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,GAC5B,IACH,EACN,cAAK,SAAS,EAAC,yBAAyB,YACtC,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC,EAC/C,SAAS,EAAE,yFACT,YAAY;4CACV,CAAC,CAAC,8EAA8E;4CAChF,CAAC,CAAC,0HACN,EAAE,aAEF,KAAC,IAAI,IAAC,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,YAC5B,eAAM,CAAC,EAAC,gEAAgE,GAAG,GACtE,EACN,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,IAClC,GACL,KA/BD,KAAK,CAAC,EAAE,CAgCT,CACP,CAAC;oBACJ,CAAC,CAAC,EAEF,eAAK,SAAS,EAAC,kCAAkC,aAC/C,iBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,iBAAiB,EAAE,EACvC,QAAQ,EAAE,gBAAgB,IAAI,aAAa,EAC3C,SAAS,EAAC,6IAA6I,YAEtJ,gBAAgB,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,cAAc,GAC9C,EACT,iBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,WAAW,EACpB,SAAS,EAAC,sIAAsI,sBAGzI,IACL,IACF,EAEL,KAAK,IAAI,CACR,YAAG,SAAS,EAAC,oCAAoC,EAAC,IAAI,EAAC,OAAO,YAC3D,KAAK,GACJ,CACL,IACG,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * ErrorBanner - 错误信息横幅(W11.3)
3
+ *
4
+ * 当 store.error 非空时显示,展示错误信息并提供:
5
+ * - 关闭按钮(清除 error)
6
+ * - 重试按钮(重新执行工作流)
7
+ *
8
+ * 节点失败高亮由 WorkflowEditor 的节点状态颜色处理(已实现);
9
+ * 本组件负责全局错误信息的可见性。
10
+ *
11
+ * 显式错误计数器作为可见性派生依据:
12
+ * store.error 是字符串,相同错误多次设置时引用不变,useEffect 依赖 [error] 不会触发。
13
+ * 改为依赖 errorSeq(error 出现的序号),每次 setError(非 null)都递增,确保 banner 重新弹出。
14
+ *
15
+ * @module ErrorBanner
16
+ */
17
+ import * as React from 'react';
18
+ export interface ErrorBannerProps {
19
+ className?: string;
20
+ }
21
+ export declare function ErrorBanner({ className }: ErrorBannerProps): React.JSX.Element | null;
22
+ //# sourceMappingURL=ErrorBanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBanner.d.ts","sourceRoot":"","sources":["../../src/components/ErrorBanner.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,WAAW,CAAC,EAAE,SAAc,EAAE,EAAE,gBAAgB,4BAqE/D"}
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * ErrorBanner - 错误信息横幅(W11.3)
4
+ *
5
+ * 当 store.error 非空时显示,展示错误信息并提供:
6
+ * - 关闭按钮(清除 error)
7
+ * - 重试按钮(重新执行工作流)
8
+ *
9
+ * 节点失败高亮由 WorkflowEditor 的节点状态颜色处理(已实现);
10
+ * 本组件负责全局错误信息的可见性。
11
+ *
12
+ * 显式错误计数器作为可见性派生依据:
13
+ * store.error 是字符串,相同错误多次设置时引用不变,useEffect 依赖 [error] 不会触发。
14
+ * 改为依赖 errorSeq(error 出现的序号),每次 setError(非 null)都递增,确保 banner 重新弹出。
15
+ *
16
+ * @module ErrorBanner
17
+ */
18
+ import * as React from 'react';
19
+ import { Icon } from '@lokvis/ui-core';
20
+ import { useWorkspaceStore } from '../store/index.js';
21
+ export function ErrorBanner({ className = '' }) {
22
+ const error = useWorkspaceStore((s) => s.error);
23
+ const errorSeq = useWorkspaceStore((s) => s.errorSeq);
24
+ const setError = useWorkspaceStore((s) => s.setError);
25
+ const run = useWorkspaceStore((s) => s.run);
26
+ const nodes = useWorkspaceStore((s) => s.nodes);
27
+ const running = useWorkspaceStore((s) => s.running);
28
+ // 用户关闭后隐藏;新错误(errorSeq 变化)时重新显示。
29
+ // 用 errorSeq 而非 error 字符串本身:store.run() 在 setError(null) 之后才设新 error,
30
+ // errorSeq 在 setError(非 null) 时递增,即使错误消息相同也能感知到"新的错误事件"。
31
+ const [dismissedSeq, setDismissedSeq] = React.useState(null);
32
+ const visible = error !== null && dismissedSeq !== errorSeq;
33
+ const handleDismiss = React.useCallback(() => {
34
+ setDismissedSeq(errorSeq);
35
+ setError(null);
36
+ }, [errorSeq, setError]);
37
+ const handleRetry = React.useCallback(() => {
38
+ // 先清 error 再 run,避免 run 同步抛错时 banner 已隐藏且 errorSeq 未变化
39
+ setError(null);
40
+ void run();
41
+ }, [setError, run]);
42
+ if (!visible || !error)
43
+ return null;
44
+ const canRetry = nodes.length > 0 && !running;
45
+ return (_jsxs("div", { className: `flex items-start gap-2 border-b border-red-200 bg-red-50 px-3 py-2 dark:border-red-800/50 dark:bg-red-950/20 ${className}`, role: "alert", children: [_jsxs(Icon, { size: 14, className: "mt-0.5 shrink-0 text-red-500", children: [_jsx("circle", { cx: "12", cy: "12", r: "10" }), _jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }), _jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })] }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-[11px] font-medium text-red-700 dark:text-red-400", children: "\u6267\u884C\u51FA\u9519" }), _jsx("p", { className: "mt-0.5 break-words text-[10px] text-red-600/80 dark:text-red-400/70", children: error })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [canRetry && (_jsx("button", { type: "button", onClick: handleRetry, className: "rounded bg-red-100 px-2 py-0.5 text-[10px] font-medium text-red-700 transition-colors hover:bg-red-200 dark:bg-red-900/40 dark:text-red-400 dark:hover:bg-red-900/60", children: "\u91CD\u8BD5" })), _jsx("button", { type: "button", onClick: handleDismiss, "aria-label": "\u5173\u95ED\u9519\u8BEF\u4FE1\u606F", className: "rounded p-0.5 text-red-400 transition-colors hover:bg-red-100 hover:text-red-600 dark:hover:bg-red-900/40", children: _jsx(Icon, { size: 12, strokeWidth: 3, children: _jsx("path", { d: "M6 18L18 6M6 6l12 12" }) }) })] })] }));
46
+ }
47
+ //# sourceMappingURL=ErrorBanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBanner.js","sourceRoot":"","sources":["../../src/components/ErrorBanner.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * ExifPanel - EXIF 元数据查看面板(W7.4)
3
+ *
4
+ * 监听当前选中的 image asset,通过 runtime.readAssetExif 桥接调用
5
+ * plugin-image 注册的 MetadataReader,展示结构化 EXIF 字段。
6
+ *
7
+ * 架构合规(长期方案):
8
+ * - UI 只依赖 @lokvis/schema(ExifData 类型 + formatExifRows)和 @lokvis/runtime
9
+ * - 不直接依赖 plugin-image / engine-image(五层单向依赖)
10
+ * - ExifData 类型不含 raw 字段(schema 层类型分层),UI 缓存无需手动剔除
11
+ *
12
+ * 设计要点:
13
+ * - 独立可折叠面板(渲染在 Inspector 顶部)
14
+ * - 仅 image 类型 asset 显示;非 image 隐藏整个面板
15
+ * - 缓存:按 assetId 缓存(LRU 上限 16),切换 asset 不重复解析
16
+ * - effect 依赖 selectedAssetId(而非 asset 对象引用),避免 assets 数组
17
+ * 重渲染时触发重复 effect
18
+ * - 取消:effect 重跑/卸载时不更新 state(避免 stale update)
19
+ * - 加载/空态/错误三态明确
20
+ */
21
+ import * as React from 'react';
22
+ export interface ExifPanelProps {
23
+ className?: string;
24
+ }
25
+ export declare function ExifPanel({ className }: ExifPanelProps): React.JSX.Element | null;
26
+ //# sourceMappingURL=ExifPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExifPanel.d.ts","sourceRoot":"","sources":["../../src/components/ExifPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD,wBAAgB,SAAS,CAAC,EAAE,SAAc,EAAE,EAAE,cAAc,4BAyJ3D"}
@@ -0,0 +1,104 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * ExifPanel - EXIF 元数据查看面板(W7.4)
4
+ *
5
+ * 监听当前选中的 image asset,通过 runtime.readAssetExif 桥接调用
6
+ * plugin-image 注册的 MetadataReader,展示结构化 EXIF 字段。
7
+ *
8
+ * 架构合规(长期方案):
9
+ * - UI 只依赖 @lokvis/schema(ExifData 类型 + formatExifRows)和 @lokvis/runtime
10
+ * - 不直接依赖 plugin-image / engine-image(五层单向依赖)
11
+ * - ExifData 类型不含 raw 字段(schema 层类型分层),UI 缓存无需手动剔除
12
+ *
13
+ * 设计要点:
14
+ * - 独立可折叠面板(渲染在 Inspector 顶部)
15
+ * - 仅 image 类型 asset 显示;非 image 隐藏整个面板
16
+ * - 缓存:按 assetId 缓存(LRU 上限 16),切换 asset 不重复解析
17
+ * - effect 依赖 selectedAssetId(而非 asset 对象引用),避免 assets 数组
18
+ * 重渲染时触发重复 effect
19
+ * - 取消:effect 重跑/卸载时不更新 state(避免 stale update)
20
+ * - 加载/空态/错误三态明确
21
+ */
22
+ import * as React from 'react';
23
+ import { Icon } from '@lokvis/ui-core';
24
+ import { formatExifRows } from '@lokvis/schema';
25
+ import { useWorkspaceStore } from '../store/index.js';
26
+ /** cache LRU 上限:防止长会话切换大量图片时 Map 无限增长 */
27
+ const EXIF_CACHE_LIMIT = 16;
28
+ export function ExifPanel({ className = '' }) {
29
+ const selectedAssetId = useWorkspaceStore((s) => s.selectedAssetId);
30
+ const assets = useWorkspaceStore((s) => s.assets);
31
+ const runtime = useWorkspaceStore((s) => s.runtime);
32
+ const selectedAsset = React.useMemo(() => assets.find((a) => a.id === selectedAssetId) ?? null, [assets, selectedAssetId]);
33
+ // 仅 image 类型 asset 显示 EXIF 面板
34
+ const isImage = selectedAsset?.type === 'image';
35
+ // effect 依赖用 id 而非 asset 引用:避免 assets 数组重渲染触发重复 effect
36
+ const selectedAssetIdForEffect = selectedAsset?.id;
37
+ const [open, setOpen] = React.useState(true);
38
+ const [exif, setExif] = React.useState(null);
39
+ const [loading, setLoading] = React.useState(false);
40
+ const [error, setError] = React.useState(null);
41
+ // 按 assetId 缓存解析结果,避免切换来回重复解析
42
+ const cacheRef = React.useRef(new Map());
43
+ React.useEffect(() => {
44
+ if (!isImage || !selectedAssetIdForEffect || !runtime) {
45
+ setExif(null);
46
+ setError(null);
47
+ setLoading(false);
48
+ return;
49
+ }
50
+ const assetId = selectedAssetIdForEffect;
51
+ const cache = cacheRef.current;
52
+ // 命中缓存:同步设置,不发请求(LRU 命中后重排到末尾)
53
+ if (cache.has(assetId)) {
54
+ const cached = cache.get(assetId) ?? null;
55
+ cache.delete(assetId);
56
+ cache.set(assetId, cached);
57
+ setExif(cached);
58
+ setError(null);
59
+ setLoading(false);
60
+ return;
61
+ }
62
+ let cancelled = false;
63
+ setLoading(true);
64
+ setError(null);
65
+ (async () => {
66
+ try {
67
+ // 通过 runtime.readAssetExif 桥接(MetadataReader 依赖反转),
68
+ // UI 不直接依赖 plugin-image / engine-image
69
+ const data = await runtime.readAssetExif(assetId);
70
+ if (cancelled)
71
+ return;
72
+ // LRU 上限:超出时删除最旧 entry(Map 第一个,即最近最少命中)
73
+ if (cache.size >= EXIF_CACHE_LIMIT) {
74
+ const oldestKey = cache.keys().next().value;
75
+ if (oldestKey !== undefined)
76
+ cache.delete(oldestKey);
77
+ }
78
+ cache.set(assetId, data);
79
+ setExif(data);
80
+ setError(null);
81
+ }
82
+ catch (err) {
83
+ if (cancelled)
84
+ return;
85
+ const msg = err instanceof Error ? err.message : 'Failed to read EXIF';
86
+ setError(msg);
87
+ setExif(null);
88
+ }
89
+ finally {
90
+ if (!cancelled)
91
+ setLoading(false);
92
+ }
93
+ })();
94
+ return () => {
95
+ cancelled = true;
96
+ };
97
+ }, [isImage, selectedAssetIdForEffect, runtime]);
98
+ // 非 image 或无选中 asset:不渲染(避免占用布局空间)
99
+ if (!isImage || !selectedAsset)
100
+ return null;
101
+ const rows = exif ? formatExifRows(exif) : [];
102
+ return (_jsxs("div", { className: `shrink-0 border-b border-zinc-200 dark:border-zinc-800 ${className}`, children: [_jsxs("button", { type: "button", onClick: () => setOpen(!open), className: "flex w-full items-center justify-between px-3 h-10 text-left transition-colors hover:bg-zinc-50 dark:hover:bg-zinc-800/50", "aria-expanded": open, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { size: 12, className: `text-zinc-400 transition-transform ${open ? 'rotate-90' : ''}`, children: _jsx("path", { d: "m9 5 7 7-7 7" }) }), _jsx("span", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-400", children: "EXIF" })] }), loading ? (_jsx("span", { className: "text-[10px] text-zinc-400", children: "loading\u2026" })) : exif ? (_jsxs("span", { className: "font-mono text-[10px] text-emerald-600 bg-emerald-50 dark:bg-emerald-950/30 dark:text-emerald-400 px-1.5 py-0.5 rounded", children: [rows.length, " fields"] })) : error ? (_jsx("span", { className: "text-[10px] text-red-500", children: "error" })) : (_jsx("span", { className: "text-[10px] text-zinc-400", children: "none" }))] }), open && (_jsx("div", { className: "px-3 pb-3 max-h-60 overflow-y-auto", children: loading ? (_jsx("div", { className: "py-4 text-center text-[11px] text-zinc-400", children: "Reading EXIF\u2026" })) : error ? (_jsx("div", { className: "py-3 text-center", children: _jsx("p", { className: "text-[11px] text-red-500", children: error }) })) : rows.length === 0 ? (_jsxs("div", { className: "py-4 text-center", children: [_jsx("p", { className: "text-[11px] text-zinc-400", children: "No EXIF data" }), _jsx("p", { className: "mt-1 text-[10px] text-zinc-500", children: "This image has no embedded metadata" })] })) : (_jsx("dl", { className: "space-y-1", children: rows.map((row) => (_jsxs("div", { className: "flex items-baseline justify-between gap-3 text-[11px]", children: [_jsx("dt", { className: "shrink-0 text-zinc-400", children: row.label }), _jsx("dd", { className: "truncate text-right font-mono text-zinc-700 dark:text-zinc-300", children: row.value })] }, row.label))) })) }))] }));
103
+ }
104
+ //# sourceMappingURL=ExifPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExifPanel.js","sourceRoot":"","sources":["../../src/components/ExifPanel.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * GlobalDropzone - 全屏拖拽导入区 + MIME 类型校验(W9.3)
3
+ *
4
+ * 当用户拖拽文件进入窗口时,显示全屏遮罩引导用户拖到任意位置即可导入。
5
+ * 通过 `accept` 白名单做 MIME 类型校验,拒绝类型时给出视觉反馈 + 警告。
6
+ *
7
+ * 工作机制:
8
+ * 1. 监听 window dragenter / dragover / dragleave / drop 事件
9
+ * 2. dragenter 显示遮罩,dragleave(离开 window)隐藏
10
+ * 3. drop 时校验文件类型 → 合法则 importFiles,非法则提示拒绝原因
11
+ *
12
+ * MIME 校验:
13
+ * - 默认接受所有 image/* 类型(以及 video/audio/pdf 等,与 workspace 多媒体定位对齐)
14
+ * - 拒绝类型在遮罩中央显示红色提示,并阻止导入
15
+ *
16
+ * 与 AssetPanel / Canvas 内置的局部 dropzone 互不冲突:它们各自处理自己的
17
+ * drop 事件并 `stopPropagation()`,本组件的 window drop 监听不会触发。
18
+ *
19
+ * 防御性兜底:即便子组件忘了 `stopPropagation`,本组件也会再次处理 drop 事件,
20
+ * 但通过 `importFiles` 的幂等性(以 File 引用去重)— 不,File 引用并不去重,
21
+ * 所以子组件**必须** `stopPropagation()`。本组件在 drop 事件触发时,先检查
22
+ * `e.defaultPrevented`(子组件会 `preventDefault()` 表明已处理),若已处理
23
+ * 则直接 return,避免双重导入。
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * <GlobalDropzone accept="image/*" />
28
+ * ```
29
+ */
30
+ import * as React from 'react';
31
+ export interface GlobalDropzoneProps {
32
+ /** 接受的 MIME 类型(逗号分隔),默认接受图片/视频/音频/PDF */
33
+ accept?: string;
34
+ className?: string;
35
+ }
36
+ export declare function GlobalDropzone({ accept, className, }: GlobalDropzoneProps): React.JSX.Element | null;
37
+ //# sourceMappingURL=GlobalDropzone.d.ts.map