@lokvis/ui-react 0.2.1 → 0.2.2

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 (97) hide show
  1. package/dist/components/AssetPanel.d.ts.map +1 -1
  2. package/dist/components/AssetPanel.js +14 -14
  3. package/dist/components/AssetPanel.js.map +1 -1
  4. package/dist/components/Canvas.d.ts +4 -4
  5. package/dist/components/Canvas.d.ts.map +1 -1
  6. package/dist/components/Canvas.js +8 -8
  7. package/dist/components/Canvas.js.map +1 -1
  8. package/dist/components/CommandPalette.d.ts +7 -7
  9. package/dist/components/CommandPalette.d.ts.map +1 -1
  10. package/dist/components/CommandPalette.js +10 -10
  11. package/dist/components/CommandPalette.js.map +1 -1
  12. package/dist/components/CompareSlider.d.ts +6 -6
  13. package/dist/components/CompareSlider.d.ts.map +1 -1
  14. package/dist/components/CompareSlider.js +7 -7
  15. package/dist/components/CompareSlider.js.map +1 -1
  16. package/dist/components/DownloadPanel.d.ts +3 -3
  17. package/dist/components/DownloadPanel.d.ts.map +1 -1
  18. package/dist/components/DownloadPanel.js +9 -9
  19. package/dist/components/DownloadPanel.js.map +1 -1
  20. package/dist/components/ErrorBanner.d.ts +2 -2
  21. package/dist/components/ErrorBanner.d.ts.map +1 -1
  22. package/dist/components/ErrorBanner.js +3 -3
  23. package/dist/components/ErrorBanner.js.map +1 -1
  24. package/dist/components/ExifPanel.d.ts +1 -1
  25. package/dist/components/ExifPanel.d.ts.map +1 -1
  26. package/dist/components/ExifPanel.js +2 -2
  27. package/dist/components/ExifPanel.js.map +1 -1
  28. package/dist/components/GlobalDropzone.d.ts +5 -5
  29. package/dist/components/GlobalDropzone.d.ts.map +1 -1
  30. package/dist/components/GlobalDropzone.js +6 -6
  31. package/dist/components/GlobalDropzone.js.map +1 -1
  32. package/dist/components/HistoryPanel.d.ts +2 -2
  33. package/dist/components/HistoryPanel.d.ts.map +1 -1
  34. package/dist/components/HistoryPanel.js +23 -23
  35. package/dist/components/HistoryPanel.js.map +1 -1
  36. package/dist/components/Inspector.d.ts.map +1 -1
  37. package/dist/components/Inspector.js +6 -6
  38. package/dist/components/Inspector.js.map +1 -1
  39. package/dist/components/ParamForm.d.ts.map +1 -1
  40. package/dist/components/ParamForm.js +7 -7
  41. package/dist/components/ParamForm.js.map +1 -1
  42. package/dist/components/PipelineBar.d.ts.map +1 -1
  43. package/dist/components/PipelineBar.js +13 -13
  44. package/dist/components/PipelineBar.js.map +1 -1
  45. package/dist/components/ProgressBar.d.ts +4 -4
  46. package/dist/components/ProgressBar.d.ts.map +1 -1
  47. package/dist/components/ProgressBar.js +8 -8
  48. package/dist/components/ProgressBar.js.map +1 -1
  49. package/dist/components/StatusBar.d.ts +3 -3
  50. package/dist/components/StatusBar.d.ts.map +1 -1
  51. package/dist/components/StatusBar.js +11 -11
  52. package/dist/components/StatusBar.js.map +1 -1
  53. package/dist/components/ThemeToggle.d.ts +4 -4
  54. package/dist/components/ThemeToggle.d.ts.map +1 -1
  55. package/dist/components/ThemeToggle.js +7 -7
  56. package/dist/components/ThemeToggle.js.map +1 -1
  57. package/dist/components/Toolbar.d.ts.map +1 -1
  58. package/dist/components/Toolbar.js +2 -2
  59. package/dist/components/Toolbar.js.map +1 -1
  60. package/dist/components/WorkflowEditor.d.ts +6 -6
  61. package/dist/components/WorkflowEditor.d.ts.map +1 -1
  62. package/dist/components/WorkflowEditor.js +25 -25
  63. package/dist/components/WorkflowEditor.js.map +1 -1
  64. package/dist/components/WorkflowTemplates.d.ts +2 -2
  65. package/dist/components/WorkflowTemplates.d.ts.map +1 -1
  66. package/dist/components/WorkflowTemplates.js +3 -3
  67. package/dist/components/WorkflowTemplates.js.map +1 -1
  68. package/dist/components/Workspace.d.ts +22 -22
  69. package/dist/components/Workspace.d.ts.map +1 -1
  70. package/dist/components/Workspace.js +30 -30
  71. package/dist/components/Workspace.js.map +1 -1
  72. package/dist/index.d.ts +1 -0
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +3 -0
  75. package/dist/index.js.map +1 -1
  76. package/package.json +6 -6
  77. package/src/components/AssetPanel.tsx +263 -263
  78. package/src/components/Canvas.tsx +174 -174
  79. package/src/components/CommandPalette.tsx +242 -242
  80. package/src/components/CompareSlider.tsx +163 -163
  81. package/src/components/DownloadPanel.tsx +203 -203
  82. package/src/components/ErrorBanner.tsx +64 -64
  83. package/src/components/ExifPanel.tsx +154 -154
  84. package/src/components/GlobalDropzone.tsx +170 -170
  85. package/src/components/HistoryPanel.tsx +219 -219
  86. package/src/components/Inspector.tsx +136 -136
  87. package/src/components/ParamForm.tsx +136 -136
  88. package/src/components/PipelineBar.tsx +98 -98
  89. package/src/components/ProgressBar.tsx +78 -78
  90. package/src/components/StatusBar.tsx +148 -148
  91. package/src/components/ThemeToggle.tsx +98 -98
  92. package/src/components/Toolbar.tsx +65 -65
  93. package/src/components/WorkflowEditor.tsx +321 -321
  94. package/src/components/WorkflowTemplates.tsx +77 -77
  95. package/src/components/Workspace.tsx +223 -223
  96. package/src/css-modules.d.ts +3 -0
  97. package/src/index.ts +4 -0
@@ -14,7 +14,7 @@
14
14
  * - 仅 image 类型 asset 显示;非 image 隐藏整个面板
15
15
  * - 缓存:按 assetId 缓存(LRU 上限 16),切换 asset 不重复解析
16
16
  * - effect 依赖 selectedAssetId(而非 asset 对象引用),避免 assets 数组
17
- * 重渲染时触发重复 effect
17
+ * 重渲染时触发重复 effect
18
18
  * - 取消:effect 重跑/卸载时不更新 state(避免 stale update)
19
19
  * - 加载/空态/错误三态明确
20
20
  */
@@ -25,163 +25,163 @@ import { formatExifRows, type ExifData } from '@lokvis/schema';
25
25
  import { useWorkspaceStore } from '../store/index.js';
26
26
 
27
27
  export interface ExifPanelProps {
28
- className?: string;
28
+ className?: string;
29
29
  }
30
30
 
31
31
  /** cache LRU 上限:防止长会话切换大量图片时 Map 无限增长 */
32
32
  const EXIF_CACHE_LIMIT = 16;
33
33
 
34
34
  export function ExifPanel({ className = '' }: ExifPanelProps) {
35
- const selectedAssetId = useWorkspaceStore((s) => s.selectedAssetId);
36
- const assets = useWorkspaceStore((s) => s.assets);
37
- const runtime = useWorkspaceStore((s) => s.runtime);
38
-
39
- const selectedAsset = React.useMemo(
40
- () => assets.find((a) => a.id === selectedAssetId) ?? null,
41
- [assets, selectedAssetId]
42
- );
43
-
44
- // 仅 image 类型 asset 显示 EXIF 面板
45
- const isImage = selectedAsset?.type === 'image';
46
- // effect 依赖用 id 而非 asset 引用:避免 assets 数组重渲染触发重复 effect
47
- const selectedAssetIdForEffect = selectedAsset?.id;
48
-
49
- const [open, setOpen] = React.useState(true);
50
- const [exif, setExif] = React.useState<ExifData | null>(null);
51
- const [loading, setLoading] = React.useState(false);
52
- const [error, setError] = React.useState<string | null>(null);
53
-
54
- // 按 assetId 缓存解析结果,避免切换来回重复解析
55
- const cacheRef = React.useRef<Map<string, ExifData | null>>(new Map());
56
-
57
- React.useEffect(() => {
58
- if (!isImage || !selectedAssetIdForEffect || !runtime) {
59
- setExif(null);
60
- setError(null);
61
- setLoading(false);
62
- return;
63
- }
64
-
65
- const assetId = selectedAssetIdForEffect;
66
- const cache = cacheRef.current;
67
-
68
- // 命中缓存:同步设置,不发请求(LRU 命中后重排到末尾)
69
- if (cache.has(assetId)) {
70
- const cached = cache.get(assetId) ?? null;
71
- cache.delete(assetId);
72
- cache.set(assetId, cached);
73
- setExif(cached);
74
- setError(null);
75
- setLoading(false);
76
- return;
77
- }
78
-
79
- let cancelled = false;
80
- setLoading(true);
81
- setError(null);
82
-
83
- (async () => {
84
- try {
85
- // 通过 runtime.readAssetExif 桥接(MetadataReader 依赖反转),
86
- // UI 不直接依赖 plugin-image / engine-image
87
- const data = await runtime.readAssetExif(assetId);
88
- if (cancelled) return;
89
- // LRU 上限:超出时删除最旧 entry(Map 第一个,即最近最少命中)
90
- if (cache.size >= EXIF_CACHE_LIMIT) {
91
- const oldestKey = cache.keys().next().value;
92
- if (oldestKey !== undefined) cache.delete(oldestKey);
93
- }
94
- cache.set(assetId, data);
95
- setExif(data);
96
- setError(null);
97
- } catch (err) {
98
- if (cancelled) return;
99
- const msg = err instanceof Error ? err.message : 'Failed to read EXIF';
100
- setError(msg);
101
- setExif(null);
102
- } finally {
103
- if (!cancelled) setLoading(false);
104
- }
105
- })();
106
-
107
- return () => {
108
- cancelled = true;
109
- };
110
- }, [isImage, selectedAssetIdForEffect, runtime]);
111
-
112
- // 非 image 或无选中 asset:不渲染(避免占用布局空间)
113
- if (!isImage || !selectedAsset) return null;
114
-
115
- const rows = exif ? formatExifRows(exif) : [];
116
-
117
- return (
118
- <div
119
- className={`shrink-0 border-b border-zinc-200 dark:border-zinc-800 ${className}`}
120
- >
121
- <button
122
- type="button"
123
- onClick={() => setOpen(!open)}
124
- 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"
125
- aria-expanded={open}
126
- >
127
- <div className="flex items-center gap-2">
128
- <Icon
129
- size={12}
130
- className={`text-zinc-400 transition-transform ${open ? 'rotate-90' : ''}`}
131
- >
132
- <path d="m9 5 7 7-7 7" />
133
- </Icon>
134
- <span className="text-[11px] font-semibold uppercase tracking-wider text-zinc-400">
135
- EXIF
136
- </span>
137
- </div>
138
- {loading ? (
139
- <span className="text-[10px] text-zinc-400">loading…</span>
140
- ) : exif ? (
141
- <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">
142
- {rows.length} fields
143
- </span>
144
- ) : error ? (
145
- <span className="text-[10px] text-red-500">error</span>
146
- ) : (
147
- <span className="text-[10px] text-zinc-400">none</span>
148
- )}
149
- </button>
150
-
151
- {open && (
152
- <div className="px-3 pb-3 max-h-60 overflow-y-auto">
153
- {loading ? (
154
- <div className="py-4 text-center text-[11px] text-zinc-400">
155
- Reading EXIF…
156
- </div>
157
- ) : error ? (
158
- <div className="py-3 text-center">
159
- <p className="text-[11px] text-red-500">{error}</p>
160
- </div>
161
- ) : rows.length === 0 ? (
162
- <div className="py-4 text-center">
163
- <p className="text-[11px] text-zinc-400">No EXIF data</p>
164
- <p className="mt-1 text-[10px] text-zinc-500">
165
- This image has no embedded metadata
166
- </p>
167
- </div>
168
- ) : (
169
- <dl className="space-y-1">
170
- {rows.map((row) => (
171
- <div
172
- key={row.label}
173
- className="flex items-baseline justify-between gap-3 text-[11px]"
174
- >
175
- <dt className="shrink-0 text-zinc-400">{row.label}</dt>
176
- <dd className="truncate text-right font-mono text-zinc-700 dark:text-zinc-300">
177
- {row.value}
178
- </dd>
179
- </div>
180
- ))}
181
- </dl>
182
- )}
183
- </div>
184
- )}
185
- </div>
186
- );
35
+ const selectedAssetId = useWorkspaceStore((s) => s.selectedAssetId);
36
+ const assets = useWorkspaceStore((s) => s.assets);
37
+ const runtime = useWorkspaceStore((s) => s.runtime);
38
+
39
+ const selectedAsset = React.useMemo(
40
+ () => assets.find((a) => a.id === selectedAssetId) ?? null,
41
+ [assets, selectedAssetId]
42
+ );
43
+
44
+ // 仅 image 类型 asset 显示 EXIF 面板
45
+ const isImage = selectedAsset?.type === 'image';
46
+ // effect 依赖用 id 而非 asset 引用:避免 assets 数组重渲染触发重复 effect
47
+ const selectedAssetIdForEffect = selectedAsset?.id;
48
+
49
+ const [open, setOpen] = React.useState(true);
50
+ const [exif, setExif] = React.useState<ExifData | null>(null);
51
+ const [loading, setLoading] = React.useState(false);
52
+ const [error, setError] = React.useState<string | null>(null);
53
+
54
+ // 按 assetId 缓存解析结果,避免切换来回重复解析
55
+ const cacheRef = React.useRef<Map<string, ExifData | null>>(new Map());
56
+
57
+ React.useEffect(() => {
58
+ if (!isImage || !selectedAssetIdForEffect || !runtime) {
59
+ setExif(null);
60
+ setError(null);
61
+ setLoading(false);
62
+ return;
63
+ }
64
+
65
+ const assetId = selectedAssetIdForEffect;
66
+ const cache = cacheRef.current;
67
+
68
+ // 命中缓存:同步设置,不发请求(LRU 命中后重排到末尾)
69
+ if (cache.has(assetId)) {
70
+ const cached = cache.get(assetId) ?? null;
71
+ cache.delete(assetId);
72
+ cache.set(assetId, cached);
73
+ setExif(cached);
74
+ setError(null);
75
+ setLoading(false);
76
+ return;
77
+ }
78
+
79
+ let cancelled = false;
80
+ setLoading(true);
81
+ setError(null);
82
+
83
+ (async () => {
84
+ try {
85
+ // 通过 runtime.readAssetExif 桥接(MetadataReader 依赖反转),
86
+ // UI 不直接依赖 plugin-image / engine-image
87
+ const data = await runtime.readAssetExif(assetId);
88
+ if (cancelled) return;
89
+ // LRU 上限:超出时删除最旧 entry(Map 第一个,即最近最少命中)
90
+ if (cache.size >= EXIF_CACHE_LIMIT) {
91
+ const oldestKey = cache.keys().next().value;
92
+ if (oldestKey !== undefined) cache.delete(oldestKey);
93
+ }
94
+ cache.set(assetId, data);
95
+ setExif(data);
96
+ setError(null);
97
+ } catch (err) {
98
+ if (cancelled) return;
99
+ const msg = err instanceof Error ? err.message : 'Failed to read EXIF';
100
+ setError(msg);
101
+ setExif(null);
102
+ } finally {
103
+ if (!cancelled) setLoading(false);
104
+ }
105
+ })();
106
+
107
+ return () => {
108
+ cancelled = true;
109
+ };
110
+ }, [isImage, selectedAssetIdForEffect, runtime]);
111
+
112
+ // 非 image 或无选中 asset:不渲染(避免占用布局空间)
113
+ if (!isImage || !selectedAsset) return null;
114
+
115
+ const rows = exif ? formatExifRows(exif) : [];
116
+
117
+ return (
118
+ <div
119
+ className={`shrink-0 border-b border-[var(--lokvis-border)] ${className}`}
120
+ >
121
+ <button
122
+ type="button"
123
+ onClick={() => setOpen(!open)}
124
+ className="flex w-full items-center justify-between px-3 h-10 text-left transition-colors hover:bg-[var(--lokvis-surface)]"
125
+ aria-expanded={open}
126
+ >
127
+ <div className="flex items-center gap-2">
128
+ <Icon
129
+ size={12}
130
+ className={`text-[var(--lokvis-fg-subtle)] transition-transform ${open ? 'rotate-90' : ''}`}
131
+ >
132
+ <path d="m9 5 7 7-7 7" />
133
+ </Icon>
134
+ <span className="text-[11px] font-semibold uppercase tracking-wider text-[var(--lokvis-fg-subtle)]">
135
+ EXIF
136
+ </span>
137
+ </div>
138
+ {loading ? (
139
+ <span className="text-[10px] text-[var(--lokvis-fg-subtle)]">loading…</span>
140
+ ) : exif ? (
141
+ <span className="font-mono text-[10px] text-[var(--lokvis-success)] bg-[var(--lokvis-success)]/10 px-1.5 py-0.5 rounded">
142
+ {rows.length} fields
143
+ </span>
144
+ ) : error ? (
145
+ <span className="text-[10px] text-[var(--lokvis-danger)]">error</span>
146
+ ) : (
147
+ <span className="text-[10px] text-[var(--lokvis-fg-subtle)]">none</span>
148
+ )}
149
+ </button>
150
+
151
+ {open && (
152
+ <div className="px-3 pb-3 max-h-60 overflow-y-auto">
153
+ {loading ? (
154
+ <div className="py-4 text-center text-[11px] text-[var(--lokvis-fg-subtle)]">
155
+ Reading EXIF…
156
+ </div>
157
+ ) : error ? (
158
+ <div className="py-3 text-center">
159
+ <p className="text-[11px] text-[var(--lokvis-danger)]">{error}</p>
160
+ </div>
161
+ ) : rows.length === 0 ? (
162
+ <div className="py-4 text-center">
163
+ <p className="text-[11px] text-[var(--lokvis-fg-subtle)]">No EXIF data</p>
164
+ <p className="mt-1 text-[10px] text-[var(--lokvis-fg-muted)]">
165
+ This image has no embedded metadata
166
+ </p>
167
+ </div>
168
+ ) : (
169
+ <dl className="space-y-1">
170
+ {rows.map((row) => (
171
+ <div
172
+ key={row.label}
173
+ className="flex items-baseline justify-between gap-3 text-[11px]"
174
+ >
175
+ <dt className="shrink-0 text-[var(--lokvis-fg-subtle)]">{row.label}</dt>
176
+ <dd className="truncate text-right font-mono text-[var(--lokvis-fg-muted)]">
177
+ {row.value}
178
+ </dd>
179
+ </div>
180
+ ))}
181
+ </dl>
182
+ )}
183
+ </div>
184
+ )}
185
+ </div>
186
+ );
187
187
  }