@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.
- 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
|
@@ -12,152 +12,152 @@ import { ParamForm } from './ParamForm.js';
|
|
|
12
12
|
import { ExifPanel } from './ExifPanel.js';
|
|
13
13
|
|
|
14
14
|
export interface InspectorProps {
|
|
15
|
-
|
|
15
|
+
className?: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function Inspector({ className = '' }: InspectorProps) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
const capabilities = useWorkspaceStore((s) => s.capabilities);
|
|
20
|
+
const nodes = useWorkspaceStore((s) => s.nodes);
|
|
21
|
+
const selectedNodeId = useWorkspaceStore((s) => s.selectedNodeId);
|
|
22
|
+
const capabilityMap = useWorkspaceStore((s) => s.capabilityMap);
|
|
23
|
+
const addNode = useWorkspaceStore((s) => s.addNode);
|
|
24
|
+
const updateNodeParams = useWorkspaceStore((s) => s.updateNodeParams);
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const selectedNode = nodes.find((n) => n.id === selectedNodeId);
|
|
27
|
+
const [filter, setFilter] = React.useState('');
|
|
28
|
+
const [configureOpen, setConfigureOpen] = React.useState(true);
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
const filtered = capabilities.filter(
|
|
31
|
+
(c) =>
|
|
32
|
+
c.name.toLowerCase().includes(filter.toLowerCase()) ||
|
|
33
|
+
c.description.toLowerCase().includes(filter.toLowerCase())
|
|
34
|
+
);
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
// 按域分组
|
|
37
|
+
const grouped = React.useMemo(() => {
|
|
38
|
+
const map = new Map<string, typeof filtered>();
|
|
39
|
+
for (const cap of filtered) {
|
|
40
|
+
const domain = cap.name.split('.')[0] ?? 'other';
|
|
41
|
+
if (!map.has(domain)) map.set(domain, []);
|
|
42
|
+
map.get(domain)!.push(cap);
|
|
43
|
+
}
|
|
44
|
+
return map;
|
|
45
|
+
}, [filtered]);
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
const showConfigure = selectedNode && capabilityMap[selectedNode.capability];
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
return (
|
|
50
|
+
<aside
|
|
51
|
+
className={`flex w-72 shrink-0 flex-col border-l border-[var(--lokvis-border)] ${className}`}
|
|
52
|
+
>
|
|
53
|
+
{/* EXIF panel (selected image asset only, hides automatically for non-image) */}
|
|
54
|
+
<ExifPanel />
|
|
55
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
|
-
|
|
56
|
+
{/* Configure section (when a node is selected) */}
|
|
57
|
+
{showConfigure && (
|
|
58
|
+
<div className="shrink-0 border-b border-[var(--lokvis-border)]">
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
onClick={() => setConfigureOpen(!configureOpen)}
|
|
62
|
+
className="flex w-full items-center justify-between px-3 h-10 text-left transition-colors hover:bg-[var(--lokvis-surface)]"
|
|
63
|
+
>
|
|
64
|
+
<div className="flex items-center gap-2">
|
|
65
|
+
<Icon size={12} className={`text-[var(--lokvis-fg-subtle)] transition-transform ${configureOpen ? 'rotate-90' : ''}`}><path d="m9 5 7 7-7 7" /></Icon>
|
|
66
|
+
<span className="text-[11px] font-semibold uppercase tracking-wider text-[var(--lokvis-fg-subtle)]">Configure</span>
|
|
67
|
+
</div>
|
|
68
|
+
<span className="font-mono text-[10px] text-[var(--lokvis-primary)] bg-[var(--lokvis-primary)]/10 px-1.5 py-0.5 rounded">
|
|
69
|
+
{selectedNode.capability}
|
|
70
|
+
</span>
|
|
71
|
+
</button>
|
|
72
|
+
{configureOpen && (
|
|
73
|
+
<div className="px-3 pb-3 max-h-60 overflow-y-auto">
|
|
74
|
+
<ParamForm
|
|
75
|
+
capability={capabilityMap[selectedNode.capability]!}
|
|
76
|
+
values={selectedNode.params}
|
|
77
|
+
onChange={(params) => updateNodeParams(selectedNode.id, params)}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
{/* Capabilities section */}
|
|
85
|
+
<div className="flex flex-1 flex-col overflow-hidden">
|
|
86
|
+
<div className="flex items-center justify-between px-3 h-10 shrink-0 border-b border-[var(--lokvis-border)]">
|
|
87
|
+
<span className="text-[11px] font-semibold uppercase tracking-wider text-[var(--lokvis-fg-subtle)]">
|
|
88
|
+
Capabilities
|
|
89
|
+
</span>
|
|
90
|
+
<span className="text-[11px] tabular-nums text-[var(--lokvis-fg-subtle)]">{filtered.length}</span>
|
|
91
|
+
</div>
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
93
|
+
{/* Search */}
|
|
94
|
+
<div className="px-2 py-2 border-b border-[var(--lokvis-border)]">
|
|
95
|
+
<div className="relative">
|
|
96
|
+
<Icon size={12} className="absolute left-2 top-1/2 -translate-y-1/2 text-[var(--lokvis-fg-subtle)]"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></Icon>
|
|
97
|
+
<input
|
|
98
|
+
type="text"
|
|
99
|
+
placeholder="Search capabilities..."
|
|
100
|
+
value={filter}
|
|
101
|
+
onChange={(e) => setFilter(e.target.value)}
|
|
102
|
+
className="w-full rounded-md border border-[var(--lokvis-border)] bg-[var(--lokvis-surface)] py-1 pl-7 pr-2 text-[11px] placeholder-zinc-400 focus:border-[var(--lokvis-primary)] focus:outline-none focus:ring-1 focus:ring-[var(--lokvis-primary)]"
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
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
|
-
|
|
107
|
+
{/* Capability list */}
|
|
108
|
+
<div className="flex-1 overflow-y-auto p-2">
|
|
109
|
+
{capabilities.length === 0 ? (
|
|
110
|
+
<div className="px-2 py-8 text-center">
|
|
111
|
+
<p className="text-[11px] text-[var(--lokvis-fg-subtle)]">No capabilities loaded</p>
|
|
112
|
+
<p className="mt-1 text-[10px] text-[var(--lokvis-fg-muted)]">Load a plugin to get started</p>
|
|
113
|
+
</div>
|
|
114
|
+
) : filtered.length === 0 ? (
|
|
115
|
+
<p className="px-2 py-4 text-[11px] text-[var(--lokvis-fg-subtle)]">No matching capabilities</p>
|
|
116
|
+
) : (
|
|
117
|
+
<div className="space-y-3">
|
|
118
|
+
{Array.from(grouped.entries()).map(([domain, caps]) => (
|
|
119
|
+
<div key={domain}>
|
|
120
|
+
<div className="px-2 py-1 text-[10px] font-semibold uppercase tracking-wider text-[var(--lokvis-fg-subtle)]">
|
|
121
|
+
{domain}
|
|
122
|
+
</div>
|
|
123
|
+
<ul className="space-y-0.5">
|
|
124
|
+
{caps.map((cap) => {
|
|
125
|
+
const isInPipeline = nodes.some((n) => n.capability === cap.name);
|
|
126
|
+
return (
|
|
127
|
+
<li key={cap.name}>
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
onClick={() => addNode(cap.name)}
|
|
131
|
+
className={`group w-full rounded-md px-2 py-1.5 text-left transition-all ${
|
|
132
|
+
isInPipeline
|
|
133
|
+
? 'bg-[var(--lokvis-primary)]/5 ring-1 ring-[var(--lokvis-primary)]/40'
|
|
134
|
+
: 'hover:bg-[var(--lokvis-surface)]'
|
|
135
|
+
}`}
|
|
136
|
+
>
|
|
137
|
+
<div className="flex items-center justify-between gap-2">
|
|
138
|
+
<span className="truncate font-mono text-[11px] font-medium">{cap.name}</span>
|
|
139
|
+
<span className={`shrink-0 rounded px-1 py-0.5 text-[9px] font-medium uppercase ${
|
|
140
|
+
cap.performance === 'fast'
|
|
141
|
+
? 'bg-[var(--lokvis-success)]/15 text-[var(--lokvis-success)]'
|
|
142
|
+
: cap.performance === 'medium'
|
|
143
|
+
? 'bg-[var(--lokvis-warning)]/15 text-[var(--lokvis-warning)]'
|
|
144
|
+
: 'bg-[var(--lokvis-surface-muted)] text-[var(--lokvis-fg-muted)]'
|
|
145
|
+
}`}>
|
|
146
|
+
{cap.performance}
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
<p className="mt-0.5 truncate text-[10px] text-[var(--lokvis-fg-muted)]">{cap.description}</p>
|
|
150
|
+
</button>
|
|
151
|
+
</li>
|
|
152
|
+
);
|
|
153
|
+
})}
|
|
154
|
+
</ul>
|
|
155
|
+
</div>
|
|
156
|
+
))}
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</aside>
|
|
162
|
+
);
|
|
163
163
|
}
|
|
@@ -8,156 +8,156 @@
|
|
|
8
8
|
import type { Capability, CapabilityParam } from '@lokvis/schema';
|
|
9
9
|
|
|
10
10
|
export interface ParamFormProps {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
capability: Capability;
|
|
12
|
+
values: Record<string, unknown>;
|
|
13
|
+
onChange: (values: Record<string, unknown>) => void;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function ParamForm({ capability, values, onChange }: ParamFormProps) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const set = (name: string, value: unknown) => {
|
|
18
|
+
onChange({ ...values, [name]: value });
|
|
19
|
+
};
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
if (capability.params.length === 0) {
|
|
22
|
+
return (
|
|
23
|
+
<p className="py-3 text-center text-[11px] text-[var(--lokvis-fg-subtle)] italic">
|
|
24
|
+
This capability has no configurable parameters.
|
|
25
|
+
</p>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
return (
|
|
30
|
+
<div className="space-y-2.5">
|
|
31
|
+
{capability.params.map((p) => (
|
|
32
|
+
<ParamField
|
|
33
|
+
key={p.name}
|
|
34
|
+
param={p}
|
|
35
|
+
value={values[p.name]}
|
|
36
|
+
onChange={(v) => set(p.name, v)}
|
|
37
|
+
/>
|
|
38
|
+
))}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const inputBase =
|
|
44
|
-
|
|
44
|
+
'w-full rounded-md border border-[var(--lokvis-border)] bg-[var(--lokvis-surface)] px-2.5 py-1.5 text-[11px] transition-colors focus:border-[var(--lokvis-primary)] focus:outline-none focus:ring-1 focus:ring-[var(--lokvis-primary)]';
|
|
45
45
|
|
|
46
46
|
function ParamField({
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
param,
|
|
48
|
+
value,
|
|
49
|
+
onChange,
|
|
50
50
|
}: {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
param: CapabilityParam;
|
|
52
|
+
value: unknown;
|
|
53
|
+
onChange: (value: unknown) => void;
|
|
54
54
|
}) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
55
|
+
if (param.type === 'boolean') {
|
|
56
|
+
return (
|
|
57
|
+
<label className="flex cursor-pointer items-center gap-2 rounded-md px-1 py-1 transition-colors hover:bg-[var(--lokvis-surface)]">
|
|
58
|
+
<input
|
|
59
|
+
type="checkbox"
|
|
60
|
+
checked={Boolean(value ?? param.default)}
|
|
61
|
+
onChange={(e) => onChange(e.target.checked)}
|
|
62
|
+
className="h-3.5 w-3.5 rounded border-[var(--lokvis-border-strong)] text-[var(--lokvis-primary)] focus:ring-[var(--lokvis-primary)]"
|
|
63
|
+
/>
|
|
64
|
+
<span className="flex-1 text-[11px] text-[var(--lokvis-fg-muted)]">{param.name}</span>
|
|
65
|
+
{param.description && (
|
|
66
|
+
<span className="shrink-0 text-[10px] text-[var(--lokvis-fg-subtle)]">{param.description}</span>
|
|
67
|
+
)}
|
|
68
|
+
</label>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
72
|
+
if (param.type === 'enum' && param.values) {
|
|
73
|
+
return (
|
|
74
|
+
<div>
|
|
75
|
+
<label className="mb-1 block text-[11px] font-medium text-[var(--lokvis-fg-muted)]">
|
|
76
|
+
{param.name}
|
|
77
|
+
{param.required && <span className="ml-0.5 text-[var(--lokvis-danger)]">*</span>}
|
|
78
|
+
</label>
|
|
79
|
+
<select
|
|
80
|
+
value={(value as string) ?? (param.default as string) ?? ''}
|
|
81
|
+
onChange={(e) => onChange(e.target.value)}
|
|
82
|
+
className={inputBase}
|
|
83
|
+
>
|
|
84
|
+
{param.values.map((v) => (
|
|
85
|
+
<option key={v} value={v}>{v}</option>
|
|
86
|
+
))}
|
|
87
|
+
</select>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
92
|
+
if (param.type === 'color') {
|
|
93
|
+
return (
|
|
94
|
+
<div>
|
|
95
|
+
<label className="mb-1 block text-[11px] font-medium text-[var(--lokvis-fg-muted)]">
|
|
96
|
+
{param.name}
|
|
97
|
+
</label>
|
|
98
|
+
<div className="flex gap-2">
|
|
99
|
+
<input
|
|
100
|
+
type="color"
|
|
101
|
+
value={(value as string) ?? (param.default as string) ?? '#ffffff'}
|
|
102
|
+
onChange={(e) => onChange(e.target.value)}
|
|
103
|
+
className="h-7 w-7 shrink-0 cursor-pointer rounded border border-[var(--lokvis-border)]"
|
|
104
|
+
/>
|
|
105
|
+
<input
|
|
106
|
+
type="text"
|
|
107
|
+
value={(value as string) ?? (param.default as string) ?? '#ffffff'}
|
|
108
|
+
onChange={(e) => onChange(e.target.value)}
|
|
109
|
+
className={inputBase}
|
|
110
|
+
/>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
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
|
-
|
|
116
|
+
if (param.type === 'number') {
|
|
117
|
+
return (
|
|
118
|
+
<div>
|
|
119
|
+
<div className="mb-1 flex items-center justify-between">
|
|
120
|
+
<label className="text-[11px] font-medium text-[var(--lokvis-fg-muted)]">
|
|
121
|
+
{param.name}
|
|
122
|
+
{param.required && <span className="ml-0.5 text-[var(--lokvis-danger)]">*</span>}
|
|
123
|
+
</label>
|
|
124
|
+
{(param.min !== undefined || param.max !== undefined) && (
|
|
125
|
+
<span className="text-[10px] text-[var(--lokvis-fg-subtle)]">
|
|
126
|
+
{param.min !== undefined ? param.min : '-'}
|
|
127
|
+
{' — '}
|
|
128
|
+
{param.max !== undefined ? param.max : '∞'}
|
|
129
|
+
</span>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
<input
|
|
133
|
+
type="number"
|
|
134
|
+
value={value === undefined ? (param.default as number | undefined) ?? '' : (value as number)}
|
|
135
|
+
min={param.min}
|
|
136
|
+
max={param.max}
|
|
137
|
+
onChange={(e) => onChange(e.target.value === '' ? undefined : Number(e.target.value))}
|
|
138
|
+
className={inputBase}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
144
|
+
// string / file / array / object → text input
|
|
145
|
+
return (
|
|
146
|
+
<div>
|
|
147
|
+
<label className="mb-1 block text-[11px] font-medium text-[var(--lokvis-fg-muted)]">
|
|
148
|
+
{param.name}
|
|
149
|
+
{param.required && <span className="ml-0.5 text-[var(--lokvis-danger)]">*</span>}
|
|
150
|
+
{param.description && (
|
|
151
|
+
<span className="ml-1 font-normal text-[var(--lokvis-fg-subtle)]">{param.description}</span>
|
|
152
|
+
)}
|
|
153
|
+
</label>
|
|
154
|
+
<input
|
|
155
|
+
type="text"
|
|
156
|
+
value={(value as string) ?? (param.default as string) ?? ''}
|
|
157
|
+
onChange={(e) => onChange(e.target.value)}
|
|
158
|
+
placeholder={param.type === 'file' ? 'data URL or path' : ''}
|
|
159
|
+
className={inputBase}
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
163
|
}
|