@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.
- package/dist/components/AssetPanel.d.ts.map +1 -1
- package/dist/components/AssetPanel.js +14 -14
- package/dist/components/AssetPanel.js.map +1 -1
- package/dist/components/Canvas.d.ts +4 -4
- package/dist/components/Canvas.d.ts.map +1 -1
- package/dist/components/Canvas.js +8 -8
- package/dist/components/Canvas.js.map +1 -1
- package/dist/components/CommandPalette.d.ts +7 -7
- package/dist/components/CommandPalette.d.ts.map +1 -1
- package/dist/components/CommandPalette.js +10 -10
- package/dist/components/CommandPalette.js.map +1 -1
- package/dist/components/CompareSlider.d.ts +6 -6
- package/dist/components/CompareSlider.d.ts.map +1 -1
- package/dist/components/CompareSlider.js +7 -7
- package/dist/components/CompareSlider.js.map +1 -1
- package/dist/components/DownloadPanel.d.ts +3 -3
- package/dist/components/DownloadPanel.d.ts.map +1 -1
- package/dist/components/DownloadPanel.js +9 -9
- package/dist/components/DownloadPanel.js.map +1 -1
- package/dist/components/ErrorBanner.d.ts +2 -2
- package/dist/components/ErrorBanner.d.ts.map +1 -1
- package/dist/components/ErrorBanner.js +3 -3
- package/dist/components/ErrorBanner.js.map +1 -1
- package/dist/components/ExifPanel.d.ts +1 -1
- package/dist/components/ExifPanel.d.ts.map +1 -1
- package/dist/components/ExifPanel.js +2 -2
- package/dist/components/ExifPanel.js.map +1 -1
- package/dist/components/GlobalDropzone.d.ts +5 -5
- package/dist/components/GlobalDropzone.d.ts.map +1 -1
- package/dist/components/GlobalDropzone.js +6 -6
- package/dist/components/GlobalDropzone.js.map +1 -1
- package/dist/components/HistoryPanel.d.ts +2 -2
- package/dist/components/HistoryPanel.d.ts.map +1 -1
- package/dist/components/HistoryPanel.js +23 -23
- package/dist/components/HistoryPanel.js.map +1 -1
- package/dist/components/Inspector.d.ts.map +1 -1
- package/dist/components/Inspector.js +6 -6
- package/dist/components/Inspector.js.map +1 -1
- package/dist/components/ParamForm.d.ts.map +1 -1
- package/dist/components/ParamForm.js +7 -7
- package/dist/components/ParamForm.js.map +1 -1
- package/dist/components/PipelineBar.d.ts.map +1 -1
- package/dist/components/PipelineBar.js +13 -13
- package/dist/components/PipelineBar.js.map +1 -1
- package/dist/components/ProgressBar.d.ts +4 -4
- package/dist/components/ProgressBar.d.ts.map +1 -1
- package/dist/components/ProgressBar.js +8 -8
- package/dist/components/ProgressBar.js.map +1 -1
- package/dist/components/StatusBar.d.ts +3 -3
- package/dist/components/StatusBar.d.ts.map +1 -1
- package/dist/components/StatusBar.js +11 -11
- package/dist/components/StatusBar.js.map +1 -1
- package/dist/components/ThemeToggle.d.ts +4 -4
- package/dist/components/ThemeToggle.d.ts.map +1 -1
- package/dist/components/ThemeToggle.js +7 -7
- package/dist/components/ThemeToggle.js.map +1 -1
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +2 -2
- package/dist/components/Toolbar.js.map +1 -1
- package/dist/components/WorkflowEditor.d.ts +6 -6
- package/dist/components/WorkflowEditor.d.ts.map +1 -1
- package/dist/components/WorkflowEditor.js +25 -25
- package/dist/components/WorkflowEditor.js.map +1 -1
- package/dist/components/WorkflowTemplates.d.ts +2 -2
- package/dist/components/WorkflowTemplates.d.ts.map +1 -1
- package/dist/components/WorkflowTemplates.js +3 -3
- package/dist/components/WorkflowTemplates.js.map +1 -1
- package/dist/components/Workspace.d.ts +22 -22
- package/dist/components/Workspace.d.ts.map +1 -1
- package/dist/components/Workspace.js +30 -30
- package/dist/components/Workspace.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/components/AssetPanel.tsx +263 -263
- package/src/components/Canvas.tsx +174 -174
- package/src/components/CommandPalette.tsx +242 -242
- package/src/components/CompareSlider.tsx +163 -163
- package/src/components/DownloadPanel.tsx +203 -203
- package/src/components/ErrorBanner.tsx +64 -64
- package/src/components/ExifPanel.tsx +154 -154
- package/src/components/GlobalDropzone.tsx +170 -170
- package/src/components/HistoryPanel.tsx +219 -219
- package/src/components/Inspector.tsx +136 -136
- package/src/components/ParamForm.tsx +136 -136
- package/src/components/PipelineBar.tsx +98 -98
- package/src/components/ProgressBar.tsx +78 -78
- package/src/components/StatusBar.tsx +148 -148
- package/src/components/ThemeToggle.tsx +98 -98
- package/src/components/Toolbar.tsx +65 -65
- package/src/components/WorkflowEditor.tsx +321 -321
- package/src/components/WorkflowTemplates.tsx +77 -77
- package/src/components/Workspace.tsx +223 -223
- package/src/css-modules.d.ts +3 -0
- 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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
}
|