@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,543 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
// Generate realistic test data based on input type
|
|
4
|
+
function generateTestValue(comp) {
|
|
5
|
+
const inputComp = comp;
|
|
6
|
+
const inputType = inputComp.input_type || 'text';
|
|
7
|
+
switch (inputType) {
|
|
8
|
+
case 'email':
|
|
9
|
+
return 'test@example.com';
|
|
10
|
+
case 'phone':
|
|
11
|
+
return '+1 (555) 123-4567';
|
|
12
|
+
case 'number':
|
|
13
|
+
const min = inputComp.range_min !== undefined ? Number(inputComp.range_min) : 0;
|
|
14
|
+
const max = inputComp.range_max !== undefined ? Number(inputComp.range_max) : 100;
|
|
15
|
+
return String(Math.floor((min + max) / 2));
|
|
16
|
+
case 'date':
|
|
17
|
+
return new Date().toISOString().split('T')[0];
|
|
18
|
+
case 'time':
|
|
19
|
+
return '12:00';
|
|
20
|
+
case 'month':
|
|
21
|
+
const now = new Date();
|
|
22
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
|
23
|
+
case 'password':
|
|
24
|
+
case 'confirm':
|
|
25
|
+
return 'TestPassword123!';
|
|
26
|
+
case 'range':
|
|
27
|
+
const rangeMin = inputComp.range_min !== undefined ? Number(inputComp.range_min) : 0;
|
|
28
|
+
const rangeMax = inputComp.range_max !== undefined ? Number(inputComp.range_max) : 100;
|
|
29
|
+
return String(Math.floor((rangeMin + rangeMax) / 2));
|
|
30
|
+
case 'switch':
|
|
31
|
+
case 'checkbox':
|
|
32
|
+
return 'checked';
|
|
33
|
+
case 'text':
|
|
34
|
+
default:
|
|
35
|
+
// Generate contextual text based on key or label
|
|
36
|
+
const key = comp.key.toLowerCase();
|
|
37
|
+
const label = (comp.label || '').toLowerCase();
|
|
38
|
+
const context = `${key} ${label}`;
|
|
39
|
+
if (context.includes('name') && context.includes('first'))
|
|
40
|
+
return 'John';
|
|
41
|
+
if (context.includes('name') && context.includes('last'))
|
|
42
|
+
return 'Doe';
|
|
43
|
+
if (context.includes('name') && context.includes('full'))
|
|
44
|
+
return 'John Doe';
|
|
45
|
+
if (context.includes('name'))
|
|
46
|
+
return 'John Doe';
|
|
47
|
+
if (context.includes('address') || context.includes('street'))
|
|
48
|
+
return '123 Main Street';
|
|
49
|
+
if (context.includes('city'))
|
|
50
|
+
return 'San Francisco';
|
|
51
|
+
if (context.includes('state'))
|
|
52
|
+
return 'California';
|
|
53
|
+
if (context.includes('zip') || context.includes('postal'))
|
|
54
|
+
return '94102';
|
|
55
|
+
if (context.includes('country'))
|
|
56
|
+
return 'United States';
|
|
57
|
+
if (context.includes('company') || context.includes('business'))
|
|
58
|
+
return 'Acme Inc.';
|
|
59
|
+
if (context.includes('title') || context.includes('job'))
|
|
60
|
+
return 'Software Engineer';
|
|
61
|
+
if (context.includes('website') || context.includes('url'))
|
|
62
|
+
return 'https://example.com';
|
|
63
|
+
if (context.includes('age'))
|
|
64
|
+
return '30';
|
|
65
|
+
if (context.includes('comment') || context.includes('message') || context.includes('note'))
|
|
66
|
+
return 'This is a test message for autofill purposes.';
|
|
67
|
+
return 'Test input';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Fill an InputBox component
|
|
71
|
+
function fillInputBox(componentId, value, inputType) {
|
|
72
|
+
const element = document.querySelector(`.cid-${componentId}`);
|
|
73
|
+
if (!element)
|
|
74
|
+
return false;
|
|
75
|
+
if (inputType === 'switch' || inputType === 'checkbox') {
|
|
76
|
+
const checkbox = element.querySelector('input[type="checkbox"]');
|
|
77
|
+
if (checkbox && !checkbox.checked) {
|
|
78
|
+
checkbox.click();
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const input = element.querySelector('input, textarea');
|
|
84
|
+
if (!input)
|
|
85
|
+
return false;
|
|
86
|
+
// Focus and set value
|
|
87
|
+
input.focus();
|
|
88
|
+
// Use native value setter to trigger React's onChange
|
|
89
|
+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
|
|
90
|
+
const nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')?.set;
|
|
91
|
+
if (input instanceof HTMLTextAreaElement && nativeTextAreaValueSetter) {
|
|
92
|
+
nativeTextAreaValueSetter.call(input, value);
|
|
93
|
+
}
|
|
94
|
+
else if (nativeInputValueSetter) {
|
|
95
|
+
nativeInputValueSetter.call(input, value);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
input.value = value;
|
|
99
|
+
}
|
|
100
|
+
// Dispatch input event
|
|
101
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
102
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
103
|
+
// Blur to trigger validation
|
|
104
|
+
input.blur();
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
// Fill an OptionSelector component by clicking the first (or a random) option
|
|
108
|
+
function fillOptionSelector(componentId, buttons, random = false) {
|
|
109
|
+
const element = document.querySelector(`.cid-${componentId}`);
|
|
110
|
+
if (!element)
|
|
111
|
+
return false;
|
|
112
|
+
// Check if it's a dropdown
|
|
113
|
+
const select = element.querySelector('select');
|
|
114
|
+
if (select) {
|
|
115
|
+
const options = Array.from(select.options).filter((opt) => opt.value);
|
|
116
|
+
if (options.length === 0)
|
|
117
|
+
return false;
|
|
118
|
+
const optionIndex = random ? Math.floor(Math.random() * options.length) : 0;
|
|
119
|
+
select.value = options[optionIndex].value;
|
|
120
|
+
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
// Look for ButtonCard elements (the main clickable elements in OptionSelector)
|
|
124
|
+
// These are divs with data-action that handle clicks via Stimulus controller
|
|
125
|
+
const buttonCards = element.querySelectorAll('.ButtonCard:not(.selected), [data-components--option-selector-target="option"]:not(.selected)');
|
|
126
|
+
if (buttonCards.length > 0) {
|
|
127
|
+
const cardIndex = random ? Math.floor(Math.random() * buttonCards.length) : 0;
|
|
128
|
+
const card = buttonCards[cardIndex];
|
|
129
|
+
if (card) {
|
|
130
|
+
card.click();
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Fallback: try clicking any button element
|
|
135
|
+
const buttonElements = element.querySelectorAll('button:not([disabled])');
|
|
136
|
+
if (buttonElements.length > 0) {
|
|
137
|
+
const buttonIndex = random ? Math.floor(Math.random() * buttonElements.length) : 0;
|
|
138
|
+
const button = buttonElements[buttonIndex];
|
|
139
|
+
if (button) {
|
|
140
|
+
button.click();
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
export function AutofillPanel({ embeddableId }) {
|
|
147
|
+
const [inputComponents, setInputComponents] = useState([]);
|
|
148
|
+
const [currentPageKey, setCurrentPageKey] = useState('');
|
|
149
|
+
const [error, setError] = useState(null);
|
|
150
|
+
const [isAutofilling, setIsAutofilling] = useState(false);
|
|
151
|
+
const [lastAutofillTime, setLastAutofillTime] = useState(null);
|
|
152
|
+
const [useRandomOptions, setUseRandomOptions] = useState(false);
|
|
153
|
+
const savvy = window.Savvy;
|
|
154
|
+
// Scan current page for input components
|
|
155
|
+
const scanPage = useCallback(() => {
|
|
156
|
+
setError(null);
|
|
157
|
+
if (!savvy?.getFlowJson) {
|
|
158
|
+
setError('window.Savvy.getFlowJson is not available.');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (!savvy?.getUserData) {
|
|
162
|
+
setError('window.Savvy.getUserData is not available.');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const flowJson = savvy.getFlowJson(embeddableId);
|
|
166
|
+
if (!flowJson) {
|
|
167
|
+
setError('Could not retrieve flow JSON.');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const userData = savvy.getUserData(embeddableId);
|
|
171
|
+
if (!userData) {
|
|
172
|
+
setError('Could not retrieve user data.');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const currentPageId = userData.current_page_id;
|
|
176
|
+
setCurrentPageKey(userData.current_page_key || '');
|
|
177
|
+
// Find the current page
|
|
178
|
+
const currentPage = flowJson.pages?.find((p) => p.id === currentPageId);
|
|
179
|
+
if (!currentPage) {
|
|
180
|
+
setError(`Current page "${currentPageId}" not found in flow.`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Collect all input components from the current page
|
|
184
|
+
const inputs = [];
|
|
185
|
+
const processComponent = (comp) => {
|
|
186
|
+
if (comp.type === 'InputBox') {
|
|
187
|
+
const inputComp = comp;
|
|
188
|
+
const element = document.querySelector(`.cid-${comp.id}`);
|
|
189
|
+
const input = element?.querySelector('input, textarea');
|
|
190
|
+
const inputType = inputComp.input_type || 'text';
|
|
191
|
+
// Get current value
|
|
192
|
+
let currentValue;
|
|
193
|
+
if (inputType === 'switch' || inputType === 'checkbox') {
|
|
194
|
+
const checkbox = element?.querySelector('input[type="checkbox"]');
|
|
195
|
+
currentValue = checkbox?.checked ? 'checked' : undefined;
|
|
196
|
+
}
|
|
197
|
+
else if (input?.value) {
|
|
198
|
+
currentValue = input.value;
|
|
199
|
+
}
|
|
200
|
+
inputs.push({
|
|
201
|
+
id: comp.id,
|
|
202
|
+
key: comp.key,
|
|
203
|
+
type: comp.type,
|
|
204
|
+
label: comp.label,
|
|
205
|
+
inputType,
|
|
206
|
+
filled: !!input?.value ||
|
|
207
|
+
(inputType === 'checkbox' || inputType === 'switch'
|
|
208
|
+
? !!element?.querySelector('input[type="checkbox"]')?.checked
|
|
209
|
+
: false),
|
|
210
|
+
currentValue,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
else if (comp.type === 'OptionSelector') {
|
|
214
|
+
const optComp = comp;
|
|
215
|
+
const element = document.querySelector(`.cid-${comp.id}`);
|
|
216
|
+
// Check if an option is selected by looking for the 'selected' class on button elements
|
|
217
|
+
// This is the most reliable indicator of the visual selection state
|
|
218
|
+
let hasSelection = false;
|
|
219
|
+
let currentValue;
|
|
220
|
+
if (element) {
|
|
221
|
+
// Check for button-style OptionSelector (ButtonCard with 'selected' class)
|
|
222
|
+
const selectedButton = element.querySelector('.ButtonCard.selected, [data-components--option-selector-target="option"].selected');
|
|
223
|
+
if (selectedButton) {
|
|
224
|
+
hasSelection = true;
|
|
225
|
+
// Try to get the text content of the selected button
|
|
226
|
+
currentValue = selectedButton.textContent?.trim() || undefined;
|
|
227
|
+
}
|
|
228
|
+
// Check for dropdown-style OptionSelector
|
|
229
|
+
if (!hasSelection) {
|
|
230
|
+
const select = element.querySelector('select');
|
|
231
|
+
if (select && select.value) {
|
|
232
|
+
hasSelection = true;
|
|
233
|
+
currentValue = select.options[select.selectedIndex]?.text || select.value;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Check for checkbox/radio style
|
|
237
|
+
if (!hasSelection) {
|
|
238
|
+
const checkedInput = element.querySelector('input[type="checkbox"]:checked, input[type="radio"]:checked');
|
|
239
|
+
if (checkedInput) {
|
|
240
|
+
hasSelection = true;
|
|
241
|
+
// Try to get label text
|
|
242
|
+
const label = checkedInput.closest('label')?.textContent?.trim();
|
|
243
|
+
currentValue = label || checkedInput.value || 'selected';
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
inputs.push({
|
|
248
|
+
id: comp.id,
|
|
249
|
+
key: comp.key,
|
|
250
|
+
type: comp.type,
|
|
251
|
+
label: comp.label,
|
|
252
|
+
buttons: optComp.buttons?.map((b) => ({ key: b.key, text: b.text })) || [],
|
|
253
|
+
filled: hasSelection,
|
|
254
|
+
currentValue,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
else if (comp.type === 'FileUpload') {
|
|
258
|
+
const element = document.querySelector(`.cid-${comp.id}`);
|
|
259
|
+
const input = element?.querySelector('input[type="file"]');
|
|
260
|
+
const hasFiles = !!(input?.files && input.files.length > 0);
|
|
261
|
+
// Get filename(s) if any
|
|
262
|
+
let currentValue;
|
|
263
|
+
if (hasFiles && input?.files) {
|
|
264
|
+
const fileNames = Array.from(input.files).map((f) => f.name);
|
|
265
|
+
currentValue = fileNames.join(', ');
|
|
266
|
+
}
|
|
267
|
+
inputs.push({
|
|
268
|
+
id: comp.id,
|
|
269
|
+
key: comp.key,
|
|
270
|
+
type: comp.type,
|
|
271
|
+
label: comp.label,
|
|
272
|
+
filled: hasFiles,
|
|
273
|
+
currentValue,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
// Process nested components (e.g., in Container)
|
|
277
|
+
if (comp.components) {
|
|
278
|
+
comp.components.forEach(processComponent);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
currentPage.components?.forEach(processComponent);
|
|
282
|
+
// Also check global components that might be visible
|
|
283
|
+
flowJson.components?.forEach(processComponent);
|
|
284
|
+
setInputComponents(inputs);
|
|
285
|
+
}, [embeddableId, savvy]);
|
|
286
|
+
useEffect(() => {
|
|
287
|
+
// Initial scan with a small delay to ensure DOM is ready
|
|
288
|
+
const timer = setTimeout(scanPage, 300);
|
|
289
|
+
return () => clearTimeout(timer);
|
|
290
|
+
}, [scanPage]);
|
|
291
|
+
// Re-scan when page changes
|
|
292
|
+
useEffect(() => {
|
|
293
|
+
const handleUserDataUpdate = () => {
|
|
294
|
+
setTimeout(scanPage, 100);
|
|
295
|
+
};
|
|
296
|
+
window.addEventListener('embeddables:userdata_updated', handleUserDataUpdate);
|
|
297
|
+
return () => window.removeEventListener('embeddables:userdata_updated', handleUserDataUpdate);
|
|
298
|
+
}, [scanPage]);
|
|
299
|
+
const handleAutofill = async () => {
|
|
300
|
+
setIsAutofilling(true);
|
|
301
|
+
setError(null);
|
|
302
|
+
let filledCount = 0;
|
|
303
|
+
for (const comp of inputComponents) {
|
|
304
|
+
if (comp.filled)
|
|
305
|
+
continue; // Skip already filled
|
|
306
|
+
try {
|
|
307
|
+
if (comp.type === 'InputBox') {
|
|
308
|
+
const testValue = generateTestValue({
|
|
309
|
+
key: comp.key,
|
|
310
|
+
label: comp.label,
|
|
311
|
+
input_type: comp.inputType,
|
|
312
|
+
});
|
|
313
|
+
if (fillInputBox(comp.id, testValue, comp.inputType || 'text')) {
|
|
314
|
+
filledCount++;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else if (comp.type === 'OptionSelector' && comp.buttons && comp.buttons.length > 0) {
|
|
318
|
+
if (fillOptionSelector(comp.id, comp.buttons, useRandomOptions)) {
|
|
319
|
+
filledCount++;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// FileUpload is skipped as it requires actual file selection
|
|
323
|
+
// Small delay between fills
|
|
324
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
console.error(`Error filling component ${comp.id}:`, err);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
setLastAutofillTime(Date.now());
|
|
331
|
+
setIsAutofilling(false);
|
|
332
|
+
// Re-scan to update filled status
|
|
333
|
+
setTimeout(scanPage, 200);
|
|
334
|
+
return filledCount;
|
|
335
|
+
};
|
|
336
|
+
const handleClearPage = () => {
|
|
337
|
+
setError(null);
|
|
338
|
+
// Collect OptionSelector keys to clear via setUserData
|
|
339
|
+
const optionSelectorKeys = [];
|
|
340
|
+
for (const comp of inputComponents) {
|
|
341
|
+
try {
|
|
342
|
+
const element = document.querySelector(`.cid-${comp.id}`);
|
|
343
|
+
if (!element)
|
|
344
|
+
continue;
|
|
345
|
+
if (comp.type === 'InputBox') {
|
|
346
|
+
const inputType = comp.inputType || 'text';
|
|
347
|
+
if (inputType === 'switch' || inputType === 'checkbox') {
|
|
348
|
+
const checkbox = element.querySelector('input[type="checkbox"]');
|
|
349
|
+
if (checkbox && checkbox.checked) {
|
|
350
|
+
checkbox.click();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
const input = element.querySelector('input, textarea');
|
|
355
|
+
if (input) {
|
|
356
|
+
input.focus();
|
|
357
|
+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
|
|
358
|
+
const nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')?.set;
|
|
359
|
+
if (input instanceof HTMLTextAreaElement && nativeTextAreaValueSetter) {
|
|
360
|
+
nativeTextAreaValueSetter.call(input, '');
|
|
361
|
+
}
|
|
362
|
+
else if (nativeInputValueSetter) {
|
|
363
|
+
nativeInputValueSetter.call(input, '');
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
input.value = '';
|
|
367
|
+
}
|
|
368
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
369
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
370
|
+
input.blur();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else if (comp.type === 'OptionSelector') {
|
|
375
|
+
// For dropdowns, reset to first (empty) option
|
|
376
|
+
const select = element.querySelector('select');
|
|
377
|
+
if (select) {
|
|
378
|
+
select.value = '';
|
|
379
|
+
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
380
|
+
}
|
|
381
|
+
// For button cards, we need to clear via setUserData since clicking
|
|
382
|
+
// a selected option doesn't deselect it (no toggle behavior)
|
|
383
|
+
if (comp.filled) {
|
|
384
|
+
optionSelectorKeys.push(comp.key);
|
|
385
|
+
}
|
|
386
|
+
// For checkbox/radio inputs
|
|
387
|
+
const checkedInputs = element.querySelectorAll('input[type="checkbox"]:checked, input[type="radio"]:checked');
|
|
388
|
+
checkedInputs.forEach((input) => {
|
|
389
|
+
input.click();
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
console.error(`Error clearing component ${comp.id}:`, err);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// Clear OptionSelector values via setUserData
|
|
398
|
+
if (optionSelectorKeys.length > 0 && savvy?.getUserData && savvy?.setUserData) {
|
|
399
|
+
const userData = savvy.getUserData(embeddableId);
|
|
400
|
+
if (userData) {
|
|
401
|
+
const clearedFields = {};
|
|
402
|
+
for (const key of optionSelectorKeys) {
|
|
403
|
+
clearedFields[key] = null;
|
|
404
|
+
}
|
|
405
|
+
savvy.setUserData(embeddableId, clearedFields);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Re-scan to update filled status
|
|
409
|
+
setTimeout(scanPage, 200);
|
|
410
|
+
};
|
|
411
|
+
const handleClearSingle = (comp) => {
|
|
412
|
+
setError(null);
|
|
413
|
+
try {
|
|
414
|
+
const element = document.querySelector(`.cid-${comp.id}`);
|
|
415
|
+
if (!element)
|
|
416
|
+
return;
|
|
417
|
+
if (comp.type === 'InputBox') {
|
|
418
|
+
const inputType = comp.inputType || 'text';
|
|
419
|
+
if (inputType === 'switch' || inputType === 'checkbox') {
|
|
420
|
+
const checkbox = element.querySelector('input[type="checkbox"]');
|
|
421
|
+
if (checkbox && checkbox.checked) {
|
|
422
|
+
checkbox.click();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
const input = element.querySelector('input, textarea');
|
|
427
|
+
if (input) {
|
|
428
|
+
input.focus();
|
|
429
|
+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
|
|
430
|
+
const nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')?.set;
|
|
431
|
+
if (input instanceof HTMLTextAreaElement && nativeTextAreaValueSetter) {
|
|
432
|
+
nativeTextAreaValueSetter.call(input, '');
|
|
433
|
+
}
|
|
434
|
+
else if (nativeInputValueSetter) {
|
|
435
|
+
nativeInputValueSetter.call(input, '');
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
input.value = '';
|
|
439
|
+
}
|
|
440
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
441
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
442
|
+
input.blur();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
else if (comp.type === 'OptionSelector') {
|
|
447
|
+
// For dropdowns, reset to first (empty) option
|
|
448
|
+
const select = element.querySelector('select');
|
|
449
|
+
if (select) {
|
|
450
|
+
select.value = '';
|
|
451
|
+
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
452
|
+
}
|
|
453
|
+
// For button cards, we need to clear via setUserData since clicking
|
|
454
|
+
// a selected option doesn't deselect it (no toggle behavior)
|
|
455
|
+
if (comp.filled && savvy?.getUserData && savvy?.setUserData) {
|
|
456
|
+
const userData = savvy.getUserData(embeddableId);
|
|
457
|
+
if (userData) {
|
|
458
|
+
savvy.setUserData(embeddableId, { [comp.key]: null });
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// For checkbox/radio inputs
|
|
462
|
+
const checkedInputs = element.querySelectorAll('input[type="checkbox"]:checked, input[type="radio"]:checked');
|
|
463
|
+
checkedInputs.forEach((input) => {
|
|
464
|
+
input.click();
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
// Re-scan to update filled status
|
|
468
|
+
setTimeout(scanPage, 200);
|
|
469
|
+
}
|
|
470
|
+
catch (err) {
|
|
471
|
+
setError(err?.message || String(err));
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
const handleAutofillAndNext = async () => {
|
|
475
|
+
await handleAutofill();
|
|
476
|
+
// Wait a bit for the DOM to update after autofill
|
|
477
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
478
|
+
// Find the first CustomButton with next-page or page-* action
|
|
479
|
+
const nextButton = document.querySelector('[data-components--custom-button-action-value="next-page"], ' +
|
|
480
|
+
'[data-components--custom-button-action-value^="page-"]');
|
|
481
|
+
if (nextButton) {
|
|
482
|
+
nextButton.click();
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
setError('No next page button found on this page.');
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
const handleFillSingle = (comp) => {
|
|
489
|
+
setError(null);
|
|
490
|
+
try {
|
|
491
|
+
if (comp.type === 'InputBox') {
|
|
492
|
+
const testValue = generateTestValue({
|
|
493
|
+
key: comp.key,
|
|
494
|
+
label: comp.label,
|
|
495
|
+
input_type: comp.inputType,
|
|
496
|
+
});
|
|
497
|
+
fillInputBox(comp.id, testValue, comp.inputType || 'text');
|
|
498
|
+
}
|
|
499
|
+
else if (comp.type === 'OptionSelector' && comp.buttons && comp.buttons.length > 0) {
|
|
500
|
+
fillOptionSelector(comp.id, comp.buttons, useRandomOptions);
|
|
501
|
+
}
|
|
502
|
+
// Re-scan to update filled status
|
|
503
|
+
setTimeout(scanPage, 200);
|
|
504
|
+
}
|
|
505
|
+
catch (err) {
|
|
506
|
+
setError(err?.message || String(err));
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
const getInputTypeIcon = (type, inputType) => {
|
|
510
|
+
if (type === 'OptionSelector')
|
|
511
|
+
return '☰';
|
|
512
|
+
if (type === 'FileUpload')
|
|
513
|
+
return '📎';
|
|
514
|
+
switch (inputType) {
|
|
515
|
+
case 'email':
|
|
516
|
+
return '✉';
|
|
517
|
+
case 'phone':
|
|
518
|
+
return '☎';
|
|
519
|
+
case 'number':
|
|
520
|
+
return '#';
|
|
521
|
+
case 'date':
|
|
522
|
+
return '📅';
|
|
523
|
+
case 'time':
|
|
524
|
+
return '⏰';
|
|
525
|
+
case 'month':
|
|
526
|
+
return '📆';
|
|
527
|
+
case 'password':
|
|
528
|
+
case 'confirm':
|
|
529
|
+
return '🔒';
|
|
530
|
+
case 'checkbox':
|
|
531
|
+
case 'switch':
|
|
532
|
+
return '☑';
|
|
533
|
+
case 'range':
|
|
534
|
+
return '↔';
|
|
535
|
+
default:
|
|
536
|
+
return '𝐓';
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
const unfilledCount = inputComponents.filter((c) => !c.filled && c.type !== 'FileUpload').length;
|
|
540
|
+
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: "Autofill Components" }), _jsx("div", { className: "mt-0.5 text-[11px] text-slate-400", children: "Automatically fill input components on the current page with test data." })] }), _jsxs("div", { className: "flex flex-wrap items-center justify-start gap-2 flex-2 sm:justify-end", children: [_jsxs("label", { className: "flex cursor-pointer items-center gap-1.5 text-[11px] text-slate-400", children: [_jsx("input", { type: "checkbox", checked: useRandomOptions, onChange: (e) => setUseRandomOptions(e.target.checked), className: "h-3 w-3 rounded border-slate-600 bg-slate-800 text-sky-500 focus:ring-sky-500/50" }), "Random options"] }), _jsx("button", { type: "button", onClick: scanPage, 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: "Rescan" }), _jsxs("button", { type: "button", onClick: handleClearPage, disabled: isAutofilling || inputComponents.length === 0, 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 disabled:cursor-not-allowed disabled:opacity-60", children: [_jsx("span", { className: "text-sm", children: "\uD83D\uDDD1" }), "Clear Page"] }), _jsx("button", { type: "button", onClick: handleAutofill, disabled: isAutofilling || unfilledCount === 0, className: "inline-flex cursor-pointer items-center gap-1.5 rounded-lg bg-emerald-600 px-2.5 py-1.5 text-xs font-semibold text-white ring-1 ring-inset ring-emerald-500 hover:bg-emerald-500 disabled:cursor-not-allowed disabled:opacity-60", children: isAutofilling ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "inline-block h-3 w-3 animate-spin rounded-full border-2 border-white border-t-transparent" }), "Filling..."] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-sm", children: "\u26A1" }), "Autofill All (", unfilledCount, ")"] })) }), _jsxs("button", { type: "button", onClick: handleAutofillAndNext, disabled: isAutofilling, className: "inline-flex cursor-pointer items-center gap-1.5 rounded-lg bg-sky-600 px-2.5 py-1.5 text-xs font-semibold text-white ring-1 ring-inset ring-sky-500 hover:bg-sky-500 disabled:cursor-not-allowed disabled:opacity-60", children: [_jsx("span", { className: "text-sm", children: "\u26A1\u2192" }), "Autofill All + Next"] })] })] }), error && (_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 })), lastAutofillTime && (_jsx("div", { className: "mt-3 rounded-xl bg-emerald-500/10 px-3 py-2 text-xs text-emerald-200 ring-1 ring-inset ring-emerald-500/20", children: "Autofill completed. Components have been filled with test data." })), _jsxs("div", { className: "mt-4 flex min-h-0 flex-1 flex-col overflow-hidden", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsx("span", { className: "text-[11px] font-semibold uppercase tracking-wider text-slate-400", children: "Input Components" }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-emerald-500/20 px-2 py-0.5 text-[11px] font-medium text-emerald-300 ring-1 ring-inset ring-emerald-500/30", children: [_jsx("span", { className: "text-emerald-400", children: inputComponents.filter((c) => c.filled).length }), _jsx("span", { className: "text-emerald-300/60", children: "filled" })] }), _jsx("span", { className: "text-slate-300 text-[12px]", children: "of" }), _jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-slate-700/50 px-2 py-0.5 text-[11px] font-medium text-slate-300 ring-1 ring-inset ring-slate-600/50", children: [_jsx("span", { children: inputComponents.length }), _jsx("span", { className: "text-slate-400", children: "total" })] })] })] }), inputComponents.length === 0 ? (_jsx("div", { className: "flex flex-1 items-center justify-center text-[11px] text-slate-500", children: "No input components found on the current page." })) : (_jsx("div", { className: "min-h-0 flex-1 overflow-auto rounded-xl bg-slate-950/60 p-2 ring-1 ring-inset ring-white/10", children: _jsx("div", { className: "space-y-1", children: inputComponents.map((comp) => (_jsxs("div", { className: `flex items-center justify-between gap-2 rounded-lg px-2.5 py-2 ${comp.filled
|
|
541
|
+
? 'bg-emerald-500/10 ring-1 ring-inset ring-emerald-500/20'
|
|
542
|
+
: 'bg-white/5 ring-1 ring-inset ring-white/10'}`, children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [_jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded bg-slate-800 text-xs", title: comp.inputType || comp.type, children: getInputTypeIcon(comp.type, comp.inputType) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-xs font-medium text-slate-100", children: comp.label || comp.key }), _jsxs("div", { className: "flex items-center gap-2 text-[10px] text-slate-500", children: [_jsx("span", { children: comp.type }), comp.inputType && (_jsxs(_Fragment, { children: [_jsx("span", { children: "\u2022" }), _jsx("span", { className: "font-mono", children: comp.inputType })] })), comp.buttons && (_jsxs(_Fragment, { children: [_jsx("span", { children: "\u2022" }), _jsxs("span", { children: [comp.buttons.length, " options"] })] }))] })] }), comp.currentValue && (_jsx("div", { className: "min-w-0 max-w-[200px] shrink-0", children: _jsx("div", { className: "truncate rounded bg-slate-800/80 px-2 py-1 text-[10px] font-mono text-sky-300", title: comp.currentValue, children: comp.currentValue }) }))] }), _jsx("div", { className: "flex shrink-0 items-center gap-2", children: comp.type === 'FileUpload' ? (_jsx("span", { className: "text-[10px] text-slate-500", children: "Manual only" })) : comp.filled ? (_jsx("button", { type: "button", onClick: () => handleClearSingle(comp), className: "cursor-pointer rounded-lg bg-slate-700 px-2 py-1 text-[10px] font-semibold text-rose-300 ring-1 ring-inset ring-slate-600 hover:bg-slate-600 hover:text-rose-200", children: "Clear" })) : (_jsx("button", { type: "button", onClick: () => handleFillSingle(comp), className: "cursor-pointer rounded-lg bg-slate-700 px-2 py-1 text-[10px] font-semibold text-teal-300 ring-1 ring-inset ring-slate-600 hover:bg-slate-600 hover:text-teal-200", children: "Fill" })) })] }, comp.id))) }) }))] })] }));
|
|
543
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComputedFieldsPanel.d.ts","sourceRoot":"","sources":["../../src/workbench/ComputedFieldsPanel.tsx"],"names":[],"mappings":"AAGA,KAAK,wBAAwB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AASD,wBAAgB,mBAAmB,CAAC,EAAE,YAAY,EAAE,EAAE,wBAAwB,2CA4I7E"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
export function ComputedFieldsPanel({ embeddableId }) {
|
|
4
|
+
const [computedFields, setComputedFields] = useState([]);
|
|
5
|
+
const [selectedField, setSelectedField] = useState(null);
|
|
6
|
+
const [error, setError] = useState(null);
|
|
7
|
+
const savvy = window.Savvy;
|
|
8
|
+
const refresh = useCallback(() => {
|
|
9
|
+
if (!savvy?.getFlowJson)
|
|
10
|
+
return;
|
|
11
|
+
try {
|
|
12
|
+
const flowJson = savvy.getFlowJson(embeddableId);
|
|
13
|
+
const list = flowJson?.computedFields ?? [];
|
|
14
|
+
setComputedFields(list);
|
|
15
|
+
setSelectedField((prev) => {
|
|
16
|
+
const stillExists = prev && list.some((cf) => cf.id === prev.id);
|
|
17
|
+
return stillExists ? prev : (list[0] ?? null);
|
|
18
|
+
});
|
|
19
|
+
setError(null);
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
23
|
+
}
|
|
24
|
+
}, [embeddableId, savvy]);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
refresh();
|
|
27
|
+
}, [refresh]);
|
|
28
|
+
return (_jsxs("div", { className: "mx-auto flex h-full w-full max-w-6xl flex-col", children: [_jsxs("div", { className: "mb-3", children: [_jsx("div", { className: "text-xs font-semibold tracking-wide text-slate-100", children: "Computed Fields" }), _jsx("div", { className: "mt-0.5 text-[11px] text-slate-400", children: "View computed field metadata and code. Computed values are derived from user data." })] }), error && (_jsx("div", { className: "mb-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 })), _jsxs("div", { className: "flex min-h-0 flex-1 gap-3 overflow-hidden", children: [_jsxs("div", { className: "flex w-1/3 min-w-0 shrink-0 flex-col", children: [_jsx("div", { className: "mb-2 text-[11px] font-semibold uppercase tracking-wider text-slate-400", children: "Computed Fields" }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto rounded-xl bg-slate-950/60 ring-1 ring-inset ring-white/10", children: computedFields.length === 0 ? (_jsx("div", { className: "p-3 text-[11px] italic text-slate-500", children: "No computed fields defined." })) : (_jsx("div", { className: "p-1", children: computedFields.map((field) => (_jsxs("button", { type: "button", onClick: () => setSelectedField(field), className: `w-full rounded-lg px-2.5 py-2 text-left text-xs transition-colors ${selectedField?.id === field.id
|
|
29
|
+
? 'bg-white/15 text-slate-100 ring-1 ring-inset ring-white/20'
|
|
30
|
+
: 'text-slate-300 hover:bg-white/5 hover:text-slate-100'}`, children: [_jsx("span", { className: "block truncate font-medium", children: field.key || field.id }), _jsx("span", { className: "mt-0.5 block truncate font-mono text-[10px] text-slate-500", children: field.id })] }, field.id))) })) })] }), _jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [_jsx("div", { className: "mb-2 text-[11px] font-semibold uppercase tracking-wider text-slate-400", children: selectedField ? selectedField.key || selectedField.id : 'Select a computed field' }), selectedField ? (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-3 overflow-hidden", children: [_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsxs("span", { className: "inline-flex items-center rounded-md bg-slate-700/80 px-2 py-1 font-mono text-[10px] text-slate-200 ring-1 ring-inset ring-slate-600", children: ["id: ", selectedField.id] }), _jsxs("span", { className: "inline-flex items-center rounded-md bg-slate-700/80 px-2 py-1 font-mono text-[10px] text-slate-200 ring-1 ring-inset ring-slate-600", children: ["key: ", selectedField.key] }), _jsx("span", { className: "inline-flex items-center rounded-md bg-amber-500/20 px-2 py-1 text-[10px] font-medium text-amber-300 ring-1 ring-inset ring-amber-500/30", children: selectedField.formula }), selectedField.inputs && selectedField.inputs.length > 0 && (_jsxs("span", { className: "inline-flex items-center rounded-md bg-teal-500/20 px-2 py-1 text-[10px] text-teal-300 ring-1 ring-inset ring-teal-500/30", children: ["inputs: [", selectedField.inputs.join(', '), "]"] })), selectedField.async && (_jsx("span", { className: "inline-flex items-center rounded-md bg-sky-500/20 px-2 py-1 text-[10px] font-medium text-sky-300 ring-1 ring-inset ring-sky-500/30", children: "async" })), selectedField.doNotSave && (_jsxs("span", { className: "inline-flex items-center rounded-md bg-rose-500/20 px-2 py-1 text-[10px] font-medium text-rose-300 ring-1 ring-inset ring-rose-500/30", children: ["doNotSave: ", String(selectedField.doNotSave)] })), selectedField.use_full_user_data && (_jsx("span", { className: "inline-flex items-center rounded-md bg-purple-500/20 px-2 py-1 text-[10px] font-medium text-purple-300 ring-1 ring-inset ring-purple-500/30", children: "use_full_user_data" })), selectedField.cache_data && (_jsx("span", { className: "inline-flex items-center rounded-md bg-emerald-500/20 px-2 py-1 text-[10px] font-medium text-emerald-300 ring-1 ring-inset ring-emerald-500/30", children: "cache_data" }))] }), _jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: _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: selectedField.code?.trim() || '(no code)' }) })] })) : (_jsx("div", { className: "flex flex-1 items-center justify-center rounded-xl bg-slate-950/60 ring-1 ring-inset ring-white/10", children: _jsx("span", { className: "text-[11px] text-slate-500", children: "Select a computed field from the list" }) }))] })] })] }));
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExperimentsPanel.d.ts","sourceRoot":"","sources":["../../src/workbench/ExperimentsPanel.tsx"],"names":[],"mappings":"AAGA,KAAK,qBAAqB,GAAG;IAC3B,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AAwED,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,EAAE,qBAAqB,2CAgQvE"}
|