@rozenite/controls-plugin 1.13.0 → 2.0.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.
- package/CHANGELOG.md +15 -0
- package/dist/devtools/assets/panel-DOPs3JU-.css +1 -0
- package/dist/devtools/assets/panel-bqV0H3UX.js +18 -0
- package/dist/devtools/panel.html +2 -2
- package/dist/react-native/index.d.ts +2 -2
- package/dist/rozenite.json +1 -1
- package/package.json +12 -15
- package/sdk.ts +1 -4
- package/src/__tests__/serialization.test.ts +10 -4
- package/src/shared/agent-tools.ts +1 -4
- package/src/shared/serialization.ts +2 -2
- package/src/ui/panel.tsx +150 -99
- package/dist/devtools/assets/panel-B5zz0XK3.css +0 -1
- package/dist/devtools/assets/panel-BoQwJa2M.js +0 -17
- package/postcss.config.js +0 -6
- package/src/ui/globals.css +0 -86
- package/tailwind.config.ts +0 -53
package/dist/devtools/panel.html
CHANGED
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
<script>
|
|
23
23
|
var __ROZENITE_PANEL__ = true;
|
|
24
24
|
</script>
|
|
25
|
-
<script type="module" crossorigin src="../devtools/assets/panel-
|
|
26
|
-
<link rel="stylesheet" crossorigin href="../devtools/assets/panel-
|
|
25
|
+
<script type="module" crossorigin src="../devtools/assets/panel-bqV0H3UX.js"></script>
|
|
26
|
+
<link rel="stylesheet" crossorigin href="../devtools/assets/panel-DOPs3JU-.css">
|
|
27
27
|
</head>
|
|
28
28
|
<body>
|
|
29
29
|
<div id="root"></div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RozeniteDevToolsRequestClient } from '@rozenite/plugin-bridge';
|
|
2
2
|
|
|
3
3
|
export declare type ControlsButtonItem = {
|
|
4
4
|
id: string;
|
|
@@ -133,6 +133,6 @@ export declare type RozeniteControlsPluginOptionsUpdater = (previousOptions: Roz
|
|
|
133
133
|
|
|
134
134
|
export declare let useRozeniteControlsPlugin: typeof useRozeniteControlsPlugin_2;
|
|
135
135
|
|
|
136
|
-
declare const useRozeniteControlsPlugin_2: (optionsInput: RozeniteControlsPluginOptionsInput) =>
|
|
136
|
+
declare const useRozeniteControlsPlugin_2: (optionsInput: RozeniteControlsPluginOptionsInput) => RozeniteDevToolsRequestClient<ControlsEventMap> | null;
|
|
137
137
|
|
|
138
138
|
export { }
|
package/dist/rozenite.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@rozenite/controls-plugin","version":"
|
|
1
|
+
{"name":"@rozenite/controls-plugin","version":"2.0.0","description":"A Rozenite plugin for exposing app-defined controls directly in React Native DevTools.","panels":[{"name":"Controls","source":"/devtools/panel.html"}]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rozenite/controls-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "A Rozenite plugin for exposing app-defined controls directly in React Native DevTools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/react-native/index.cjs",
|
|
@@ -15,25 +15,22 @@
|
|
|
15
15
|
"url": "https://github.com/callstackincubator/rozenite.git"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@rozenite/agent-shared": "
|
|
19
|
-
"@rozenite/agent-bridge": "
|
|
20
|
-
"@rozenite/plugin-bridge": "
|
|
18
|
+
"@rozenite/agent-shared": "2.0.0",
|
|
19
|
+
"@rozenite/agent-bridge": "2.0.0",
|
|
20
|
+
"@rozenite/plugin-bridge": "2.0.0",
|
|
21
|
+
"@rozenite/ui": "2.0.0"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
|
-
"@types/react": "~19.2.
|
|
24
|
-
"@types/react-dom": "~19.
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"react": "
|
|
28
|
-
"react-dom": "19.2.0",
|
|
29
|
-
"react-native": "0.83.1",
|
|
24
|
+
"@types/react": "~19.2.3",
|
|
25
|
+
"@types/react-dom": "~19.2.3",
|
|
26
|
+
"react": "19.2.3",
|
|
27
|
+
"react-dom": "19.2.3",
|
|
28
|
+
"react-native": "0.86.0",
|
|
30
29
|
"react-native-web": "^0.21.2",
|
|
31
|
-
"tailwindcss": "^3.4.17",
|
|
32
|
-
"tailwindcss-animate": "^1.0.7",
|
|
33
30
|
"typescript": "~5.9.3",
|
|
34
31
|
"vite": "^7.3.1",
|
|
35
|
-
"@rozenite/vite-plugin": "
|
|
36
|
-
"rozenite": "
|
|
32
|
+
"@rozenite/vite-plugin": "2.0.0",
|
|
33
|
+
"rozenite": "2.0.0"
|
|
37
34
|
},
|
|
38
35
|
"peerDependencies": {
|
|
39
36
|
"react": "*",
|
package/sdk.ts
CHANGED
|
@@ -4,10 +4,7 @@ import {
|
|
|
4
4
|
controlsToolDefinitions,
|
|
5
5
|
} from './src/shared/agent-tools.js';
|
|
6
6
|
|
|
7
|
-
export {
|
|
8
|
-
CONTROLS_AGENT_PLUGIN_ID,
|
|
9
|
-
controlsToolDefinitions,
|
|
10
|
-
};
|
|
7
|
+
export { CONTROLS_AGENT_PLUGIN_ID, controlsToolDefinitions };
|
|
11
8
|
|
|
12
9
|
export const controlsTools = defineAgentToolDescriptors(
|
|
13
10
|
CONTROLS_AGENT_PLUGIN_ID,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
buildActionRegistry,
|
|
4
|
+
getActionRegistryKey,
|
|
5
|
+
serializeSections,
|
|
6
|
+
} from '../shared/serialization';
|
|
3
7
|
import { createSection } from '../shared/types';
|
|
4
8
|
|
|
5
9
|
describe('controls serialization', () => {
|
|
@@ -167,12 +171,14 @@ describe('controls serialization', () => {
|
|
|
167
171
|
const registry = buildActionRegistry(sections);
|
|
168
172
|
|
|
169
173
|
const toggleEntry = registry.get(getActionRegistryKey('controls', 'flag'));
|
|
170
|
-
const buttonEntry = registry.get(
|
|
174
|
+
const buttonEntry = registry.get(
|
|
175
|
+
getActionRegistryKey('controls', 'refresh'),
|
|
176
|
+
);
|
|
171
177
|
const selectEntry = registry.get(
|
|
172
|
-
getActionRegistryKey('controls', 'environment')
|
|
178
|
+
getActionRegistryKey('controls', 'environment'),
|
|
173
179
|
);
|
|
174
180
|
const inputEntry = registry.get(
|
|
175
|
-
getActionRegistryKey('controls', 'release-label')
|
|
181
|
+
getActionRegistryKey('controls', 'release-label'),
|
|
176
182
|
);
|
|
177
183
|
|
|
178
184
|
expect(toggleEntry?.type).toBe('toggle');
|
|
@@ -2,10 +2,7 @@ import {
|
|
|
2
2
|
defineAgentToolContract,
|
|
3
3
|
type AgentToolContract,
|
|
4
4
|
} from '@rozenite/agent-shared';
|
|
5
|
-
import type {
|
|
6
|
-
ControlsItem,
|
|
7
|
-
ControlsItemSnapshot,
|
|
8
|
-
} from './types';
|
|
5
|
+
import type { ControlsItem, ControlsItemSnapshot } from './types';
|
|
9
6
|
|
|
10
7
|
export const CONTROLS_AGENT_PLUGIN_ID = '@rozenite/controls-plugin';
|
|
11
8
|
|
|
@@ -34,7 +34,7 @@ export type ActionRegistryEntry =
|
|
|
34
34
|
|
|
35
35
|
const validateValue = <TValue>(
|
|
36
36
|
validate: ControlsMutableItemBase<TValue>['validate'],
|
|
37
|
-
value: TValue
|
|
37
|
+
value: TValue,
|
|
38
38
|
): ControlsValidationResult => {
|
|
39
39
|
if (!validate) {
|
|
40
40
|
return { valid: true };
|
|
@@ -95,7 +95,7 @@ const toSnapshotItem = (item: ControlsItem): ControlsItemSnapshot => {
|
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
export const serializeSections = (
|
|
98
|
-
sections: ControlsSection[]
|
|
98
|
+
sections: ControlsSection[],
|
|
99
99
|
): ControlsSectionSnapshot[] =>
|
|
100
100
|
sections.map((section) => ({
|
|
101
101
|
id: section.id,
|
package/src/ui/panel.tsx
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
EmptyState,
|
|
5
|
+
Input,
|
|
6
|
+
PluginShell,
|
|
7
|
+
Select,
|
|
8
|
+
Sidebar,
|
|
9
|
+
Split,
|
|
10
|
+
Switch,
|
|
11
|
+
} from '@rozenite/ui';
|
|
2
12
|
import type { ReactNode } from 'react';
|
|
3
13
|
import { useEffect, useRef, useState } from 'react';
|
|
4
14
|
import type {
|
|
@@ -6,15 +16,19 @@ import type {
|
|
|
6
16
|
ControlsSnapshotEvent,
|
|
7
17
|
ControlsUpdateResultEvent,
|
|
8
18
|
} from '../shared/messaging';
|
|
9
|
-
import type {
|
|
10
|
-
|
|
19
|
+
import type {
|
|
20
|
+
ControlsItemSnapshot,
|
|
21
|
+
ControlsSectionSnapshot,
|
|
22
|
+
} from '../shared/types';
|
|
23
|
+
import '@rozenite/ui/styles.css';
|
|
11
24
|
|
|
12
25
|
type ItemUiState = {
|
|
13
26
|
pending: boolean;
|
|
14
27
|
message?: string;
|
|
15
28
|
};
|
|
16
29
|
|
|
17
|
-
const getItemKey = (sectionId: string, itemId: string) =>
|
|
30
|
+
const getItemKey = (sectionId: string, itemId: string) =>
|
|
31
|
+
`${sectionId}:${itemId}`;
|
|
18
32
|
|
|
19
33
|
const createRequestId = () =>
|
|
20
34
|
`${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -32,12 +46,12 @@ const RowShell = ({
|
|
|
32
46
|
}) => (
|
|
33
47
|
<div className="flex items-start justify-between gap-4 py-3">
|
|
34
48
|
<div className="min-w-0">
|
|
35
|
-
<div className="text-sm font-medium text-
|
|
49
|
+
<div className="text-sm font-medium text-foreground">{title}</div>
|
|
36
50
|
{description ? (
|
|
37
|
-
<div className="mt-1 text-xs text-
|
|
51
|
+
<div className="mt-1 text-xs text-muted-foreground">{description}</div>
|
|
38
52
|
) : null}
|
|
39
53
|
{errorMessage ? (
|
|
40
|
-
<div className="mt-1 text-xs text-
|
|
54
|
+
<div className="mt-1 text-xs text-destructive">{errorMessage}</div>
|
|
41
55
|
) : null}
|
|
42
56
|
</div>
|
|
43
57
|
{children}
|
|
@@ -61,16 +75,12 @@ const ToggleRow = ({
|
|
|
61
75
|
description={item.description}
|
|
62
76
|
errorMessage={uiState?.message}
|
|
63
77
|
>
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
onChange={(event) => onToggle(sectionId, item.id, event.target.checked)}
|
|
71
|
-
/>
|
|
72
|
-
<div className="h-6 w-11 rounded-full bg-gray-700 transition peer-checked:bg-violet-500 peer-disabled:cursor-not-allowed peer-disabled:opacity-50 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:bg-white after:transition-all after:content-[''] peer-checked:after:translate-x-5" />
|
|
73
|
-
</label>
|
|
78
|
+
<Switch
|
|
79
|
+
aria-label={item.title}
|
|
80
|
+
checked={item.value}
|
|
81
|
+
disabled={item.disabled || uiState?.pending}
|
|
82
|
+
onCheckedChange={(checked) => onToggle(sectionId, item.id, checked)}
|
|
83
|
+
/>
|
|
74
84
|
</RowShell>
|
|
75
85
|
);
|
|
76
86
|
};
|
|
@@ -92,13 +102,14 @@ const ButtonRow = ({
|
|
|
92
102
|
description={item.description}
|
|
93
103
|
errorMessage={uiState?.message}
|
|
94
104
|
>
|
|
95
|
-
<
|
|
96
|
-
|
|
105
|
+
<Button
|
|
106
|
+
size="compact"
|
|
107
|
+
variant="outline"
|
|
97
108
|
disabled={item.disabled || uiState?.pending}
|
|
98
109
|
onClick={() => onPress(sectionId, item.id)}
|
|
99
110
|
>
|
|
100
|
-
{uiState?.pending ? 'Running...' : item.actionLabel ?? 'Run'}
|
|
101
|
-
</
|
|
111
|
+
{uiState?.pending ? 'Running...' : (item.actionLabel ?? 'Run')}
|
|
112
|
+
</Button>
|
|
102
113
|
</RowShell>
|
|
103
114
|
);
|
|
104
115
|
};
|
|
@@ -120,18 +131,26 @@ const SelectRow = ({
|
|
|
120
131
|
description={item.description}
|
|
121
132
|
errorMessage={uiState?.message}
|
|
122
133
|
>
|
|
123
|
-
<
|
|
124
|
-
className="min-w-[160px] rounded-md border border-gray-700 bg-gray-950 px-3 py-2 text-xs text-gray-200 outline-none transition focus:border-violet-500 disabled:cursor-not-allowed disabled:opacity-50"
|
|
134
|
+
<Select
|
|
125
135
|
value={item.value}
|
|
126
136
|
disabled={item.disabled || uiState?.pending}
|
|
127
|
-
|
|
137
|
+
onValueChange={(value) => {
|
|
138
|
+
if (value !== null) {
|
|
139
|
+
onSelect(sectionId, item.id, value);
|
|
140
|
+
}
|
|
141
|
+
}}
|
|
128
142
|
>
|
|
129
|
-
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
<Select.Trigger className="w-40">
|
|
144
|
+
<Select.Value />
|
|
145
|
+
</Select.Trigger>
|
|
146
|
+
<Select.Content>
|
|
147
|
+
{item.options.map((option) => (
|
|
148
|
+
<Select.Item key={option.value} value={option.value}>
|
|
149
|
+
{option.label}
|
|
150
|
+
</Select.Item>
|
|
151
|
+
))}
|
|
152
|
+
</Select.Content>
|
|
153
|
+
</Select>
|
|
135
154
|
</RowShell>
|
|
136
155
|
);
|
|
137
156
|
};
|
|
@@ -143,7 +162,7 @@ const TextRow = ({
|
|
|
143
162
|
}) => {
|
|
144
163
|
return (
|
|
145
164
|
<RowShell title={item.title} description={item.description}>
|
|
146
|
-
<div className="max-w-[50%] rounded-md bg-
|
|
165
|
+
<div className="max-w-[50%] rounded-md bg-muted px-3 py-1.5 text-right text-xs text-muted-foreground">
|
|
147
166
|
{item.value}
|
|
148
167
|
</div>
|
|
149
168
|
</RowShell>
|
|
@@ -174,8 +193,8 @@ const InputRow = ({
|
|
|
174
193
|
errorMessage={uiState?.message}
|
|
175
194
|
>
|
|
176
195
|
<div className="flex min-w-[240px] items-center gap-2">
|
|
177
|
-
<
|
|
178
|
-
className="flex-1
|
|
196
|
+
<Input
|
|
197
|
+
className="flex-1"
|
|
179
198
|
type="text"
|
|
180
199
|
value={draftValue}
|
|
181
200
|
placeholder={item.placeholder}
|
|
@@ -184,13 +203,14 @@ const InputRow = ({
|
|
|
184
203
|
onDraftChange(sectionId, item.id, event.target.value)
|
|
185
204
|
}
|
|
186
205
|
/>
|
|
187
|
-
<
|
|
188
|
-
|
|
206
|
+
<Button
|
|
207
|
+
size="compact"
|
|
208
|
+
variant="outline"
|
|
189
209
|
disabled={!isChanged || item.disabled || uiState?.pending}
|
|
190
210
|
onClick={() => onApply(sectionId, item.id)}
|
|
191
211
|
>
|
|
192
|
-
{uiState?.pending ? 'Applying...' : item.applyLabel ?? 'Apply'}
|
|
193
|
-
</
|
|
212
|
+
{uiState?.pending ? 'Applying...' : (item.applyLabel ?? 'Apply')}
|
|
213
|
+
</Button>
|
|
194
214
|
</div>
|
|
195
215
|
</RowShell>
|
|
196
216
|
);
|
|
@@ -214,7 +234,11 @@ const renderItem = ({
|
|
|
214
234
|
onToggle: (sectionId: string, itemId: string, value: boolean) => void;
|
|
215
235
|
onPress: (sectionId: string, itemId: string) => void;
|
|
216
236
|
onSelect: (sectionId: string, itemId: string, value: string) => void;
|
|
217
|
-
onInputDraftChange: (
|
|
237
|
+
onInputDraftChange: (
|
|
238
|
+
sectionId: string,
|
|
239
|
+
itemId: string,
|
|
240
|
+
value: string,
|
|
241
|
+
) => void;
|
|
218
242
|
onInputApply: (sectionId: string, itemId: string) => void;
|
|
219
243
|
}) => {
|
|
220
244
|
if (item.type === 'text') {
|
|
@@ -268,9 +292,16 @@ const renderItem = ({
|
|
|
268
292
|
|
|
269
293
|
export default function ControlsPanel() {
|
|
270
294
|
const [sections, setSections] = useState<ControlsSectionSnapshot[]>([]);
|
|
295
|
+
const [selectedSectionId, setSelectedSectionId] = useState<string | null>(
|
|
296
|
+
null,
|
|
297
|
+
);
|
|
271
298
|
const [loading, setLoading] = useState(true);
|
|
272
|
-
const [itemUiState, setItemUiState] = useState<Map<string, ItemUiState>>(
|
|
273
|
-
|
|
299
|
+
const [itemUiState, setItemUiState] = useState<Map<string, ItemUiState>>(
|
|
300
|
+
new Map(),
|
|
301
|
+
);
|
|
302
|
+
const [inputDrafts, setInputDrafts] = useState<Map<string, string>>(
|
|
303
|
+
new Map(),
|
|
304
|
+
);
|
|
274
305
|
const committedInputValuesRef = useRef<Map<string, string>>(new Map());
|
|
275
306
|
|
|
276
307
|
const client = useRozeniteDevToolsClient<ControlsEventMap>({
|
|
@@ -286,13 +317,21 @@ export default function ControlsPanel() {
|
|
|
286
317
|
'snapshot',
|
|
287
318
|
(event: ControlsSnapshotEvent) => {
|
|
288
319
|
setSections(event.sections);
|
|
320
|
+
setSelectedSectionId((previous) =>
|
|
321
|
+
event.sections.some((section) => section.id === previous)
|
|
322
|
+
? previous
|
|
323
|
+
: (event.sections[0]?.id ?? null),
|
|
324
|
+
);
|
|
289
325
|
setLoading(false);
|
|
290
326
|
|
|
291
327
|
const nextCommittedValues = new Map<string, string>();
|
|
292
328
|
event.sections.forEach((section) => {
|
|
293
329
|
section.items.forEach((item) => {
|
|
294
330
|
if (item.type === 'input') {
|
|
295
|
-
nextCommittedValues.set(
|
|
331
|
+
nextCommittedValues.set(
|
|
332
|
+
getItemKey(section.id, item.id),
|
|
333
|
+
item.value,
|
|
334
|
+
);
|
|
296
335
|
}
|
|
297
336
|
});
|
|
298
337
|
});
|
|
@@ -310,7 +349,8 @@ export default function ControlsPanel() {
|
|
|
310
349
|
const previousCommitted = committedInputValuesRef.current.get(key);
|
|
311
350
|
const previousDraft = previous.get(key);
|
|
312
351
|
const isDirty =
|
|
313
|
-
previousDraft !== undefined &&
|
|
352
|
+
previousDraft !== undefined &&
|
|
353
|
+
previousDraft !== previousCommitted;
|
|
314
354
|
|
|
315
355
|
if (!isDirty || previousDraft === committedValue) {
|
|
316
356
|
next.set(key, committedValue);
|
|
@@ -321,7 +361,7 @@ export default function ControlsPanel() {
|
|
|
321
361
|
});
|
|
322
362
|
|
|
323
363
|
committedInputValuesRef.current = nextCommittedValues;
|
|
324
|
-
}
|
|
364
|
+
},
|
|
325
365
|
);
|
|
326
366
|
const updateResultSubscription = client.onMessage(
|
|
327
367
|
'update-result',
|
|
@@ -336,7 +376,7 @@ export default function ControlsPanel() {
|
|
|
336
376
|
});
|
|
337
377
|
return next;
|
|
338
378
|
});
|
|
339
|
-
}
|
|
379
|
+
},
|
|
340
380
|
);
|
|
341
381
|
|
|
342
382
|
client.send('get-snapshot', {
|
|
@@ -352,7 +392,7 @@ export default function ControlsPanel() {
|
|
|
352
392
|
const sendUpdateRequest = (
|
|
353
393
|
sectionId: string,
|
|
354
394
|
itemId: string,
|
|
355
|
-
value: boolean | string
|
|
395
|
+
value: boolean | string,
|
|
356
396
|
) => {
|
|
357
397
|
if (!client) {
|
|
358
398
|
return;
|
|
@@ -403,7 +443,7 @@ export default function ControlsPanel() {
|
|
|
403
443
|
const handleInputDraftChange = (
|
|
404
444
|
sectionId: string,
|
|
405
445
|
itemId: string,
|
|
406
|
-
value: string
|
|
446
|
+
value: string,
|
|
407
447
|
) => {
|
|
408
448
|
const key = getItemKey(sectionId, itemId);
|
|
409
449
|
|
|
@@ -437,65 +477,76 @@ export default function ControlsPanel() {
|
|
|
437
477
|
sendUpdateRequest(sectionId, itemId, draftValue);
|
|
438
478
|
};
|
|
439
479
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
<span className="text-sm font-medium text-gray-200">Controls</span>
|
|
444
|
-
<div className="flex-1" />
|
|
445
|
-
<span className="text-xs text-gray-400">{sections.length} sections</span>
|
|
446
|
-
</div>
|
|
480
|
+
const selectedSection = sections.find(
|
|
481
|
+
(section) => section.id === selectedSectionId,
|
|
482
|
+
);
|
|
447
483
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
</div>
|
|
453
|
-
) : null}
|
|
484
|
+
return (
|
|
485
|
+
<PluginShell className="dark">
|
|
486
|
+
<PluginShell.Body>
|
|
487
|
+
{loading ? <EmptyState title="Loading controls…" /> : null}
|
|
454
488
|
|
|
455
489
|
{!loading && sections.length === 0 ? (
|
|
456
|
-
<
|
|
457
|
-
No controls registered
|
|
458
|
-
|
|
490
|
+
<EmptyState
|
|
491
|
+
title="No controls registered"
|
|
492
|
+
description="Register controls in the app to manage them here."
|
|
493
|
+
/>
|
|
459
494
|
) : null}
|
|
460
495
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
<
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
496
|
+
{!loading && selectedSection ? (
|
|
497
|
+
<Split direction="horizontal" autoSaveId="controls-sections">
|
|
498
|
+
<Split.Pane defaultSize={22} minSize={15} maxSize={35}>
|
|
499
|
+
<Sidebar className="w-full border-r-0">
|
|
500
|
+
<Sidebar.Group>
|
|
501
|
+
{sections.map((section) => (
|
|
502
|
+
<Sidebar.Item
|
|
503
|
+
key={section.id}
|
|
504
|
+
selected={section.id === selectedSection.id}
|
|
505
|
+
onClick={() => setSelectedSectionId(section.id)}
|
|
506
|
+
>
|
|
507
|
+
{section.title}
|
|
508
|
+
</Sidebar.Item>
|
|
509
|
+
))}
|
|
510
|
+
</Sidebar.Group>
|
|
511
|
+
</Sidebar>
|
|
512
|
+
</Split.Pane>
|
|
513
|
+
<Split.Handle />
|
|
514
|
+
<Split.Pane>
|
|
515
|
+
<section className="h-full overflow-auto p-6">
|
|
516
|
+
<h1 className="text-base font-semibold text-foreground">
|
|
517
|
+
{selectedSection.title}
|
|
518
|
+
</h1>
|
|
519
|
+
{selectedSection.description ? (
|
|
520
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
521
|
+
{selectedSection.description}
|
|
522
|
+
</p>
|
|
475
523
|
) : null}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
524
|
+
<div className="mt-5 divide-y divide-border">
|
|
525
|
+
{selectedSection.items.map((item) => (
|
|
526
|
+
<div key={item.id}>
|
|
527
|
+
{renderItem({
|
|
528
|
+
sectionId: selectedSection.id,
|
|
529
|
+
item,
|
|
530
|
+
uiState: itemUiState.get(
|
|
531
|
+
getItemKey(selectedSection.id, item.id),
|
|
532
|
+
),
|
|
533
|
+
inputDraft: inputDrafts.get(
|
|
534
|
+
getItemKey(selectedSection.id, item.id),
|
|
535
|
+
),
|
|
536
|
+
onToggle: handleToggle,
|
|
537
|
+
onPress: handlePress,
|
|
538
|
+
onSelect: handleSelect,
|
|
539
|
+
onInputDraftChange: handleInputDraftChange,
|
|
540
|
+
onInputApply: handleInputApply,
|
|
541
|
+
})}
|
|
542
|
+
</div>
|
|
543
|
+
))}
|
|
544
|
+
</div>
|
|
545
|
+
</section>
|
|
546
|
+
</Split.Pane>
|
|
547
|
+
</Split>
|
|
548
|
+
) : null}
|
|
549
|
+
</PluginShell.Body>
|
|
550
|
+
</PluginShell>
|
|
500
551
|
);
|
|
501
552
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--background: 0 0% 100%;--foreground: 0 0% 3.9%;--card: 0 0% 100%;--card-foreground: 0 0% 3.9%;--popover: 0 0% 100%;--popover-foreground: 0 0% 3.9%;--primary: 0 0% 9%;--primary-foreground: 0 0% 98%;--secondary: 0 0% 96.1%;--secondary-foreground: 0 0% 9%;--muted: 0 0% 96.1%;--muted-foreground: 0 0% 45.1%;--accent: 0 0% 96.1%;--accent-foreground: 0 0% 9%;--destructive: 0 84.2% 60.2%;--destructive-foreground: 0 0% 98%;--border: 0 0% 14.9%;--input: 0 0% 14.9%;--ring: 0 0% 83.1%;--radius: .5rem}*{border-color:hsl(var(--border))}body{background-color:hsl(var(--background));color:hsl(var(--foreground))}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.relative{position:relative}.mt-1{margin-top:.25rem}.flex{display:flex}.inline-flex{display:inline-flex}.h-6{height:1.5rem}.h-screen{height:100vh}.w-11{width:2.75rem}.min-w-0{min-width:0px}.min-w-\[160px\]{min-width:160px}.min-w-\[240px\]{min-width:240px}.max-w-\[50\%\]{max-width:50%}.flex-1{flex:1 1 0%}.cursor-pointer{cursor:pointer}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-4{gap:1rem}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-gray-800>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(31 41 55 / var(--tw-divide-opacity, 1))}.overflow-auto{overflow:auto}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-dashed{border-style:dashed}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-gray-800{--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.border-violet-500\/60{border-color:#8b5cf699}.bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-800\/80{background-color:#1f2937cc}.bg-gray-800\/90{background-color:#1f2937e6}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-gray-950{--tw-bg-opacity: 1;background-color:rgb(3 7 18 / var(--tw-bg-opacity, 1))}.bg-gray-950\/80{background-color:#030712cc}.bg-violet-500\/10{background-color:#8b5cf61a}.p-2{padding:.5rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.text-right{text-align:right}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-violet-100{--tw-text-opacity: 1;color:rgb(237 233 254 / var(--tw-text-opacity, 1))}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-black\/10{--tw-shadow-color: rgb(0 0 0 / .1);--tw-shadow: var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}@keyframes enter{0%{opacity:var(--tw-enter-opacity, 1);transform:translate3d(var(--tw-enter-translate-x, 0),var(--tw-enter-translate-y, 0),0) scale3d(var(--tw-enter-scale, 1),var(--tw-enter-scale, 1),var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity, 1);transform:translate3d(var(--tw-exit-translate-x, 0),var(--tw-exit-translate-y, 0),0) scale3d(var(--tw-exit-scale, 1),var(--tw-exit-scale, 1),var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))}}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:#1f2937;border-radius:4px}::-webkit-scrollbar-thumb{background:#374151;border-radius:4px;border:1px solid #1f2937}::-webkit-scrollbar-thumb:hover{background:#4b5563}::-webkit-scrollbar-thumb:active{background:#6b7280}::-webkit-scrollbar-corner{background:#1f2937}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:left-\[2px\]:after{content:var(--tw-content);left:2px}.after\:top-\[2px\]:after{content:var(--tw-content);top:2px}.after\:h-5:after{content:var(--tw-content);height:1.25rem}.after\:w-5:after{content:var(--tw-content);width:1.25rem}.after\:rounded-full:after{content:var(--tw-content);border-radius:9999px}.after\:bg-white:after{content:var(--tw-content);--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.after\:transition-all:after{content:var(--tw-content);transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.after\:content-\[\'\'\]:after{--tw-content: "";content:var(--tw-content)}.hover\:bg-violet-500\/20:hover{background-color:#8b5cf633}.focus\:border-violet-500:focus{--tw-border-opacity: 1;border-color:rgb(139 92 246 / var(--tw-border-opacity, 1))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:border-gray-700:disabled{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.disabled\:bg-gray-800:disabled{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.disabled\:text-gray-500:disabled{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.disabled\:opacity-50:disabled{opacity:.5}.peer:checked~.peer-checked\:bg-violet-500{--tw-bg-opacity: 1;background-color:rgb(139 92 246 / var(--tw-bg-opacity, 1))}.peer:checked~.peer-checked\:after\:translate-x-5:after{content:var(--tw-content);--tw-translate-x: 1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.peer:disabled~.peer-disabled\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\:opacity-50{opacity:.5}
|