@embeddables/cli 0.1.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/README.md +116 -0
- package/bin/embeddables.mjs +2 -0
- package/dist/auth/index.d.ts +43 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +100 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +75 -0
- package/dist/commands/build-workbench.d.ts +5 -0
- package/dist/commands/build-workbench.d.ts.map +1 -0
- package/dist/commands/build-workbench.js +122 -0
- package/dist/commands/build.d.ts +7 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +22 -0
- package/dist/commands/dev.d.ts +11 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +153 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +112 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +18 -0
- package/dist/commands/pull.d.ts +7 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +97 -0
- package/dist/compiler/errors.d.ts +20 -0
- package/dist/compiler/errors.d.ts.map +1 -0
- package/dist/compiler/errors.js +35 -0
- package/dist/compiler/evalStatic.d.ts +3 -0
- package/dist/compiler/evalStatic.d.ts.map +1 -0
- package/dist/compiler/evalStatic.js +57 -0
- package/dist/compiler/flatten.js +1 -0
- package/dist/compiler/helpers/duplicateIds.d.ts +9 -0
- package/dist/compiler/helpers/duplicateIds.d.ts.map +1 -0
- package/dist/compiler/helpers/duplicateIds.js +71 -0
- package/dist/compiler/index.d.ts +16 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +934 -0
- package/dist/compiler/parsePage.d.ts +15 -0
- package/dist/compiler/parsePage.d.ts.map +1 -0
- package/dist/compiler/parsePage.js +562 -0
- package/dist/compiler/registry.d.ts +4 -0
- package/dist/compiler/registry.d.ts.map +1 -0
- package/dist/compiler/registry.js +44 -0
- package/dist/compiler/reverse.d.ts +17 -0
- package/dist/compiler/reverse.d.ts.map +1 -0
- package/dist/compiler/reverse.js +1632 -0
- package/dist/compiler/types.d.ts +21 -0
- package/dist/compiler/types.d.ts.map +1 -0
- package/dist/compiler/types.js +1 -0
- package/dist/components/index.d.ts +21 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +21 -0
- package/dist/components/primitives/BaseComponent.d.ts +32 -0
- package/dist/components/primitives/BaseComponent.d.ts.map +1 -0
- package/dist/components/primitives/BaseComponent.js +26 -0
- package/dist/components/primitives/BookMeeting.d.ts +18 -0
- package/dist/components/primitives/BookMeeting.d.ts.map +1 -0
- package/dist/components/primitives/BookMeeting.js +5 -0
- package/dist/components/primitives/Chart.d.ts +41 -0
- package/dist/components/primitives/Chart.d.ts.map +1 -0
- package/dist/components/primitives/Chart.js +5 -0
- package/dist/components/primitives/Container.d.ts +8 -0
- package/dist/components/primitives/Container.d.ts.map +1 -0
- package/dist/components/primitives/Container.js +5 -0
- package/dist/components/primitives/CustomButton.d.ts +37 -0
- package/dist/components/primitives/CustomButton.d.ts.map +1 -0
- package/dist/components/primitives/CustomButton.js +10 -0
- package/dist/components/primitives/CustomHTML.d.ts +8 -0
- package/dist/components/primitives/CustomHTML.d.ts.map +1 -0
- package/dist/components/primitives/CustomHTML.js +5 -0
- package/dist/components/primitives/FileUpload.d.ts +18 -0
- package/dist/components/primitives/FileUpload.d.ts.map +1 -0
- package/dist/components/primitives/FileUpload.js +16 -0
- package/dist/components/primitives/InputBox.d.ts +34 -0
- package/dist/components/primitives/InputBox.d.ts.map +1 -0
- package/dist/components/primitives/InputBox.js +25 -0
- package/dist/components/primitives/Lottie.d.ts +11 -0
- package/dist/components/primitives/Lottie.d.ts.map +1 -0
- package/dist/components/primitives/Lottie.js +5 -0
- package/dist/components/primitives/MediaEmbed.d.ts +13 -0
- package/dist/components/primitives/MediaEmbed.d.ts.map +1 -0
- package/dist/components/primitives/MediaEmbed.js +6 -0
- package/dist/components/primitives/MediaImage.d.ts +8 -0
- package/dist/components/primitives/MediaImage.d.ts.map +1 -0
- package/dist/components/primitives/MediaImage.js +5 -0
- package/dist/components/primitives/OptionSelector.d.ts +35 -0
- package/dist/components/primitives/OptionSelector.d.ts.map +1 -0
- package/dist/components/primitives/OptionSelector.js +8 -0
- package/dist/components/primitives/PaypalCheckout.d.ts +25 -0
- package/dist/components/primitives/PaypalCheckout.d.ts.map +1 -0
- package/dist/components/primitives/PaypalCheckout.js +5 -0
- package/dist/components/primitives/PlainText.d.ts +6 -0
- package/dist/components/primitives/PlainText.d.ts.map +1 -0
- package/dist/components/primitives/PlainText.js +5 -0
- package/dist/components/primitives/ProgressBar.d.ts +15 -0
- package/dist/components/primitives/ProgressBar.d.ts.map +1 -0
- package/dist/components/primitives/ProgressBar.js +5 -0
- package/dist/components/primitives/RichText.d.ts +6 -0
- package/dist/components/primitives/RichText.d.ts.map +1 -0
- package/dist/components/primitives/RichText.js +5 -0
- package/dist/components/primitives/RichTextMarkdown.d.ts +6 -0
- package/dist/components/primitives/RichTextMarkdown.d.ts.map +1 -0
- package/dist/components/primitives/RichTextMarkdown.js +5 -0
- package/dist/components/primitives/Rive.d.ts +16 -0
- package/dist/components/primitives/Rive.d.ts.map +1 -0
- package/dist/components/primitives/Rive.js +8 -0
- package/dist/components/primitives/StripeCheckout.d.ts +52 -0
- package/dist/components/primitives/StripeCheckout.d.ts.map +1 -0
- package/dist/components/primitives/StripeCheckout.js +5 -0
- package/dist/components/primitives/StripeCheckout2.d.ts +30 -0
- package/dist/components/primitives/StripeCheckout2.d.ts.map +1 -0
- package/dist/components/primitives/StripeCheckout2.js +7 -0
- package/dist/proxy/injectApiInterceptor.d.ts +6 -0
- package/dist/proxy/injectApiInterceptor.d.ts.map +1 -0
- package/dist/proxy/injectApiInterceptor.js +66 -0
- package/dist/proxy/injectReload.d.ts +2 -0
- package/dist/proxy/injectReload.d.ts.map +1 -0
- package/dist/proxy/injectReload.js +14 -0
- package/dist/proxy/injectWorkbench.d.ts +4 -0
- package/dist/proxy/injectWorkbench.d.ts.map +1 -0
- package/dist/proxy/injectWorkbench.js +16 -0
- package/dist/proxy/server.d.ts +11 -0
- package/dist/proxy/server.d.ts.map +1 -0
- package/dist/proxy/server.js +246 -0
- package/dist/proxy/sse.d.ts +5 -0
- package/dist/proxy/sse.d.ts.map +1 -0
- package/dist/proxy/sse.js +17 -0
- package/dist/types-builder.d.ts +800 -0
- package/dist/types-builder.d.ts.map +1 -0
- package/dist/types-builder.js +20 -0
- package/dist/workbench/ActionsPanel.d.ts +6 -0
- package/dist/workbench/ActionsPanel.d.ts.map +1 -0
- package/dist/workbench/ActionsPanel.js +47 -0
- package/dist/workbench/AutofillPanel.d.ts +6 -0
- package/dist/workbench/AutofillPanel.d.ts.map +1 -0
- package/dist/workbench/AutofillPanel.js +543 -0
- package/dist/workbench/ComputedFieldsPanel.d.ts +6 -0
- package/dist/workbench/ComputedFieldsPanel.d.ts.map +1 -0
- package/dist/workbench/ComputedFieldsPanel.js +31 -0
- package/dist/workbench/ExperimentsPanel.d.ts +6 -0
- package/dist/workbench/ExperimentsPanel.d.ts.map +1 -0
- package/dist/workbench/ExperimentsPanel.js +182 -0
- package/dist/workbench/FieldEditorPanel.d.ts +9 -0
- package/dist/workbench/FieldEditorPanel.d.ts.map +1 -0
- package/dist/workbench/FieldEditorPanel.js +650 -0
- package/dist/workbench/InspectorPanel.d.ts +6 -0
- package/dist/workbench/InspectorPanel.d.ts.map +1 -0
- package/dist/workbench/InspectorPanel.js +341 -0
- package/dist/workbench/PageNavigator.d.ts +6 -0
- package/dist/workbench/PageNavigator.d.ts.map +1 -0
- package/dist/workbench/PageNavigator.js +123 -0
- package/dist/workbench/SchemaPanel.d.ts +6 -0
- package/dist/workbench/SchemaPanel.d.ts.map +1 -0
- package/dist/workbench/SchemaPanel.js +222 -0
- package/dist/workbench/UserDataPanel.d.ts +6 -0
- package/dist/workbench/UserDataPanel.d.ts.map +1 -0
- package/dist/workbench/UserDataPanel.js +350 -0
- package/dist/workbench/WorkbenchApp.d.ts +6 -0
- package/dist/workbench/WorkbenchApp.d.ts.map +1 -0
- package/dist/workbench/WorkbenchApp.js +193 -0
- package/dist/workbench/cloudflare-worker/README.md +31 -0
- package/dist/workbench/cloudflare-worker/public/workbench.css +1614 -0
- package/dist/workbench/cloudflare-worker/public/workbench.js +77 -0
- package/dist/workbench/cloudflare-worker/worker.js +40 -0
- package/dist/workbench/cloudflare-worker/wrangler.toml +10 -0
- package/dist/workbench/index.d.ts +9 -0
- package/dist/workbench/index.d.ts.map +1 -0
- package/dist/workbench/index.js +44 -0
- package/dist/workbench/workbench.css +1614 -0
- package/dist/workbench/workbench.js +77 -0
- package/package.json +79 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
// Component types that store data in user data
|
|
4
|
+
const INPUT_COMPONENT_TYPES = ['InputBox', 'OptionSelector', 'FileUpload'];
|
|
5
|
+
const BUILT_IN_KEYS = [
|
|
6
|
+
'entryId',
|
|
7
|
+
'current_page_id',
|
|
8
|
+
'current_page_key',
|
|
9
|
+
'current_page_index',
|
|
10
|
+
'highest_page_reached_id',
|
|
11
|
+
'highest_page_reached_key',
|
|
12
|
+
'highest_page_reached_index',
|
|
13
|
+
];
|
|
14
|
+
function inferTypeFromInputType(inputType) {
|
|
15
|
+
switch (inputType) {
|
|
16
|
+
case 'number':
|
|
17
|
+
case 'range':
|
|
18
|
+
return 'number';
|
|
19
|
+
case 'switch':
|
|
20
|
+
case 'checkbox':
|
|
21
|
+
return 'boolean';
|
|
22
|
+
case 'date':
|
|
23
|
+
case 'time':
|
|
24
|
+
case 'month':
|
|
25
|
+
case 'email':
|
|
26
|
+
case 'phone':
|
|
27
|
+
case 'text':
|
|
28
|
+
case 'password':
|
|
29
|
+
case 'confirm':
|
|
30
|
+
default:
|
|
31
|
+
return 'string';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function escapeKey(key) {
|
|
35
|
+
// If the key contains special characters, wrap it in quotes
|
|
36
|
+
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
|
|
37
|
+
return key;
|
|
38
|
+
}
|
|
39
|
+
return `'${key}'`;
|
|
40
|
+
}
|
|
41
|
+
export function SchemaPanel({ embeddableId }) {
|
|
42
|
+
const [schema, setSchema] = useState('');
|
|
43
|
+
const [includeComments, setIncludeComments] = useState(false);
|
|
44
|
+
const savvy = window.Savvy;
|
|
45
|
+
const generateSchema = useCallback(() => {
|
|
46
|
+
const flowJson = savvy?.getFlowJson?.(embeddableId);
|
|
47
|
+
if (!flowJson) {
|
|
48
|
+
setSchema('// Unable to load flow JSON');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Collect component fields
|
|
52
|
+
const componentFields = new Map();
|
|
53
|
+
const processComponent = (comp) => {
|
|
54
|
+
if (!comp.key || !INPUT_COMPONENT_TYPES.includes(comp.type))
|
|
55
|
+
return;
|
|
56
|
+
const existing = componentFields.get(comp.key);
|
|
57
|
+
if (comp.type === 'InputBox') {
|
|
58
|
+
const inputComp = comp;
|
|
59
|
+
const tsType = inferTypeFromInputType(inputComp.input_type);
|
|
60
|
+
componentFields.set(comp.key, {
|
|
61
|
+
key: comp.key,
|
|
62
|
+
tsType: `${tsType} | null`,
|
|
63
|
+
componentType: 'InputBox',
|
|
64
|
+
inputType: inputComp.input_type,
|
|
65
|
+
comment: comp.label ? `${comp.label}` : undefined,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
else if (comp.type === 'OptionSelector') {
|
|
69
|
+
const optComp = comp;
|
|
70
|
+
const options = optComp.buttons?.map((b) => b.key).filter(Boolean);
|
|
71
|
+
let tsType;
|
|
72
|
+
if (options && options.length > 0) {
|
|
73
|
+
tsType = options.map((o) => `'${o}'`).join(' | ');
|
|
74
|
+
if (optComp.multiple) {
|
|
75
|
+
tsType = `(${tsType})[]`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
tsType = optComp.multiple ? 'string[]' : 'string';
|
|
80
|
+
}
|
|
81
|
+
componentFields.set(comp.key, {
|
|
82
|
+
key: comp.key,
|
|
83
|
+
tsType: `${tsType} | null`,
|
|
84
|
+
componentType: 'OptionSelector',
|
|
85
|
+
options,
|
|
86
|
+
comment: comp.label ? `${comp.label}` : undefined,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else if (comp.type === 'FileUpload') {
|
|
90
|
+
componentFields.set(comp.key, {
|
|
91
|
+
key: comp.key,
|
|
92
|
+
tsType: 'string | null', // File uploads typically store a URL or file reference
|
|
93
|
+
componentType: 'FileUpload',
|
|
94
|
+
comment: comp.label ? `${comp.label}` : undefined,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// Process nested components
|
|
98
|
+
if (comp.components) {
|
|
99
|
+
comp.components.forEach(processComponent);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
// Process all pages and global components
|
|
103
|
+
flowJson.pages?.forEach((page) => {
|
|
104
|
+
page.components?.forEach(processComponent);
|
|
105
|
+
});
|
|
106
|
+
flowJson.components?.forEach(processComponent);
|
|
107
|
+
// Collect computed fields
|
|
108
|
+
const computedFields = [];
|
|
109
|
+
flowJson.computedFields?.forEach((cf) => {
|
|
110
|
+
if (cf.key) {
|
|
111
|
+
computedFields.push({
|
|
112
|
+
key: cf.key,
|
|
113
|
+
tsType: 'unknown', // Computed fields can return any type
|
|
114
|
+
comment: 'Computed field',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
// Collect registered keys
|
|
119
|
+
const registeredFields = [];
|
|
120
|
+
if (flowJson.registered_keys) {
|
|
121
|
+
const keys = flowJson.registered_keys
|
|
122
|
+
.split(/[\n,]/)
|
|
123
|
+
.map((k) => k.trim())
|
|
124
|
+
.filter(Boolean);
|
|
125
|
+
keys.forEach((key) => {
|
|
126
|
+
if (!componentFields.has(key)) {
|
|
127
|
+
registeredFields.push({
|
|
128
|
+
key,
|
|
129
|
+
tsType: 'unknown',
|
|
130
|
+
comment: 'Registered key',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// Generate TypeScript
|
|
136
|
+
const lines = [];
|
|
137
|
+
lines.push('/**');
|
|
138
|
+
lines.push(` * UserData schema for embeddable: ${flowJson.title || flowJson.name || embeddableId}`);
|
|
139
|
+
lines.push(' * Auto-generated from flow configuration');
|
|
140
|
+
lines.push(' */');
|
|
141
|
+
lines.push('type UserData = {');
|
|
142
|
+
// Built-in keys
|
|
143
|
+
lines.push(' // Built-in keys');
|
|
144
|
+
lines.push(' entryId: string');
|
|
145
|
+
lines.push(' current_page_id: string');
|
|
146
|
+
lines.push(' current_page_key: string');
|
|
147
|
+
lines.push(' current_page_index: number');
|
|
148
|
+
lines.push(' highest_page_reached_id: string');
|
|
149
|
+
lines.push(' highest_page_reached_key: string');
|
|
150
|
+
lines.push(' highest_page_reached_index: number');
|
|
151
|
+
lines.push('');
|
|
152
|
+
// Component fields
|
|
153
|
+
if (componentFields.size > 0) {
|
|
154
|
+
lines.push(' // Component fields');
|
|
155
|
+
const sortedFields = Array.from(componentFields.values()).sort((a, b) => a.key.localeCompare(b.key));
|
|
156
|
+
for (const field of sortedFields) {
|
|
157
|
+
if (includeComments) {
|
|
158
|
+
if (field.comment) {
|
|
159
|
+
lines.push(` /** ${field.comment} (${field.componentType}${field.inputType ? `: ${field.inputType}` : ''}) */`);
|
|
160
|
+
}
|
|
161
|
+
else if (field.componentType) {
|
|
162
|
+
lines.push(` /** ${field.componentType}${field.inputType ? `: ${field.inputType}` : ''} */`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
lines.push(` ${escapeKey(field.key)}: ${field.tsType}`);
|
|
166
|
+
}
|
|
167
|
+
lines.push('');
|
|
168
|
+
}
|
|
169
|
+
// Computed fields
|
|
170
|
+
if (computedFields.length > 0) {
|
|
171
|
+
lines.push(' // Computed fields');
|
|
172
|
+
const sortedFields = computedFields.sort((a, b) => a.key.localeCompare(b.key));
|
|
173
|
+
for (const field of sortedFields) {
|
|
174
|
+
if (includeComments) {
|
|
175
|
+
lines.push(` /** ${field.comment} */`);
|
|
176
|
+
}
|
|
177
|
+
lines.push(` ${escapeKey(field.key)}?: ${field.tsType}`);
|
|
178
|
+
}
|
|
179
|
+
lines.push('');
|
|
180
|
+
}
|
|
181
|
+
// Registered keys
|
|
182
|
+
if (registeredFields.length > 0) {
|
|
183
|
+
lines.push(' // Registered keys');
|
|
184
|
+
const sortedFields = registeredFields.sort((a, b) => a.key.localeCompare(b.key));
|
|
185
|
+
for (const field of sortedFields) {
|
|
186
|
+
lines.push(` ${escapeKey(field.key)}?: ${field.tsType}`);
|
|
187
|
+
}
|
|
188
|
+
lines.push('');
|
|
189
|
+
}
|
|
190
|
+
// Index signature for any additional keys
|
|
191
|
+
lines.push(' // Additional dynamic keys');
|
|
192
|
+
lines.push(' [key: string]: unknown');
|
|
193
|
+
lines.push('}');
|
|
194
|
+
// Generate page keys type
|
|
195
|
+
const pageKeys = flowJson.pages?.map((p) => `'${p.key}'`) || [];
|
|
196
|
+
if (pageKeys.length > 0) {
|
|
197
|
+
lines.push('');
|
|
198
|
+
lines.push('/**');
|
|
199
|
+
lines.push(' * Available page keys in this flow');
|
|
200
|
+
lines.push(' */');
|
|
201
|
+
lines.push(`type PageKey = ${pageKeys.join(' | ')}`);
|
|
202
|
+
}
|
|
203
|
+
// Generate page IDs type
|
|
204
|
+
const pageIds = flowJson.pages?.map((p) => `'${p.id}'`) || [];
|
|
205
|
+
if (pageIds.length > 0) {
|
|
206
|
+
lines.push('');
|
|
207
|
+
lines.push('/**');
|
|
208
|
+
lines.push(' * Available page IDs in this flow');
|
|
209
|
+
lines.push(' */');
|
|
210
|
+
lines.push(`type PageId = ${pageIds.join(' | ')}`);
|
|
211
|
+
}
|
|
212
|
+
setSchema(lines.join('\n'));
|
|
213
|
+
}, [embeddableId, savvy, includeComments]);
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
const timer = setTimeout(generateSchema, 100);
|
|
216
|
+
return () => clearTimeout(timer);
|
|
217
|
+
}, [generateSchema]);
|
|
218
|
+
const handleCopy = () => {
|
|
219
|
+
navigator.clipboard.writeText(schema);
|
|
220
|
+
};
|
|
221
|
+
return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-[11px] text-slate-400", children: "TypeScript type definition" }), _jsxs("label", { className: "flex cursor-pointer items-center gap-1.5 text-[11px] text-slate-400", children: [_jsx("input", { type: "checkbox", checked: includeComments, onChange: (e) => setIncludeComments(e.target.checked), className: "h-3 w-3 rounded border-slate-600 bg-slate-800 text-sky-500 focus:ring-sky-500/50" }), "Include component-level comments"] })] }), _jsx("button", { type: "button", onClick: handleCopy, className: "cursor-pointer rounded-lg bg-slate-700 px-2 py-1 text-[10px] font-semibold text-slate-200 ring-1 ring-inset ring-slate-600 hover:bg-slate-600 hover:text-white", children: "Copy" })] }), _jsx("pre", { className: "min-h-0 flex-1 overflow-auto rounded-xl bg-slate-950/60 p-3 font-mono text-xs leading-5 text-slate-100 ring-1 ring-inset ring-white/10", children: schema })] }));
|
|
222
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserDataPanel.d.ts","sourceRoot":"","sources":["../../src/workbench/UserDataPanel.tsx"],"names":[],"mappings":"AAIA,KAAK,kBAAkB,GAAG;IACxB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AAqGD,wBAAgB,aAAa,CAAC,EAAE,YAAY,EAAE,EAAE,kBAAkB,2CAmcjE"}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { FieldEditorPanel } from './FieldEditorPanel';
|
|
4
|
+
import { SchemaPanel } from './SchemaPanel';
|
|
5
|
+
const STORAGE_KEY_PREFIX = 'embeddables:workbench:';
|
|
6
|
+
const USERDATA_UPDATED_EVENT = 'embeddables:userdata_updated';
|
|
7
|
+
const FIELD_UPDATE_DEBOUNCE_MS = 350;
|
|
8
|
+
function getStorageKey(embeddableId) {
|
|
9
|
+
return `${STORAGE_KEY_PREFIX}${embeddableId}`;
|
|
10
|
+
}
|
|
11
|
+
function loadStorage(embeddableId) {
|
|
12
|
+
try {
|
|
13
|
+
const raw = window.localStorage.getItem(getStorageKey(embeddableId));
|
|
14
|
+
if (!raw)
|
|
15
|
+
return {};
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function saveStorage(embeddableId, value) {
|
|
23
|
+
try {
|
|
24
|
+
window.localStorage.setItem(getStorageKey(embeddableId), JSON.stringify(value));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// ignore
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function filterJsonByKey(obj, filterText) {
|
|
31
|
+
if (!filterText.trim())
|
|
32
|
+
return obj;
|
|
33
|
+
if (typeof obj !== 'object' || obj === null)
|
|
34
|
+
return obj;
|
|
35
|
+
const lowerFilter = filterText.toLowerCase();
|
|
36
|
+
// For arrays, filter each item and only keep non-empty results
|
|
37
|
+
if (Array.isArray(obj)) {
|
|
38
|
+
const filtered = obj
|
|
39
|
+
.map((item) => filterJsonByKey(item, filterText))
|
|
40
|
+
.filter((item) => {
|
|
41
|
+
if (typeof item !== 'object' || item === null)
|
|
42
|
+
return false;
|
|
43
|
+
if (Array.isArray(item))
|
|
44
|
+
return item.length > 0;
|
|
45
|
+
return Object.keys(item).length > 0;
|
|
46
|
+
});
|
|
47
|
+
return filtered;
|
|
48
|
+
}
|
|
49
|
+
const result = {};
|
|
50
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
51
|
+
if (key.toLowerCase().includes(lowerFilter)) {
|
|
52
|
+
// Key matches - include the entire value unfiltered
|
|
53
|
+
result[key] = value;
|
|
54
|
+
}
|
|
55
|
+
else if (typeof value === 'object' && value !== null) {
|
|
56
|
+
// Key doesn't match - recurse to find nested matches
|
|
57
|
+
const filtered = filterJsonByKey(value, filterText);
|
|
58
|
+
if (Array.isArray(filtered)) {
|
|
59
|
+
if (filtered.length > 0)
|
|
60
|
+
result[key] = filtered;
|
|
61
|
+
}
|
|
62
|
+
else if (filtered !== null && Object.keys(filtered).length > 0) {
|
|
63
|
+
result[key] = filtered;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
export function UserDataPanel({ embeddableId }) {
|
|
70
|
+
const [rawJson, setRawJson] = useState('');
|
|
71
|
+
const [error, setError] = useState(null);
|
|
72
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
73
|
+
const [personas, setPersonas] = useState([]);
|
|
74
|
+
const [personaError, setPersonaError] = useState(null);
|
|
75
|
+
const [keyFilter, setKeyFilter] = useState('');
|
|
76
|
+
const [activeTab, setActiveTab] = useState('fields');
|
|
77
|
+
const savvy = window.Savvy;
|
|
78
|
+
const pendingFieldUpdatesRef = useRef(new Map());
|
|
79
|
+
const refresh = useCallback(async () => {
|
|
80
|
+
setError(null);
|
|
81
|
+
if (!savvy?.getUserData) {
|
|
82
|
+
setError('window.Savvy.getUserData is not available.');
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const data = savvy.getUserData(embeddableId);
|
|
87
|
+
const merged = typeof data === 'object' && data !== null ? { ...data } : {};
|
|
88
|
+
// Overlay any pending (not-yet-sent) field updates so we don't overwrite in-progress typing
|
|
89
|
+
pendingFieldUpdatesRef.current.forEach((entry, key) => {
|
|
90
|
+
merged[key] = entry.value;
|
|
91
|
+
});
|
|
92
|
+
const nextRawJson = JSON.stringify(merged, null, 2);
|
|
93
|
+
setRawJson(nextRawJson);
|
|
94
|
+
return nextRawJson;
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
setError(err?.message ?? String(err));
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}, [embeddableId, savvy]);
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
const timer = setTimeout(() => {
|
|
103
|
+
void refresh();
|
|
104
|
+
const storage = loadStorage(embeddableId);
|
|
105
|
+
setPersonas(storage.personas ?? []);
|
|
106
|
+
}, 500);
|
|
107
|
+
return () => clearTimeout(timer);
|
|
108
|
+
}, [embeddableId, refresh]);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
const handler = (event) => {
|
|
111
|
+
// Optional filtering: if the event includes an embeddableId, only react to ours.
|
|
112
|
+
const detail = event?.detail;
|
|
113
|
+
const eventEmbeddableId = detail?.embeddableId;
|
|
114
|
+
if (typeof eventEmbeddableId === 'string' &&
|
|
115
|
+
eventEmbeddableId.length > 0 &&
|
|
116
|
+
eventEmbeddableId !== embeddableId) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
void refresh();
|
|
120
|
+
};
|
|
121
|
+
window.addEventListener(USERDATA_UPDATED_EVENT, handler);
|
|
122
|
+
return () => window.removeEventListener(USERDATA_UPDATED_EVENT, handler);
|
|
123
|
+
}, [embeddableId, refresh]);
|
|
124
|
+
// Cleanup pending debounce timers on unmount
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
return () => {
|
|
127
|
+
pendingFieldUpdatesRef.current.forEach((entry) => clearTimeout(entry.timer));
|
|
128
|
+
pendingFieldUpdatesRef.current.clear();
|
|
129
|
+
};
|
|
130
|
+
}, []);
|
|
131
|
+
const handleSave = () => {
|
|
132
|
+
if (!savvy?.setUserData) {
|
|
133
|
+
setError('window.Savvy.setUserData is not available.');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
setIsSaving(true);
|
|
137
|
+
setError(null);
|
|
138
|
+
try {
|
|
139
|
+
const parsed = rawJson.trim() ? JSON.parse(rawJson) : {};
|
|
140
|
+
savvy.setUserData(embeddableId, parsed);
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
setError(err?.message ?? String(err));
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
setIsSaving(false);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const handleSaveAndGoToPage = () => {
|
|
150
|
+
let currentPageId = '';
|
|
151
|
+
try {
|
|
152
|
+
const parsed = rawJson.trim() ? JSON.parse(rawJson) : null;
|
|
153
|
+
currentPageId = parsed?.current_page_id ?? '';
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// ignore parse errors for default value
|
|
157
|
+
}
|
|
158
|
+
const pageId = window.prompt('Enter the page ID to navigate to:', currentPageId);
|
|
159
|
+
if (!pageId)
|
|
160
|
+
return;
|
|
161
|
+
if (!savvy?.setUserData) {
|
|
162
|
+
setError('window.Savvy.setUserData is not available.');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (!savvy?.goToPage) {
|
|
166
|
+
setError('window.Savvy.goToPage is not available.');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
setIsSaving(true);
|
|
170
|
+
setError(null);
|
|
171
|
+
try {
|
|
172
|
+
const parsed = rawJson.trim() ? JSON.parse(rawJson) : {};
|
|
173
|
+
savvy.setUserData(embeddableId, parsed);
|
|
174
|
+
savvy.goToPage(embeddableId, pageId.trim());
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
setError(err?.message ?? String(err));
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
setIsSaving(false);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const handleReset = async () => {
|
|
184
|
+
await refresh();
|
|
185
|
+
if (!savvy?.resetUserData) {
|
|
186
|
+
setError('window.Savvy.resetUserData is not available.');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
setIsSaving(true);
|
|
190
|
+
setError(null);
|
|
191
|
+
try {
|
|
192
|
+
savvy.resetUserData(embeddableId);
|
|
193
|
+
// Hack - find all inputs inside the main page and set them to empty string
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
const inputs = document.querySelectorAll('savvy input');
|
|
196
|
+
for (const input of inputs) {
|
|
197
|
+
;
|
|
198
|
+
input.value = '';
|
|
199
|
+
}
|
|
200
|
+
const textareas = document.querySelectorAll('savvy textarea');
|
|
201
|
+
for (const textarea of textareas) {
|
|
202
|
+
;
|
|
203
|
+
textarea.value = '';
|
|
204
|
+
}
|
|
205
|
+
}, 100);
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
setError(err?.message ?? String(err));
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
setIsSaving(false);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
const handleSavePersona = async () => {
|
|
215
|
+
const refreshedRawJson = await refresh();
|
|
216
|
+
const latestRawJson = refreshedRawJson ?? rawJson;
|
|
217
|
+
setPersonaError(null);
|
|
218
|
+
let name = window.prompt('Save current user data as persona name:', '');
|
|
219
|
+
if (!name)
|
|
220
|
+
return;
|
|
221
|
+
name = name.trim();
|
|
222
|
+
if (!name) {
|
|
223
|
+
setPersonaError('Persona name cannot be empty.');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
const data = latestRawJson.trim() ? JSON.parse(latestRawJson) : {};
|
|
228
|
+
const existingIndex = personas.findIndex((p) => p.name === name);
|
|
229
|
+
let nextPersonas;
|
|
230
|
+
if (existingIndex >= 0) {
|
|
231
|
+
nextPersonas = personas.slice();
|
|
232
|
+
nextPersonas[existingIndex] = { name, data };
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
nextPersonas = [...personas, { name, data }];
|
|
236
|
+
}
|
|
237
|
+
setPersonas(nextPersonas);
|
|
238
|
+
const storage = loadStorage(embeddableId);
|
|
239
|
+
saveStorage(embeddableId, {
|
|
240
|
+
...storage,
|
|
241
|
+
personas: nextPersonas,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
setPersonaError(err?.message ?? String(err));
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
const applyPersona = async (persona) => {
|
|
249
|
+
await refresh();
|
|
250
|
+
if (!savvy?.setUserData) {
|
|
251
|
+
setPersonaError('window.Savvy.setUserData is not available.');
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
setPersonaError(null);
|
|
255
|
+
try {
|
|
256
|
+
await savvy.setUserData(embeddableId, persona.data);
|
|
257
|
+
void refresh();
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
setPersonaError(err?.message ?? String(err));
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
const applyPersonaAndGoToPage = async (persona) => {
|
|
264
|
+
const pageId = persona.data?.current_page_id;
|
|
265
|
+
if (!pageId)
|
|
266
|
+
return;
|
|
267
|
+
await refresh();
|
|
268
|
+
if (!savvy?.setUserData) {
|
|
269
|
+
setPersonaError('window.Savvy.setUserData is not available.');
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (!savvy?.goToPage) {
|
|
273
|
+
setPersonaError('window.Savvy.goToPage is not available.');
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
setPersonaError(null);
|
|
277
|
+
try {
|
|
278
|
+
await savvy.setUserData(embeddableId, persona.data);
|
|
279
|
+
savvy.goToPage(embeddableId, pageId.trim());
|
|
280
|
+
void refresh();
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
setPersonaError(err?.message ?? String(err));
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const deletePersona = (persona) => {
|
|
287
|
+
const nextPersonas = personas.filter((p) => p.name !== persona.name);
|
|
288
|
+
setPersonas(nextPersonas);
|
|
289
|
+
const storage = loadStorage(embeddableId);
|
|
290
|
+
saveStorage(embeddableId, {
|
|
291
|
+
...storage,
|
|
292
|
+
personas: nextPersonas,
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
const handleFieldUpdate = useCallback((key, value) => {
|
|
296
|
+
if (!savvy?.setUserData) {
|
|
297
|
+
setError('window.Savvy.setUserData is not available.');
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
// Optimistic update: merge into rawJson immediately so the input doesn't lag
|
|
302
|
+
setRawJson((prev) => {
|
|
303
|
+
try {
|
|
304
|
+
const obj = prev.trim() ? JSON.parse(prev) : {};
|
|
305
|
+
obj[key] = value;
|
|
306
|
+
return JSON.stringify(obj, null, 2);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return prev;
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
// Debounce setUserData to avoid flooding the embeddable and prevent
|
|
313
|
+
// the userdata_updated event ping-pong from overwriting in-progress typing
|
|
314
|
+
const existing = pendingFieldUpdatesRef.current.get(key);
|
|
315
|
+
if (existing)
|
|
316
|
+
clearTimeout(existing.timer);
|
|
317
|
+
const timer = setTimeout(() => {
|
|
318
|
+
const entry = pendingFieldUpdatesRef.current.get(key);
|
|
319
|
+
const valueToSend = entry?.value;
|
|
320
|
+
pendingFieldUpdatesRef.current.delete(key);
|
|
321
|
+
try {
|
|
322
|
+
savvy.setUserData(embeddableId, { [key]: valueToSend });
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
setError(err?.message ?? String(err));
|
|
326
|
+
}
|
|
327
|
+
}, FIELD_UPDATE_DEBOUNCE_MS);
|
|
328
|
+
pendingFieldUpdatesRef.current.set(key, { value, timer });
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
setError(err?.message ?? String(err));
|
|
332
|
+
}
|
|
333
|
+
}, [embeddableId, savvy]);
|
|
334
|
+
return (_jsxs("div", { className: "mx-auto flex h-full w-full max-w-6xl flex-col", children: [_jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-xs font-semibold tracking-wide text-slate-100", children: "User data" }), _jsx("div", { className: "mt-0.5 text-[11px] text-slate-400", children: "View and edit the JSON stored for this embeddable. Use personas to quickly switch states." })] }), _jsxs("div", { className: "flex flex-wrap items-center justify-start gap-2 sm:justify-end", children: [_jsx("button", { type: "button", onClick: handleSavePersona, className: "inline-flex cursor-pointer items-center gap-1.5 rounded-lg bg-slate-700 px-2.5 py-1.5 text-xs font-semibold text-slate-200 ring-1 ring-inset ring-slate-600 hover:bg-slate-600 hover:text-white", children: "Save persona" }), _jsx("button", { type: "button", onClick: handleReset, disabled: isSaving, className: "inline-flex cursor-pointer items-center gap-1.5 rounded-lg bg-slate-700 px-2.5 py-1.5 text-xs font-semibold text-rose-300 ring-1 ring-inset ring-slate-600 hover:bg-slate-600 hover:text-rose-200 disabled:cursor-not-allowed disabled:opacity-60", children: "Reset User Data" })] })] }), (error || personaError) && (_jsx("div", { className: "mt-3 rounded-xl bg-rose-500/10 px-3 py-2 text-xs text-rose-200 ring-1 ring-inset ring-rose-500/20", children: error || personaError })), _jsxs("div", { className: "mt-4 flex min-h-0 flex-1 flex-col gap-4 lg:flex-row", children: [_jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [_jsxs("div", { className: "mb-2 flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => setActiveTab('fields'), className: `cursor-pointer rounded-lg px-2.5 py-1.5 text-[11px] font-semibold uppercase tracking-wider transition-colors ${activeTab === 'fields'
|
|
335
|
+
? 'bg-slate-700 text-slate-100 ring-1 ring-inset ring-slate-600'
|
|
336
|
+
: 'text-slate-400 hover:bg-slate-800 hover:text-slate-300'}`, children: "Field Editor" }), _jsx("button", { type: "button", onClick: () => setActiveTab('json'), className: `cursor-pointer rounded-lg px-2.5 py-1.5 text-[11px] font-semibold uppercase tracking-wider transition-colors ${activeTab === 'json'
|
|
337
|
+
? 'bg-slate-700 text-slate-100 ring-1 ring-inset ring-slate-600'
|
|
338
|
+
: 'text-slate-400 hover:bg-slate-800 hover:text-slate-300'}`, children: "JSON Editor" }), _jsx("button", { type: "button", onClick: () => setActiveTab('schema'), className: `cursor-pointer rounded-lg px-2.5 py-1.5 text-[11px] font-semibold uppercase tracking-wider transition-colors ${activeTab === 'schema'
|
|
339
|
+
? 'bg-slate-700 text-slate-100 ring-1 ring-inset ring-slate-600'
|
|
340
|
+
: 'text-slate-400 hover:bg-slate-800 hover:text-slate-300'}`, children: "Schema" })] }), activeTab !== 'schema' && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("input", { type: "text", value: keyFilter, onChange: (e) => setKeyFilter(e.target.value), placeholder: "Filter by key...", className: "w-32 rounded-lg bg-slate-800 px-2 py-1.5 text-xs text-slate-100 ring-1 ring-inset ring-slate-600 placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-sky-500/60" }), activeTab === 'json' && (_jsx("button", { type: "button", onClick: handleSave, disabled: isSaving || rawJson.trim() === '' || keyFilter.trim() !== '', className: "inline-flex cursor-pointer items-center gap-1.5 rounded-lg bg-slate-700 px-2.5 py-1.5 text-xs font-semibold text-teal-300 ring-1 ring-inset ring-slate-600 hover:bg-slate-600 hover:text-teal-200 disabled:cursor-not-allowed disabled:opacity-60", children: "Update" }))] }))] }), activeTab === 'json' ? (keyFilter.trim() ? (_jsx("pre", { className: "min-h-0 w-full flex-1 overflow-auto rounded-xl bg-slate-950/60 p-3 font-mono text-xs leading-5 text-slate-100 ring-1 ring-inset ring-white/10", children: (() => {
|
|
341
|
+
try {
|
|
342
|
+
const parsed = rawJson.trim() ? JSON.parse(rawJson) : {};
|
|
343
|
+
const filtered = filterJsonByKey(parsed, keyFilter);
|
|
344
|
+
return JSON.stringify(filtered, null, 2);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return 'Invalid JSON';
|
|
348
|
+
}
|
|
349
|
+
})() })) : (_jsx("textarea", { value: rawJson, onChange: (e) => setRawJson(e.target.value), spellCheck: false, className: "min-h-0 w-full flex-1 resize-none rounded-xl bg-slate-950/60 p-3 font-mono text-xs leading-5 text-slate-100 ring-1 ring-inset ring-white/10 placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-sky-500/60" }))) : activeTab === 'fields' ? (_jsx(FieldEditorPanel, { embeddableId: embeddableId, rawJson: rawJson, onUpdateUserData: handleFieldUpdate, keyFilter: keyFilter })) : (_jsx(SchemaPanel, { embeddableId: embeddableId }))] }), personas.length > 0 && (_jsxs("div", { className: "lg:w-110 lg:shrink-0", children: [_jsx("div", { className: "mb-2 text-[11px] font-semibold uppercase tracking-wider text-slate-400", children: "Personas" }), _jsx("div", { className: "flex flex-wrap gap-2 lg:grid lg:grid-cols-2 lg:gap-2", children: personas.map((persona) => (_jsxs("div", { className: "flex items-center gap-2 rounded-xl bg-white/5 px-2 py-1.5 ring-1 ring-inset ring-white/10 lg:flex-col lg:items-start lg:gap-1.5 lg:p-2", children: [_jsx("span", { className: "max-w-[220px] truncate text-xs text-slate-100 lg:max-w-full", children: persona.name }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("button", { type: "button", onClick: () => applyPersona(persona), className: "cursor-pointer rounded-lg bg-slate-700 px-2 py-1 text-[11px] font-semibold text-teal-300 ring-1 ring-inset ring-slate-600 hover:bg-slate-600 hover:text-teal-200", children: "Apply" }), _jsx("button", { type: "button", onClick: () => applyPersonaAndGoToPage(persona), className: "cursor-pointer whitespace-nowrap rounded-lg bg-slate-700 px-2 py-1 text-[11px] font-semibold text-slate-200 ring-1 ring-inset ring-slate-600 hover:bg-slate-600 hover:text-white", children: "Apply + Page" }), _jsx("button", { type: "button", onClick: () => deletePersona(persona), className: "cursor-pointer rounded-lg bg-white/5 px-2 py-1 text-[11px] font-medium text-slate-200 ring-1 ring-inset ring-white/10 hover:bg-white/10", children: "Delete" })] })] }, persona.name))) })] }))] })] }));
|
|
350
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkbenchApp.d.ts","sourceRoot":"","sources":["../../src/workbench/WorkbenchApp.tsx"],"names":[],"mappings":"AAuBA,KAAK,iBAAiB,GAAG;IACvB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AAqCD,wBAAgB,YAAY,CAAC,EAAE,YAAY,EAAE,EAAE,iBAAiB,2CA6S/D"}
|