@lokvis/ui-react 0.2.0 → 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
@@ -5,14 +5,14 @@
5
5
  * before / after 两张图。用于直观展示工作流处理效果。
6
6
  *
7
7
  * 交互:
8
- * - 鼠标拖拽分隔条(或点击容器任意位置)调整揭示比例
9
- * - 触摸:touchmove 同步
10
- * - 键盘:← → 调整 5% 步长(聚焦分隔条时)
8
+ * - 鼠标拖拽分隔条(或点击容器任意位置)调整揭示比例
9
+ * - 触摸:touchmove 同步
10
+ * - 键盘:← → 调整 5% 步长(聚焦分隔条时)
11
11
  *
12
12
  * 数据来源:
13
- * - before: `store.selectedAssetId` 对应的 thumbnails(由 AssetPanel 自动生成)
14
- * - after: `store.selectedOutputId` 对应的 thumbnails
15
- * - 任一缺失则不渲染(返回 null)
13
+ * - before: `store.selectedAssetId` 对应的 thumbnails(由 AssetPanel 自动生成)
14
+ * - after: `store.selectedOutputId` 对应的 thumbnails
15
+ * - 任一缺失则不渲染(返回 null)
16
16
  *
17
17
  * 注:本组件只负责"展示对比",不负责生成缩略图。缩略图由 AssetPanel
18
18
  * 的 effect 统一生成并存入 store.thumbnails,本组件按 assetId 取用。
@@ -23,164 +23,164 @@ import { Icon } from '@lokvis/ui-core';
23
23
  import { useWorkspaceStore } from '../store/index.js';
24
24
 
25
25
  export interface CompareSliderProps {
26
- className?: string;
26
+ className?: string;
27
27
  }
28
28
 
29
29
  export function CompareSlider({ className = '' }: CompareSliderProps) {
30
- const assets = useWorkspaceStore((s) => s.assets);
31
- const thumbnails = useWorkspaceStore((s) => s.thumbnails);
32
- const selectedAssetId = useWorkspaceStore((s) => s.selectedAssetId);
33
- const selectedOutputId = useWorkspaceStore((s) => s.selectedOutputId);
34
- const lastOutputIds = useWorkspaceStore((s) => s.lastOutputIds);
35
-
36
- // before:用户选中的输入资产;after:用户选中的输出资产(默认第一个)
37
- const beforeId = selectedAssetId;
38
- const afterId = selectedOutputId ?? lastOutputIds[0] ?? null;
39
-
40
- const beforeAsset = beforeId ? assets.find((a) => a.id === beforeId) : null;
41
- const afterAsset = afterId ? assets.find((a) => a.id === afterId) : null;
42
- const beforeUrl = beforeId ? thumbnails[beforeId] : undefined;
43
- const afterUrl = afterId ? thumbnails[afterId] : undefined;
44
-
45
- const containerRef = React.useRef<HTMLDivElement>(null);
46
- const [pos, setPos] = React.useState(50); // 0-100,分隔条位置百分比
47
- const [dragging, setDragging] = React.useState(false);
48
-
49
- // 切换资产时重置位置到中央
50
- React.useEffect(() => {
51
- setPos(50);
52
- }, [beforeId, afterId]);
53
-
54
- const updatePos = React.useCallback((clientX: number) => {
55
- const el = containerRef.current;
56
- if (!el) return;
57
- const rect = el.getBoundingClientRect();
58
- const ratio = (clientX - rect.left) / rect.width;
59
- setPos(Math.max(0, Math.min(100, ratio * 100)));
60
- }, []);
61
-
62
- // 鼠标拖拽
63
- const handleMouseDown = (e: React.MouseEvent) => {
64
- e.preventDefault();
65
- setDragging(true);
66
- updatePos(e.clientX);
67
- };
68
- React.useEffect(() => {
69
- if (!dragging) return;
70
- const onMove = (e: MouseEvent) => updatePos(e.clientX);
71
- const onUp = () => setDragging(false);
72
- document.addEventListener('mousemove', onMove);
73
- document.addEventListener('mouseup', onUp);
74
- return () => {
75
- document.removeEventListener('mousemove', onMove);
76
- document.removeEventListener('mouseup', onUp);
77
- };
78
- }, [dragging, updatePos]);
79
-
80
- // 触摸拖拽
81
- const handleTouchStart = (e: React.TouchEvent) => {
82
- setDragging(true);
83
- if (e.touches[0]) updatePos(e.touches[0].clientX);
84
- };
85
- React.useEffect(() => {
86
- if (!dragging) return;
87
- const onMove = (e: TouchEvent) => {
88
- if (e.touches[0]) updatePos(e.touches[0].clientX);
89
- };
90
- const onEnd = () => setDragging(false);
91
- document.addEventListener('touchmove', onMove, { passive: true });
92
- document.addEventListener('touchend', onEnd);
93
- return () => {
94
- document.removeEventListener('touchmove', onMove);
95
- document.removeEventListener('touchend', onEnd);
96
- };
97
- }, [dragging, updatePos]);
98
-
99
- // 键盘:← → 调整 5%
100
- const handleKeyDown = (e: React.KeyboardEvent) => {
101
- if (e.key === 'ArrowLeft') {
102
- e.preventDefault();
103
- setPos((p) => Math.max(0, p - 5));
104
- } else if (e.key === 'ArrowRight') {
105
- e.preventDefault();
106
- setPos((p) => Math.min(100, p + 5));
107
- }
108
- };
109
-
110
- if (!beforeUrl || !afterUrl) return null;
111
-
112
- return (
113
- <div
114
- ref={containerRef}
115
- className={`relative select-none overflow-hidden rounded-lg ring-1 ring-black/5 dark:ring-black/30 ${className}`}
116
- onMouseDown={handleMouseDown}
117
- onTouchStart={handleTouchStart}
118
- >
119
- {/* after 全图(底层)——决定容器尺寸 */}
120
- <img
121
- src={afterUrl}
122
- alt="After"
123
- draggable={false}
124
- className="block max-h-full max-w-full object-contain pointer-events-none"
125
- />
126
-
127
- {/* before 覆盖层:绝对定位同尺寸,用 clip-path 从右侧裁剪到 pos%
128
- (避免读取 containerRef.current.clientWidth 这种"渲染期读 ref"反模式) */}
129
- <div
130
- className="absolute inset-0"
131
- style={{ clipPath: `inset(0 ${100 - pos}% 0 0)` }}
132
- >
133
- <img
134
- src={beforeUrl}
135
- alt="Before"
136
- draggable={false}
137
- className="block max-h-full max-w-full object-contain pointer-events-none"
138
- />
139
- </div>
140
-
141
- {/* before/after 标签 */}
142
- <span className="absolute top-2 left-2 rounded bg-black/60 px-1.5 py-0.5 text-[10px] font-medium text-white">
143
- Before
144
- </span>
145
- <span className="absolute top-2 right-2 rounded bg-black/60 px-1.5 py-0.5 text-[10px] font-medium text-white">
146
- After
147
- </span>
148
-
149
- {/* 分隔条 */}
150
- <div
151
- className="absolute top-0 bottom-0 z-10 cursor-ew-resize"
152
- style={{ left: `${pos}%`, transform: 'translateX(-50%)' }}
153
- role="separator"
154
- aria-valuenow={Math.round(pos)}
155
- aria-valuemin={0}
156
- aria-valuemax={100}
157
- aria-label="Comparison slider position"
158
- tabIndex={0}
159
- onKeyDown={handleKeyDown}
160
- >
161
- <div className="absolute inset-y-0 left-1/2 w-0.5 -translate-x-1/2 bg-white shadow-md" />
162
- <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">
163
- <Icon size={14} className="text-zinc-600">
164
- <path d="M9 5l-5 7 5 7M15 5l5 7-5 7" />
165
- </Icon>
166
- </div>
167
- </div>
168
-
169
- {/* 元数据对比(底部叠加) */}
170
- {(beforeAsset || afterAsset) && (
171
- <div className="absolute bottom-2 inset-x-2 flex justify-between text-[10px] text-white">
172
- {beforeAsset?.metadata.dimensions && (
173
- <span className="rounded bg-black/60 px-1.5 py-0.5">
174
- {beforeAsset.metadata.dimensions.width}×{beforeAsset.metadata.dimensions.height}
175
- </span>
176
- )}
177
- {afterAsset?.metadata.dimensions && (
178
- <span className="rounded bg-black/60 px-1.5 py-0.5">
179
- {afterAsset.metadata.dimensions.width}×{afterAsset.metadata.dimensions.height}
180
- </span>
181
- )}
182
- </div>
183
- )}
184
- </div>
185
- );
30
+ const assets = useWorkspaceStore((s) => s.assets);
31
+ const thumbnails = useWorkspaceStore((s) => s.thumbnails);
32
+ const selectedAssetId = useWorkspaceStore((s) => s.selectedAssetId);
33
+ const selectedOutputId = useWorkspaceStore((s) => s.selectedOutputId);
34
+ const lastOutputIds = useWorkspaceStore((s) => s.lastOutputIds);
35
+
36
+ // before:用户选中的输入资产;after:用户选中的输出资产(默认第一个)
37
+ const beforeId = selectedAssetId;
38
+ const afterId = selectedOutputId ?? lastOutputIds[0] ?? null;
39
+
40
+ const beforeAsset = beforeId ? assets.find((a) => a.id === beforeId) : null;
41
+ const afterAsset = afterId ? assets.find((a) => a.id === afterId) : null;
42
+ const beforeUrl = beforeId ? thumbnails[beforeId] : undefined;
43
+ const afterUrl = afterId ? thumbnails[afterId] : undefined;
44
+
45
+ const containerRef = React.useRef<HTMLDivElement>(null);
46
+ const [pos, setPos] = React.useState(50); // 0-100,分隔条位置百分比
47
+ const [dragging, setDragging] = React.useState(false);
48
+
49
+ // 切换资产时重置位置到中央
50
+ React.useEffect(() => {
51
+ setPos(50);
52
+ }, [beforeId, afterId]);
53
+
54
+ const updatePos = React.useCallback((clientX: number) => {
55
+ const el = containerRef.current;
56
+ if (!el) return;
57
+ const rect = el.getBoundingClientRect();
58
+ const ratio = (clientX - rect.left) / rect.width;
59
+ setPos(Math.max(0, Math.min(100, ratio * 100)));
60
+ }, []);
61
+
62
+ // 鼠标拖拽
63
+ const handleMouseDown = (e: React.MouseEvent) => {
64
+ e.preventDefault();
65
+ setDragging(true);
66
+ updatePos(e.clientX);
67
+ };
68
+ React.useEffect(() => {
69
+ if (!dragging) return;
70
+ const onMove = (e: MouseEvent) => updatePos(e.clientX);
71
+ const onUp = () => setDragging(false);
72
+ document.addEventListener('mousemove', onMove);
73
+ document.addEventListener('mouseup', onUp);
74
+ return () => {
75
+ document.removeEventListener('mousemove', onMove);
76
+ document.removeEventListener('mouseup', onUp);
77
+ };
78
+ }, [dragging, updatePos]);
79
+
80
+ // 触摸拖拽
81
+ const handleTouchStart = (e: React.TouchEvent) => {
82
+ setDragging(true);
83
+ if (e.touches[0]) updatePos(e.touches[0].clientX);
84
+ };
85
+ React.useEffect(() => {
86
+ if (!dragging) return;
87
+ const onMove = (e: TouchEvent) => {
88
+ if (e.touches[0]) updatePos(e.touches[0].clientX);
89
+ };
90
+ const onEnd = () => setDragging(false);
91
+ document.addEventListener('touchmove', onMove, { passive: true });
92
+ document.addEventListener('touchend', onEnd);
93
+ return () => {
94
+ document.removeEventListener('touchmove', onMove);
95
+ document.removeEventListener('touchend', onEnd);
96
+ };
97
+ }, [dragging, updatePos]);
98
+
99
+ // 键盘:← → 调整 5%
100
+ const handleKeyDown = (e: React.KeyboardEvent) => {
101
+ if (e.key === 'ArrowLeft') {
102
+ e.preventDefault();
103
+ setPos((p) => Math.max(0, p - 5));
104
+ } else if (e.key === 'ArrowRight') {
105
+ e.preventDefault();
106
+ setPos((p) => Math.min(100, p + 5));
107
+ }
108
+ };
109
+
110
+ if (!beforeUrl || !afterUrl) return null;
111
+
112
+ return (
113
+ <div
114
+ ref={containerRef}
115
+ className={`relative select-none overflow-hidden rounded-lg ring-1 ring-black/5 ${className}`}
116
+ onMouseDown={handleMouseDown}
117
+ onTouchStart={handleTouchStart}
118
+ >
119
+ {/* after 全图(底层)——决定容器尺寸 */}
120
+ <img
121
+ src={afterUrl}
122
+ alt="After"
123
+ draggable={false}
124
+ className="block max-h-full max-w-full object-contain pointer-events-none"
125
+ />
126
+
127
+ {/* before 覆盖层:绝对定位同尺寸,用 clip-path 从右侧裁剪到 pos%
128
+ (避免读取 containerRef.current.clientWidth 这种"渲染期读 ref"反模式) */}
129
+ <div
130
+ className="absolute inset-0"
131
+ style={{ clipPath: `inset(0 ${100 - pos}% 0 0)` }}
132
+ >
133
+ <img
134
+ src={beforeUrl}
135
+ alt="Before"
136
+ draggable={false}
137
+ className="block max-h-full max-w-full object-contain pointer-events-none"
138
+ />
139
+ </div>
140
+
141
+ {/* before/after 标签 */}
142
+ <span className="absolute top-2 left-2 rounded bg-black/60 px-1.5 py-0.5 text-[10px] font-medium text-[var(--lokvis-primary-fg)]">
143
+ Before
144
+ </span>
145
+ <span className="absolute top-2 right-2 rounded bg-black/60 px-1.5 py-0.5 text-[10px] font-medium text-[var(--lokvis-primary-fg)]">
146
+ After
147
+ </span>
148
+
149
+ {/* 分隔条 */}
150
+ <div
151
+ className="absolute top-0 bottom-0 z-10 cursor-ew-resize"
152
+ style={{ left: `${pos}%`, transform: 'translateX(-50%)' }}
153
+ role="separator"
154
+ aria-valuenow={Math.round(pos)}
155
+ aria-valuemin={0}
156
+ aria-valuemax={100}
157
+ aria-label="Comparison slider position"
158
+ tabIndex={0}
159
+ onKeyDown={handleKeyDown}
160
+ >
161
+ <div className="absolute inset-y-0 left-1/2 w-0.5 -translate-x-1/2 bg-[var(--lokvis-surface)] shadow-md" />
162
+ <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-[var(--lokvis-surface)] shadow-lg ring-1 ring-black/10">
163
+ <Icon size={14} className="text-[var(--lokvis-fg-muted)]">
164
+ <path d="M9 5l-5 7 5 7M15 5l5 7-5 7" />
165
+ </Icon>
166
+ </div>
167
+ </div>
168
+
169
+ {/* 元数据对比(底部叠加) */}
170
+ {(beforeAsset || afterAsset) && (
171
+ <div className="absolute bottom-2 inset-x-2 flex justify-between text-[10px] text-[var(--lokvis-primary-fg)]">
172
+ {beforeAsset?.metadata.dimensions && (
173
+ <span className="rounded bg-black/60 px-1.5 py-0.5">
174
+ {beforeAsset.metadata.dimensions.width}×{beforeAsset.metadata.dimensions.height}
175
+ </span>
176
+ )}
177
+ {afterAsset?.metadata.dimensions && (
178
+ <span className="rounded bg-black/60 px-1.5 py-0.5">
179
+ {afterAsset.metadata.dimensions.width}×{afterAsset.metadata.dimensions.height}
180
+ </span>
181
+ )}
182
+ </div>
183
+ )}
184
+ </div>
185
+ );
186
186
  }