@showwhat/configurator 1.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/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/index.d.ts +332 -0
- package/dist/index.js +3741 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +115 -0
- package/package.json +75 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3741 @@
|
|
|
1
|
+
// src/utils/cn.ts
|
|
2
|
+
import { clsx } from "clsx";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
function cn(...inputs) {
|
|
5
|
+
return twMerge(clsx(inputs));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/utils/id.ts
|
|
9
|
+
var AUTO_ID_PREFIX = "_tmp:";
|
|
10
|
+
function isAutoId(id) {
|
|
11
|
+
return id != null && id.startsWith(AUTO_ID_PREFIX);
|
|
12
|
+
}
|
|
13
|
+
function ensureIds(items) {
|
|
14
|
+
let changed = false;
|
|
15
|
+
const result = items.map((item) => {
|
|
16
|
+
if (item.id) return item;
|
|
17
|
+
changed = true;
|
|
18
|
+
return { ...item, id: `${AUTO_ID_PREFIX}${crypto.randomUUID()}` };
|
|
19
|
+
});
|
|
20
|
+
return changed ? result : items;
|
|
21
|
+
}
|
|
22
|
+
function removeId(item) {
|
|
23
|
+
const copy = { ...item };
|
|
24
|
+
delete copy.id;
|
|
25
|
+
return copy;
|
|
26
|
+
}
|
|
27
|
+
function stripId(item) {
|
|
28
|
+
if (!isAutoId(item.id)) return item;
|
|
29
|
+
return removeId(item);
|
|
30
|
+
}
|
|
31
|
+
function stripConditionIds(conditions) {
|
|
32
|
+
return conditions.map((c) => {
|
|
33
|
+
const rec = c;
|
|
34
|
+
const stripped = isAutoId(rec.id) ? removeId(rec) : rec;
|
|
35
|
+
if ((c.type === "and" || c.type === "or") && "conditions" in c) {
|
|
36
|
+
return {
|
|
37
|
+
...stripped,
|
|
38
|
+
conditions: stripConditionIds(c.conditions)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return stripped;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function stripAutoIds(definitions) {
|
|
45
|
+
const result = {};
|
|
46
|
+
for (const [key, def] of Object.entries(definitions)) {
|
|
47
|
+
const cleaned = stripId(def);
|
|
48
|
+
result[key] = {
|
|
49
|
+
...cleaned,
|
|
50
|
+
variations: cleaned.variations.map((v) => {
|
|
51
|
+
const sv = stripId(v);
|
|
52
|
+
if (sv.conditions) {
|
|
53
|
+
return { ...sv, conditions: stripConditionIds(sv.conditions) };
|
|
54
|
+
}
|
|
55
|
+
return sv;
|
|
56
|
+
})
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/components/condition-builder/StringConditionEditor.tsx
|
|
63
|
+
import { useCallback as useCallback2, useMemo } from "react";
|
|
64
|
+
|
|
65
|
+
// src/components/condition-builder/ConditionRow.tsx
|
|
66
|
+
import { jsx } from "react/jsx-runtime";
|
|
67
|
+
function ConditionRow({ children }) {
|
|
68
|
+
return /* @__PURE__ */ jsx(
|
|
69
|
+
"div",
|
|
70
|
+
{
|
|
71
|
+
className: "grid flex-1 items-start gap-2",
|
|
72
|
+
style: { gridTemplateColumns: "140px 72px 1fr" },
|
|
73
|
+
children
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/components/ui/input.tsx
|
|
79
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
80
|
+
function Input({ className, type, ...props }) {
|
|
81
|
+
return /* @__PURE__ */ jsx2(
|
|
82
|
+
"input",
|
|
83
|
+
{
|
|
84
|
+
type,
|
|
85
|
+
"data-slot": "input",
|
|
86
|
+
className: cn(
|
|
87
|
+
"h-9 w-full min-w-0 rounded border border-border/70 bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
|
|
88
|
+
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
89
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
90
|
+
className
|
|
91
|
+
),
|
|
92
|
+
...props
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/components/condition-builder/KeyInput.tsx
|
|
98
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
99
|
+
function KeyInput({ value, onChange, placeholder, disabled }) {
|
|
100
|
+
return /* @__PURE__ */ jsx3(
|
|
101
|
+
Input,
|
|
102
|
+
{
|
|
103
|
+
className: "h-8 font-mono text-sm",
|
|
104
|
+
value,
|
|
105
|
+
placeholder: placeholder ?? "key",
|
|
106
|
+
disabled,
|
|
107
|
+
onChange: onChange ? (e) => onChange(e.target.value) : void 0,
|
|
108
|
+
readOnly: !onChange
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/components/ui/select.tsx
|
|
114
|
+
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
115
|
+
import { Select as SelectPrimitive } from "radix-ui";
|
|
116
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
117
|
+
function Select({ ...props }) {
|
|
118
|
+
return /* @__PURE__ */ jsx4(SelectPrimitive.Root, { "data-slot": "select", ...props });
|
|
119
|
+
}
|
|
120
|
+
function SelectGroup({ ...props }) {
|
|
121
|
+
return /* @__PURE__ */ jsx4(SelectPrimitive.Group, { "data-slot": "select-group", ...props });
|
|
122
|
+
}
|
|
123
|
+
function SelectValue({ ...props }) {
|
|
124
|
+
return /* @__PURE__ */ jsx4(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
|
|
125
|
+
}
|
|
126
|
+
function SelectTrigger({
|
|
127
|
+
className,
|
|
128
|
+
size = "default",
|
|
129
|
+
children,
|
|
130
|
+
...props
|
|
131
|
+
}) {
|
|
132
|
+
return /* @__PURE__ */ jsxs(
|
|
133
|
+
SelectPrimitive.Trigger,
|
|
134
|
+
{
|
|
135
|
+
"data-slot": "select-trigger",
|
|
136
|
+
"data-size": size,
|
|
137
|
+
className: cn(
|
|
138
|
+
"flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
139
|
+
className
|
|
140
|
+
),
|
|
141
|
+
...props,
|
|
142
|
+
children: [
|
|
143
|
+
children,
|
|
144
|
+
/* @__PURE__ */ jsx4(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx4(ChevronDownIcon, { className: "size-4 opacity-50" }) })
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
function SelectContent({
|
|
150
|
+
className,
|
|
151
|
+
children,
|
|
152
|
+
position = "item-aligned",
|
|
153
|
+
align = "center",
|
|
154
|
+
...props
|
|
155
|
+
}) {
|
|
156
|
+
return /* @__PURE__ */ jsx4(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
|
|
157
|
+
SelectPrimitive.Content,
|
|
158
|
+
{
|
|
159
|
+
"data-slot": "select-content",
|
|
160
|
+
className: cn(
|
|
161
|
+
"relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
162
|
+
position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
163
|
+
className
|
|
164
|
+
),
|
|
165
|
+
position,
|
|
166
|
+
align,
|
|
167
|
+
...props,
|
|
168
|
+
children: [
|
|
169
|
+
/* @__PURE__ */ jsx4(SelectScrollUpButton, {}),
|
|
170
|
+
/* @__PURE__ */ jsx4(
|
|
171
|
+
SelectPrimitive.Viewport,
|
|
172
|
+
{
|
|
173
|
+
className: cn(
|
|
174
|
+
"p-1",
|
|
175
|
+
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
|
176
|
+
),
|
|
177
|
+
children
|
|
178
|
+
}
|
|
179
|
+
),
|
|
180
|
+
/* @__PURE__ */ jsx4(SelectScrollDownButton, {})
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
) });
|
|
184
|
+
}
|
|
185
|
+
function SelectLabel({ className, ...props }) {
|
|
186
|
+
return /* @__PURE__ */ jsx4(
|
|
187
|
+
SelectPrimitive.Label,
|
|
188
|
+
{
|
|
189
|
+
"data-slot": "select-label",
|
|
190
|
+
className: cn("px-2 py-1.5 text-xs text-muted-foreground", className),
|
|
191
|
+
...props
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
function SelectItem({
|
|
196
|
+
className,
|
|
197
|
+
children,
|
|
198
|
+
...props
|
|
199
|
+
}) {
|
|
200
|
+
return /* @__PURE__ */ jsxs(
|
|
201
|
+
SelectPrimitive.Item,
|
|
202
|
+
{
|
|
203
|
+
"data-slot": "select-item",
|
|
204
|
+
className: cn(
|
|
205
|
+
"relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
206
|
+
className
|
|
207
|
+
),
|
|
208
|
+
...props,
|
|
209
|
+
children: [
|
|
210
|
+
/* @__PURE__ */ jsx4(
|
|
211
|
+
"span",
|
|
212
|
+
{
|
|
213
|
+
"data-slot": "select-item-indicator",
|
|
214
|
+
className: "absolute right-2 flex size-3.5 items-center justify-center",
|
|
215
|
+
children: /* @__PURE__ */ jsx4(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx4(CheckIcon, { className: "size-4" }) })
|
|
216
|
+
}
|
|
217
|
+
),
|
|
218
|
+
/* @__PURE__ */ jsx4(SelectPrimitive.ItemText, { children })
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
function SelectSeparator({
|
|
224
|
+
className,
|
|
225
|
+
...props
|
|
226
|
+
}) {
|
|
227
|
+
return /* @__PURE__ */ jsx4(
|
|
228
|
+
SelectPrimitive.Separator,
|
|
229
|
+
{
|
|
230
|
+
"data-slot": "select-separator",
|
|
231
|
+
className: cn("pointer-events-none -mx-1 my-1 h-px bg-border", className),
|
|
232
|
+
...props
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
function SelectScrollUpButton({
|
|
237
|
+
className,
|
|
238
|
+
...props
|
|
239
|
+
}) {
|
|
240
|
+
return /* @__PURE__ */ jsx4(
|
|
241
|
+
SelectPrimitive.ScrollUpButton,
|
|
242
|
+
{
|
|
243
|
+
"data-slot": "select-scroll-up-button",
|
|
244
|
+
className: cn("flex cursor-default items-center justify-center py-1", className),
|
|
245
|
+
...props,
|
|
246
|
+
children: /* @__PURE__ */ jsx4(ChevronUpIcon, { className: "size-4" })
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
function SelectScrollDownButton({
|
|
251
|
+
className,
|
|
252
|
+
...props
|
|
253
|
+
}) {
|
|
254
|
+
return /* @__PURE__ */ jsx4(
|
|
255
|
+
SelectPrimitive.ScrollDownButton,
|
|
256
|
+
{
|
|
257
|
+
"data-slot": "select-scroll-down-button",
|
|
258
|
+
className: cn("flex cursor-default items-center justify-center py-1", className),
|
|
259
|
+
...props,
|
|
260
|
+
children: /* @__PURE__ */ jsx4(ChevronDownIcon, { className: "size-4" })
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/components/condition-builder/OperatorSelect.tsx
|
|
266
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
267
|
+
function OperatorSelect({ value, onChange, options, disabled }) {
|
|
268
|
+
return /* @__PURE__ */ jsxs2(Select, { value, onValueChange: onChange, disabled, children: [
|
|
269
|
+
/* @__PURE__ */ jsx5(SelectTrigger, { className: "h-8 font-mono text-xs", disabled, children: /* @__PURE__ */ jsx5(SelectValue, {}) }),
|
|
270
|
+
/* @__PURE__ */ jsx5(SelectContent, { children: options.map((opt) => /* @__PURE__ */ jsx5(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
|
|
271
|
+
] });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/components/condition-builder/TagInput.tsx
|
|
275
|
+
import { useCallback, useState } from "react";
|
|
276
|
+
|
|
277
|
+
// src/components/ui/badge.tsx
|
|
278
|
+
import { cva } from "class-variance-authority";
|
|
279
|
+
import { Slot } from "radix-ui";
|
|
280
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
281
|
+
var badgeVariants = cva(
|
|
282
|
+
"inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
|
|
283
|
+
{
|
|
284
|
+
variants: {
|
|
285
|
+
variant: {
|
|
286
|
+
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
287
|
+
secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
288
|
+
destructive: "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",
|
|
289
|
+
outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
290
|
+
ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
291
|
+
link: "text-primary underline-offset-4 [a&]:hover:underline"
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
defaultVariants: {
|
|
295
|
+
variant: "default"
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
function Badge({
|
|
300
|
+
className,
|
|
301
|
+
variant = "default",
|
|
302
|
+
asChild = false,
|
|
303
|
+
...props
|
|
304
|
+
}) {
|
|
305
|
+
const Comp = asChild ? Slot.Root : "span";
|
|
306
|
+
return /* @__PURE__ */ jsx6(
|
|
307
|
+
Comp,
|
|
308
|
+
{
|
|
309
|
+
"data-slot": "badge",
|
|
310
|
+
"data-variant": variant,
|
|
311
|
+
className: cn(badgeVariants({ variant }), className),
|
|
312
|
+
...props
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/components/condition-builder/TagInput.tsx
|
|
318
|
+
import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
319
|
+
function TagInput({ value, onChange, placeholder }) {
|
|
320
|
+
const values = Array.isArray(value) ? value.filter(Boolean) : value ? [value] : [];
|
|
321
|
+
const [text, setText] = useState("");
|
|
322
|
+
const emit = useCallback(
|
|
323
|
+
(next2) => {
|
|
324
|
+
onChange(next2.length === 1 ? next2[0] : next2);
|
|
325
|
+
},
|
|
326
|
+
[onChange]
|
|
327
|
+
);
|
|
328
|
+
const addValues = useCallback(
|
|
329
|
+
(raw) => {
|
|
330
|
+
const cleaned = raw.map((s) => s.trim()).filter(Boolean);
|
|
331
|
+
const unique = cleaned.filter((s) => !values.includes(s));
|
|
332
|
+
if (unique.length > 0) {
|
|
333
|
+
emit([...values, ...unique]);
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
[values, emit]
|
|
337
|
+
);
|
|
338
|
+
const removeValue = useCallback(
|
|
339
|
+
(index) => {
|
|
340
|
+
const next2 = values.filter((_, i) => i !== index);
|
|
341
|
+
emit(next2.length === 0 ? [] : next2);
|
|
342
|
+
},
|
|
343
|
+
[values, emit]
|
|
344
|
+
);
|
|
345
|
+
const handleKeyDown = useCallback(
|
|
346
|
+
(e) => {
|
|
347
|
+
if (e.key === "Enter" || e.key === "Tab" && text.trim()) {
|
|
348
|
+
e.preventDefault();
|
|
349
|
+
addValues([text]);
|
|
350
|
+
setText("");
|
|
351
|
+
} else if (e.key === "Backspace" && text === "" && values.length > 0) {
|
|
352
|
+
removeValue(values.length - 1);
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
[text, values, addValues, removeValue]
|
|
356
|
+
);
|
|
357
|
+
const handlePaste = useCallback(
|
|
358
|
+
(e) => {
|
|
359
|
+
const pasted = e.clipboardData.getData("text");
|
|
360
|
+
if (pasted.includes("\n")) {
|
|
361
|
+
e.preventDefault();
|
|
362
|
+
addValues(pasted.split("\n"));
|
|
363
|
+
setText("");
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
[addValues]
|
|
367
|
+
);
|
|
368
|
+
return /* @__PURE__ */ jsxs3("div", { className: "border-input focus-within:border-ring focus-within:ring-ring/50 flex min-h-9 flex-1 flex-wrap items-center gap-1 rounded-md border px-2 py-1 focus-within:ring-[3px]", children: [
|
|
369
|
+
values.map((v, i) => /* @__PURE__ */ jsxs3(Badge, { variant: "outline", className: "bg-muted gap-1 font-mono text-xs", children: [
|
|
370
|
+
v,
|
|
371
|
+
/* @__PURE__ */ jsx7(
|
|
372
|
+
"button",
|
|
373
|
+
{
|
|
374
|
+
type: "button",
|
|
375
|
+
className: "text-muted-foreground hover:text-foreground ml-0.5 cursor-pointer leading-none",
|
|
376
|
+
onClick: () => removeValue(i),
|
|
377
|
+
"aria-label": `Remove ${v}`,
|
|
378
|
+
children: "\xD7"
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
] }, `${v}-${i}`)),
|
|
382
|
+
/* @__PURE__ */ jsx7(
|
|
383
|
+
"input",
|
|
384
|
+
{
|
|
385
|
+
className: "min-w-[80px] flex-1 bg-transparent py-0.5 font-mono text-sm outline-none placeholder:text-muted-foreground",
|
|
386
|
+
value: text,
|
|
387
|
+
placeholder: values.length === 0 ? placeholder ?? "type and press Enter" : "",
|
|
388
|
+
onChange: (e) => setText(e.target.value),
|
|
389
|
+
onKeyDown: handleKeyDown,
|
|
390
|
+
onPaste: handlePaste
|
|
391
|
+
}
|
|
392
|
+
)
|
|
393
|
+
] });
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/components/condition-builder/condition-builders.ts
|
|
397
|
+
function buildAndCondition(conditions, id) {
|
|
398
|
+
return id ? { id, type: "and", conditions } : { type: "and", conditions };
|
|
399
|
+
}
|
|
400
|
+
function buildOrCondition(conditions, id) {
|
|
401
|
+
return id ? { id, type: "or", conditions } : { type: "or", conditions };
|
|
402
|
+
}
|
|
403
|
+
function buildCustomCondition(fields) {
|
|
404
|
+
return fields;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/components/condition-builder/StringConditionEditor.tsx
|
|
408
|
+
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
409
|
+
var OP_OPTIONS = [
|
|
410
|
+
{ value: "eq", label: "eq" },
|
|
411
|
+
{ value: "neq", label: "neq" },
|
|
412
|
+
{ value: "in", label: "in" },
|
|
413
|
+
{ value: "nin", label: "nin" },
|
|
414
|
+
{ value: "regex", label: "regex" }
|
|
415
|
+
];
|
|
416
|
+
var meta = {
|
|
417
|
+
type: "string",
|
|
418
|
+
label: "String",
|
|
419
|
+
description: "Match a context key against string value(s)",
|
|
420
|
+
defaults: { type: "string", key: "", op: "eq", value: "" }
|
|
421
|
+
};
|
|
422
|
+
function StringConditionEditor({ condition, onChange }) {
|
|
423
|
+
const rec = useMemo(() => condition, [condition]);
|
|
424
|
+
const update = useCallback2(
|
|
425
|
+
(field, value) => {
|
|
426
|
+
onChange(buildCustomCondition({ ...rec, [field]: value, type: "string" }));
|
|
427
|
+
},
|
|
428
|
+
[rec, onChange]
|
|
429
|
+
);
|
|
430
|
+
const handleOpChange = useCallback2(
|
|
431
|
+
(newOp) => {
|
|
432
|
+
const isArrayOp = newOp === "in" || newOp === "nin";
|
|
433
|
+
const currentValue = rec.value;
|
|
434
|
+
const coercedValue = isArrayOp ? Array.isArray(currentValue) ? currentValue : currentValue ? [String(currentValue)] : [] : Array.isArray(currentValue) ? currentValue[0] ?? "" : currentValue;
|
|
435
|
+
onChange(buildCustomCondition({ ...rec, op: newOp, value: coercedValue, type: "string" }));
|
|
436
|
+
},
|
|
437
|
+
[rec, onChange]
|
|
438
|
+
);
|
|
439
|
+
const op = rec.op;
|
|
440
|
+
const isArray = op === "in" || op === "nin";
|
|
441
|
+
const isRegex = op === "regex";
|
|
442
|
+
return /* @__PURE__ */ jsxs4(ConditionRow, { children: [
|
|
443
|
+
/* @__PURE__ */ jsx8(
|
|
444
|
+
KeyInput,
|
|
445
|
+
{
|
|
446
|
+
value: String(rec.key ?? ""),
|
|
447
|
+
onChange: (v) => update("key", v),
|
|
448
|
+
placeholder: "e.g. userId"
|
|
449
|
+
}
|
|
450
|
+
),
|
|
451
|
+
/* @__PURE__ */ jsx8(
|
|
452
|
+
OperatorSelect,
|
|
453
|
+
{
|
|
454
|
+
value: String(rec.op ?? "eq"),
|
|
455
|
+
onChange: handleOpChange,
|
|
456
|
+
options: OP_OPTIONS
|
|
457
|
+
}
|
|
458
|
+
),
|
|
459
|
+
isArray ? /* @__PURE__ */ jsx8(
|
|
460
|
+
TagInput,
|
|
461
|
+
{
|
|
462
|
+
value: rec.value ?? "",
|
|
463
|
+
onChange: (v) => update("value", v),
|
|
464
|
+
placeholder: "e.g. user-123"
|
|
465
|
+
}
|
|
466
|
+
) : isRegex ? /* @__PURE__ */ jsx8(
|
|
467
|
+
Input,
|
|
468
|
+
{
|
|
469
|
+
className: "h-8 font-mono text-sm",
|
|
470
|
+
value: String(rec.value ?? ""),
|
|
471
|
+
placeholder: "e.g. ^test.*$",
|
|
472
|
+
onChange: (e) => update("value", e.target.value)
|
|
473
|
+
}
|
|
474
|
+
) : /* @__PURE__ */ jsx8(
|
|
475
|
+
Input,
|
|
476
|
+
{
|
|
477
|
+
className: "h-8 text-sm",
|
|
478
|
+
value: String(rec.value ?? ""),
|
|
479
|
+
placeholder: "e.g. user-123",
|
|
480
|
+
onChange: (e) => update("value", e.target.value)
|
|
481
|
+
}
|
|
482
|
+
)
|
|
483
|
+
] });
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/components/condition-builder/NumberConditionEditor.tsx
|
|
487
|
+
import { useCallback as useCallback4, useMemo as useMemo2 } from "react";
|
|
488
|
+
|
|
489
|
+
// src/components/condition-builder/NumberTagInput.tsx
|
|
490
|
+
import { useCallback as useCallback3, useState as useState2 } from "react";
|
|
491
|
+
import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
492
|
+
function NumberTagInput({ value, onChange, placeholder }) {
|
|
493
|
+
const values = Array.isArray(value) ? value : [value];
|
|
494
|
+
const [text, setText] = useState2("");
|
|
495
|
+
const emit = useCallback3(
|
|
496
|
+
(next2) => {
|
|
497
|
+
onChange(next2.length === 1 ? next2[0] : next2);
|
|
498
|
+
},
|
|
499
|
+
[onChange]
|
|
500
|
+
);
|
|
501
|
+
const addValues = useCallback3(
|
|
502
|
+
(raw) => {
|
|
503
|
+
const parsed = raw.map((s) => s.trim()).filter(Boolean).map(Number).filter((n) => !Number.isNaN(n));
|
|
504
|
+
const unique = parsed.filter((n) => !values.includes(n));
|
|
505
|
+
if (unique.length > 0) {
|
|
506
|
+
emit([...values, ...unique]);
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
[values, emit]
|
|
510
|
+
);
|
|
511
|
+
const removeValue = useCallback3(
|
|
512
|
+
(index) => {
|
|
513
|
+
const next2 = values.filter((_, i) => i !== index);
|
|
514
|
+
emit(next2.length === 0 ? [] : next2);
|
|
515
|
+
},
|
|
516
|
+
[values, emit]
|
|
517
|
+
);
|
|
518
|
+
const handleKeyDown = useCallback3(
|
|
519
|
+
(e) => {
|
|
520
|
+
if (e.key === "Enter" || e.key === "Tab" && text.trim()) {
|
|
521
|
+
e.preventDefault();
|
|
522
|
+
addValues([text]);
|
|
523
|
+
setText("");
|
|
524
|
+
} else if (e.key === "Backspace" && text === "" && values.length > 0) {
|
|
525
|
+
removeValue(values.length - 1);
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
[text, values, addValues, removeValue]
|
|
529
|
+
);
|
|
530
|
+
const handlePaste = useCallback3(
|
|
531
|
+
(e) => {
|
|
532
|
+
const pasted = e.clipboardData.getData("text");
|
|
533
|
+
if (pasted.includes("\n")) {
|
|
534
|
+
e.preventDefault();
|
|
535
|
+
addValues(pasted.split("\n"));
|
|
536
|
+
setText("");
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
[addValues]
|
|
540
|
+
);
|
|
541
|
+
return /* @__PURE__ */ jsxs5("div", { className: "border-input focus-within:border-ring focus-within:ring-ring/50 flex min-h-9 flex-1 flex-wrap items-center gap-1 rounded-md border px-2 py-1 focus-within:ring-[3px]", children: [
|
|
542
|
+
values.map((v, i) => /* @__PURE__ */ jsxs5(Badge, { variant: "outline", className: "bg-muted gap-1 font-mono text-xs", children: [
|
|
543
|
+
v,
|
|
544
|
+
/* @__PURE__ */ jsx9(
|
|
545
|
+
"button",
|
|
546
|
+
{
|
|
547
|
+
type: "button",
|
|
548
|
+
className: "text-muted-foreground hover:text-foreground ml-0.5 cursor-pointer leading-none",
|
|
549
|
+
onClick: () => removeValue(i),
|
|
550
|
+
"aria-label": `Remove ${v}`,
|
|
551
|
+
children: "\xD7"
|
|
552
|
+
}
|
|
553
|
+
)
|
|
554
|
+
] }, `${v}-${i}`)),
|
|
555
|
+
/* @__PURE__ */ jsx9(
|
|
556
|
+
"input",
|
|
557
|
+
{
|
|
558
|
+
className: "min-w-[80px] flex-1 bg-transparent py-0.5 font-mono text-sm outline-none placeholder:text-muted-foreground",
|
|
559
|
+
type: "number",
|
|
560
|
+
value: text,
|
|
561
|
+
placeholder: values.length === 0 ? placeholder ?? "type and press Enter" : "",
|
|
562
|
+
onChange: (e) => setText(e.target.value),
|
|
563
|
+
onKeyDown: handleKeyDown,
|
|
564
|
+
onPaste: handlePaste
|
|
565
|
+
}
|
|
566
|
+
)
|
|
567
|
+
] });
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/components/condition-builder/NumberConditionEditor.tsx
|
|
571
|
+
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
572
|
+
var OP_OPTIONS2 = [
|
|
573
|
+
{ value: "eq", label: "eq" },
|
|
574
|
+
{ value: "neq", label: "neq" },
|
|
575
|
+
{ value: "gt", label: "gt" },
|
|
576
|
+
{ value: "gte", label: "gte" },
|
|
577
|
+
{ value: "lt", label: "lt" },
|
|
578
|
+
{ value: "lte", label: "lte" },
|
|
579
|
+
{ value: "in", label: "in" },
|
|
580
|
+
{ value: "nin", label: "nin" }
|
|
581
|
+
];
|
|
582
|
+
var meta2 = {
|
|
583
|
+
type: "number",
|
|
584
|
+
label: "Number",
|
|
585
|
+
description: "Compare a context key against a number or list of numbers",
|
|
586
|
+
defaults: { type: "number", key: "", op: "eq", value: 0 }
|
|
587
|
+
};
|
|
588
|
+
function NumberConditionEditor({ condition, onChange }) {
|
|
589
|
+
const rec = useMemo2(() => condition, [condition]);
|
|
590
|
+
const update = useCallback4(
|
|
591
|
+
(field, value) => {
|
|
592
|
+
onChange(buildCustomCondition({ ...rec, [field]: value, type: "number" }));
|
|
593
|
+
},
|
|
594
|
+
[rec, onChange]
|
|
595
|
+
);
|
|
596
|
+
const handleOpChange = useCallback4(
|
|
597
|
+
(newOp) => {
|
|
598
|
+
const isArrayOp = newOp === "in" || newOp === "nin";
|
|
599
|
+
const currentValue = rec.value;
|
|
600
|
+
const coercedValue = isArrayOp ? Array.isArray(currentValue) ? currentValue : currentValue !== void 0 && currentValue !== "" ? [Number(currentValue)] : [] : Array.isArray(currentValue) ? currentValue[0] ?? 0 : currentValue;
|
|
601
|
+
onChange(buildCustomCondition({ ...rec, op: newOp, value: coercedValue, type: "number" }));
|
|
602
|
+
},
|
|
603
|
+
[rec, onChange]
|
|
604
|
+
);
|
|
605
|
+
const op = rec.op;
|
|
606
|
+
const isArray = op === "in" || op === "nin";
|
|
607
|
+
return /* @__PURE__ */ jsxs6(ConditionRow, { children: [
|
|
608
|
+
/* @__PURE__ */ jsx10(
|
|
609
|
+
KeyInput,
|
|
610
|
+
{
|
|
611
|
+
value: String(rec.key ?? ""),
|
|
612
|
+
onChange: (v) => update("key", v),
|
|
613
|
+
placeholder: "e.g. score"
|
|
614
|
+
}
|
|
615
|
+
),
|
|
616
|
+
/* @__PURE__ */ jsx10(
|
|
617
|
+
OperatorSelect,
|
|
618
|
+
{
|
|
619
|
+
value: String(rec.op ?? "eq"),
|
|
620
|
+
onChange: handleOpChange,
|
|
621
|
+
options: OP_OPTIONS2
|
|
622
|
+
}
|
|
623
|
+
),
|
|
624
|
+
isArray ? /* @__PURE__ */ jsx10(
|
|
625
|
+
NumberTagInput,
|
|
626
|
+
{
|
|
627
|
+
value: rec.value ?? [],
|
|
628
|
+
onChange: (v) => update("value", v),
|
|
629
|
+
placeholder: "e.g. 200"
|
|
630
|
+
}
|
|
631
|
+
) : /* @__PURE__ */ jsx10(
|
|
632
|
+
Input,
|
|
633
|
+
{
|
|
634
|
+
type: "number",
|
|
635
|
+
className: "h-8 font-mono text-sm",
|
|
636
|
+
value: rec.value !== void 0 ? String(rec.value) : "",
|
|
637
|
+
placeholder: "e.g. 100",
|
|
638
|
+
onChange: (e) => update("value", e.target.value === "" ? "" : Number(e.target.value))
|
|
639
|
+
}
|
|
640
|
+
)
|
|
641
|
+
] });
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// src/components/condition-builder/DatetimeConditionEditor.tsx
|
|
645
|
+
import { useCallback as useCallback5, useMemo as useMemo3 } from "react";
|
|
646
|
+
|
|
647
|
+
// src/components/common/DateTimeInput.tsx
|
|
648
|
+
import { useRef, useState as useState3 } from "react";
|
|
649
|
+
import { Calendar, Code } from "lucide-react";
|
|
650
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
651
|
+
function toLocalDatetime(iso) {
|
|
652
|
+
if (!iso) return "";
|
|
653
|
+
const d = new Date(iso);
|
|
654
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
655
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
656
|
+
const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
657
|
+
const time = `${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
658
|
+
return `${date}T${time}`;
|
|
659
|
+
}
|
|
660
|
+
function fromLocalDatetime(local) {
|
|
661
|
+
if (!local) return "";
|
|
662
|
+
const d = new Date(local);
|
|
663
|
+
if (Number.isNaN(d.getTime())) return local;
|
|
664
|
+
return d.toISOString();
|
|
665
|
+
}
|
|
666
|
+
function DateTimeInput({ value, onChange }) {
|
|
667
|
+
const [rawValue, setRawValue] = useState3(value);
|
|
668
|
+
const [showRaw, setShowRaw] = useState3(false);
|
|
669
|
+
const prevValueRef = useRef(value);
|
|
670
|
+
if (prevValueRef.current !== value) {
|
|
671
|
+
prevValueRef.current = value;
|
|
672
|
+
setRawValue(value);
|
|
673
|
+
}
|
|
674
|
+
if (showRaw) {
|
|
675
|
+
return /* @__PURE__ */ jsxs7("div", { className: "flex gap-1", children: [
|
|
676
|
+
/* @__PURE__ */ jsx11(
|
|
677
|
+
Input,
|
|
678
|
+
{
|
|
679
|
+
className: "h-8 flex-1 font-mono text-xs",
|
|
680
|
+
value: rawValue,
|
|
681
|
+
placeholder: "ISO 8601 datetime",
|
|
682
|
+
onChange: (e) => {
|
|
683
|
+
setRawValue(e.target.value);
|
|
684
|
+
onChange(e.target.value);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
),
|
|
688
|
+
/* @__PURE__ */ jsx11(
|
|
689
|
+
"button",
|
|
690
|
+
{
|
|
691
|
+
type: "button",
|
|
692
|
+
className: "flex items-center justify-center w-8 h-8 text-muted-foreground hover:text-foreground",
|
|
693
|
+
"aria-label": "Switch to date picker",
|
|
694
|
+
onClick: () => setShowRaw(false),
|
|
695
|
+
children: /* @__PURE__ */ jsx11(Calendar, { className: "h-3.5 w-3.5" })
|
|
696
|
+
}
|
|
697
|
+
)
|
|
698
|
+
] });
|
|
699
|
+
}
|
|
700
|
+
return /* @__PURE__ */ jsxs7("div", { className: "flex gap-1", children: [
|
|
701
|
+
/* @__PURE__ */ jsx11(
|
|
702
|
+
Input,
|
|
703
|
+
{
|
|
704
|
+
className: "h-8 flex-1 text-xs",
|
|
705
|
+
type: "datetime-local",
|
|
706
|
+
value: toLocalDatetime(value),
|
|
707
|
+
onChange: (e) => onChange(fromLocalDatetime(e.target.value))
|
|
708
|
+
}
|
|
709
|
+
),
|
|
710
|
+
/* @__PURE__ */ jsx11(
|
|
711
|
+
"button",
|
|
712
|
+
{
|
|
713
|
+
type: "button",
|
|
714
|
+
className: "flex items-center justify-center w-8 h-8 text-muted-foreground hover:text-foreground",
|
|
715
|
+
"aria-label": "Switch to raw input",
|
|
716
|
+
onClick: () => {
|
|
717
|
+
setRawValue(value);
|
|
718
|
+
setShowRaw(true);
|
|
719
|
+
},
|
|
720
|
+
children: /* @__PURE__ */ jsx11(Code, { className: "h-3.5 w-3.5" })
|
|
721
|
+
}
|
|
722
|
+
)
|
|
723
|
+
] });
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// src/components/condition-builder/DatetimeConditionEditor.tsx
|
|
727
|
+
import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
728
|
+
var OP_OPTIONS3 = [
|
|
729
|
+
{ value: "eq", label: "eq" },
|
|
730
|
+
{ value: "gt", label: "gt" },
|
|
731
|
+
{ value: "gte", label: "gte" },
|
|
732
|
+
{ value: "lt", label: "lt" },
|
|
733
|
+
{ value: "lte", label: "lte" }
|
|
734
|
+
];
|
|
735
|
+
var meta3 = {
|
|
736
|
+
type: "datetime",
|
|
737
|
+
label: "Datetime",
|
|
738
|
+
description: "Compare a context key against a date/time",
|
|
739
|
+
defaults: { type: "datetime", key: "", op: "eq", value: (/* @__PURE__ */ new Date()).toISOString() }
|
|
740
|
+
};
|
|
741
|
+
function DatetimeConditionEditor({ condition, onChange }) {
|
|
742
|
+
const rec = useMemo3(() => condition, [condition]);
|
|
743
|
+
const update = useCallback5(
|
|
744
|
+
(field, value) => {
|
|
745
|
+
onChange(buildCustomCondition({ ...rec, [field]: value, type: "datetime" }));
|
|
746
|
+
},
|
|
747
|
+
[rec, onChange]
|
|
748
|
+
);
|
|
749
|
+
return /* @__PURE__ */ jsxs8(ConditionRow, { children: [
|
|
750
|
+
/* @__PURE__ */ jsx12(
|
|
751
|
+
KeyInput,
|
|
752
|
+
{
|
|
753
|
+
value: String(rec.key ?? ""),
|
|
754
|
+
onChange: (v) => update("key", v),
|
|
755
|
+
placeholder: "e.g. at"
|
|
756
|
+
}
|
|
757
|
+
),
|
|
758
|
+
/* @__PURE__ */ jsx12(
|
|
759
|
+
OperatorSelect,
|
|
760
|
+
{
|
|
761
|
+
value: String(rec.op ?? "eq"),
|
|
762
|
+
onChange: (v) => update("op", v),
|
|
763
|
+
options: OP_OPTIONS3
|
|
764
|
+
}
|
|
765
|
+
),
|
|
766
|
+
/* @__PURE__ */ jsx12(DateTimeInput, { value: String(rec.value ?? ""), onChange: (v) => update("value", v) })
|
|
767
|
+
] });
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// src/components/condition-builder/BoolConditionEditor.tsx
|
|
771
|
+
import { useCallback as useCallback6, useMemo as useMemo4 } from "react";
|
|
772
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
773
|
+
var OP_OPTIONS4 = [{ value: "eq", label: "eq" }];
|
|
774
|
+
var meta4 = {
|
|
775
|
+
type: "bool",
|
|
776
|
+
label: "Boolean",
|
|
777
|
+
description: "Match a context key against a boolean",
|
|
778
|
+
defaults: { type: "bool", key: "", value: true }
|
|
779
|
+
};
|
|
780
|
+
function BoolConditionEditor({ condition, onChange }) {
|
|
781
|
+
const rec = useMemo4(() => condition, [condition]);
|
|
782
|
+
const update = useCallback6(
|
|
783
|
+
(field, value) => {
|
|
784
|
+
onChange(buildCustomCondition({ ...rec, [field]: value, type: "bool" }));
|
|
785
|
+
},
|
|
786
|
+
[rec, onChange]
|
|
787
|
+
);
|
|
788
|
+
return /* @__PURE__ */ jsxs9(ConditionRow, { children: [
|
|
789
|
+
/* @__PURE__ */ jsx13(
|
|
790
|
+
KeyInput,
|
|
791
|
+
{
|
|
792
|
+
value: String(rec.key ?? ""),
|
|
793
|
+
onChange: (v) => update("key", v),
|
|
794
|
+
placeholder: "e.g. isAdmin"
|
|
795
|
+
}
|
|
796
|
+
),
|
|
797
|
+
/* @__PURE__ */ jsx13(OperatorSelect, { value: "eq", options: OP_OPTIONS4, disabled: true }),
|
|
798
|
+
/* @__PURE__ */ jsxs9(
|
|
799
|
+
Select,
|
|
800
|
+
{
|
|
801
|
+
value: String(rec.value ?? "true"),
|
|
802
|
+
onValueChange: (v) => update("value", v === "true"),
|
|
803
|
+
children: [
|
|
804
|
+
/* @__PURE__ */ jsx13(SelectTrigger, { className: "h-8 text-sm", children: /* @__PURE__ */ jsx13(SelectValue, {}) }),
|
|
805
|
+
/* @__PURE__ */ jsxs9(SelectContent, { children: [
|
|
806
|
+
/* @__PURE__ */ jsx13(SelectItem, { value: "true", children: "true" }),
|
|
807
|
+
/* @__PURE__ */ jsx13(SelectItem, { value: "false", children: "false" })
|
|
808
|
+
] })
|
|
809
|
+
]
|
|
810
|
+
}
|
|
811
|
+
)
|
|
812
|
+
] });
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// src/components/condition-builder/EnvConditionEditor.tsx
|
|
816
|
+
import { useCallback as useCallback7, useMemo as useMemo5 } from "react";
|
|
817
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
818
|
+
var OP_OPTIONS5 = [{ value: "eq", label: "eq" }];
|
|
819
|
+
var meta5 = {
|
|
820
|
+
type: "env",
|
|
821
|
+
label: "Environment",
|
|
822
|
+
description: "Match the environment name",
|
|
823
|
+
defaults: { type: "env", value: "" }
|
|
824
|
+
};
|
|
825
|
+
function EnvConditionEditor({ condition, onChange }) {
|
|
826
|
+
const rec = useMemo5(() => condition, [condition]);
|
|
827
|
+
const handleChange = useCallback7(
|
|
828
|
+
(value) => {
|
|
829
|
+
onChange(buildCustomCondition({ ...rec, value, type: "env" }));
|
|
830
|
+
},
|
|
831
|
+
[rec, onChange]
|
|
832
|
+
);
|
|
833
|
+
return /* @__PURE__ */ jsxs10(ConditionRow, { children: [
|
|
834
|
+
/* @__PURE__ */ jsx14(KeyInput, { value: "env", disabled: true }),
|
|
835
|
+
/* @__PURE__ */ jsx14(OperatorSelect, { value: "eq", options: OP_OPTIONS5, disabled: true }),
|
|
836
|
+
/* @__PURE__ */ jsx14(
|
|
837
|
+
TagInput,
|
|
838
|
+
{
|
|
839
|
+
value: rec.value ?? "",
|
|
840
|
+
onChange: handleChange,
|
|
841
|
+
placeholder: "e.g. production"
|
|
842
|
+
}
|
|
843
|
+
)
|
|
844
|
+
] });
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/components/condition-builder/StartAtConditionEditor.tsx
|
|
848
|
+
import { useMemo as useMemo6 } from "react";
|
|
849
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
850
|
+
var OP_OPTIONS6 = [{ value: "gte", label: "gte" }];
|
|
851
|
+
var meta6 = {
|
|
852
|
+
type: "startAt",
|
|
853
|
+
label: "Start At",
|
|
854
|
+
description: "Active after a specific date/time",
|
|
855
|
+
defaults: { type: "startAt", value: (/* @__PURE__ */ new Date()).toISOString() }
|
|
856
|
+
};
|
|
857
|
+
function StartAtConditionEditor({ condition, onChange }) {
|
|
858
|
+
const rec = useMemo6(() => condition, [condition]);
|
|
859
|
+
return /* @__PURE__ */ jsxs11(ConditionRow, { children: [
|
|
860
|
+
/* @__PURE__ */ jsx15(KeyInput, { value: "at", disabled: true }),
|
|
861
|
+
/* @__PURE__ */ jsx15(OperatorSelect, { value: "gte", options: OP_OPTIONS6, disabled: true }),
|
|
862
|
+
/* @__PURE__ */ jsx15(
|
|
863
|
+
DateTimeInput,
|
|
864
|
+
{
|
|
865
|
+
value: String(rec.value ?? ""),
|
|
866
|
+
onChange: (v) => onChange(buildCustomCondition({ ...rec, value: v, type: "startAt" }))
|
|
867
|
+
}
|
|
868
|
+
)
|
|
869
|
+
] });
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/components/condition-builder/EndAtConditionEditor.tsx
|
|
873
|
+
import { useMemo as useMemo7 } from "react";
|
|
874
|
+
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
875
|
+
var OP_OPTIONS7 = [{ value: "lt", label: "lt" }];
|
|
876
|
+
var meta7 = {
|
|
877
|
+
type: "endAt",
|
|
878
|
+
label: "End At",
|
|
879
|
+
description: "Active before a specific date/time",
|
|
880
|
+
defaults: { type: "endAt", value: (/* @__PURE__ */ new Date()).toISOString() }
|
|
881
|
+
};
|
|
882
|
+
function EndAtConditionEditor({ condition, onChange }) {
|
|
883
|
+
const rec = useMemo7(() => condition, [condition]);
|
|
884
|
+
return /* @__PURE__ */ jsxs12(ConditionRow, { children: [
|
|
885
|
+
/* @__PURE__ */ jsx16(KeyInput, { value: "at", disabled: true }),
|
|
886
|
+
/* @__PURE__ */ jsx16(OperatorSelect, { value: "lt", options: OP_OPTIONS7, disabled: true }),
|
|
887
|
+
/* @__PURE__ */ jsx16(
|
|
888
|
+
DateTimeInput,
|
|
889
|
+
{
|
|
890
|
+
value: String(rec.value ?? ""),
|
|
891
|
+
onChange: (v) => onChange(buildCustomCondition({ ...rec, value: v, type: "endAt" }))
|
|
892
|
+
}
|
|
893
|
+
)
|
|
894
|
+
] });
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/components/condition-builder/condition-registry.ts
|
|
898
|
+
var BUILTIN_CONDITION_TYPES = [
|
|
899
|
+
meta,
|
|
900
|
+
meta2,
|
|
901
|
+
meta3,
|
|
902
|
+
meta4,
|
|
903
|
+
meta5,
|
|
904
|
+
meta6,
|
|
905
|
+
meta7
|
|
906
|
+
];
|
|
907
|
+
var CONDITION_TYPE_MAP = new Map(BUILTIN_CONDITION_TYPES.map((m) => [m.type, m]));
|
|
908
|
+
function getConditionMeta(type) {
|
|
909
|
+
return CONDITION_TYPE_MAP.get(type);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// src/components/ui/button.tsx
|
|
913
|
+
import { cva as cva2 } from "class-variance-authority";
|
|
914
|
+
import { Slot as Slot2 } from "radix-ui";
|
|
915
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
916
|
+
var buttonVariants = cva2(
|
|
917
|
+
"inline-flex shrink-0 items-center justify-center gap-2 rounded text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 hover:cursor-pointer",
|
|
918
|
+
{
|
|
919
|
+
variants: {
|
|
920
|
+
variant: {
|
|
921
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
922
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
|
|
923
|
+
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
924
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
925
|
+
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
926
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
927
|
+
},
|
|
928
|
+
size: {
|
|
929
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
930
|
+
xs: "h-6 gap-1 rounded px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
931
|
+
sm: "h-8 gap-1.5 rounded px-3 has-[>svg]:px-2.5",
|
|
932
|
+
lg: "h-10 rounded px-6 has-[>svg]:px-4",
|
|
933
|
+
icon: "size-9",
|
|
934
|
+
"icon-xs": "size-6 rounded [&_svg:not([class*='size-'])]:size-3",
|
|
935
|
+
"icon-sm": "size-8",
|
|
936
|
+
"icon-lg": "size-10"
|
|
937
|
+
}
|
|
938
|
+
},
|
|
939
|
+
defaultVariants: {
|
|
940
|
+
variant: "default",
|
|
941
|
+
size: "default"
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
);
|
|
945
|
+
function Button({
|
|
946
|
+
className,
|
|
947
|
+
variant = "default",
|
|
948
|
+
size = "default",
|
|
949
|
+
asChild = false,
|
|
950
|
+
ref,
|
|
951
|
+
...props
|
|
952
|
+
}) {
|
|
953
|
+
const Comp = asChild ? Slot2.Root : "button";
|
|
954
|
+
return /* @__PURE__ */ jsx17(
|
|
955
|
+
Comp,
|
|
956
|
+
{
|
|
957
|
+
ref,
|
|
958
|
+
"data-slot": "button",
|
|
959
|
+
"data-variant": variant,
|
|
960
|
+
"data-size": size,
|
|
961
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
962
|
+
...props
|
|
963
|
+
}
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// src/components/ui/separator.tsx
|
|
968
|
+
import { Separator as SeparatorPrimitive } from "radix-ui";
|
|
969
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
970
|
+
function Separator({
|
|
971
|
+
className,
|
|
972
|
+
orientation = "horizontal",
|
|
973
|
+
decorative = true,
|
|
974
|
+
...props
|
|
975
|
+
}) {
|
|
976
|
+
return /* @__PURE__ */ jsx18(
|
|
977
|
+
SeparatorPrimitive.Root,
|
|
978
|
+
{
|
|
979
|
+
"data-slot": "separator",
|
|
980
|
+
decorative,
|
|
981
|
+
orientation,
|
|
982
|
+
className: cn(
|
|
983
|
+
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
|
984
|
+
className
|
|
985
|
+
),
|
|
986
|
+
...props
|
|
987
|
+
}
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// src/components/ui/scroll-area.tsx
|
|
992
|
+
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui";
|
|
993
|
+
import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
994
|
+
function ScrollArea({
|
|
995
|
+
className,
|
|
996
|
+
children,
|
|
997
|
+
...props
|
|
998
|
+
}) {
|
|
999
|
+
return /* @__PURE__ */ jsxs13(
|
|
1000
|
+
ScrollAreaPrimitive.Root,
|
|
1001
|
+
{
|
|
1002
|
+
"data-slot": "scroll-area",
|
|
1003
|
+
className: cn("relative", className),
|
|
1004
|
+
...props,
|
|
1005
|
+
children: [
|
|
1006
|
+
/* @__PURE__ */ jsx19(
|
|
1007
|
+
ScrollAreaPrimitive.Viewport,
|
|
1008
|
+
{
|
|
1009
|
+
"data-slot": "scroll-area-viewport",
|
|
1010
|
+
className: "size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1",
|
|
1011
|
+
children
|
|
1012
|
+
}
|
|
1013
|
+
),
|
|
1014
|
+
/* @__PURE__ */ jsx19(ScrollBar, {}),
|
|
1015
|
+
/* @__PURE__ */ jsx19(ScrollAreaPrimitive.Corner, {})
|
|
1016
|
+
]
|
|
1017
|
+
}
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
function ScrollBar({
|
|
1021
|
+
className,
|
|
1022
|
+
orientation = "vertical",
|
|
1023
|
+
...props
|
|
1024
|
+
}) {
|
|
1025
|
+
return /* @__PURE__ */ jsx19(
|
|
1026
|
+
ScrollAreaPrimitive.ScrollAreaScrollbar,
|
|
1027
|
+
{
|
|
1028
|
+
"data-slot": "scroll-area-scrollbar",
|
|
1029
|
+
orientation,
|
|
1030
|
+
className: cn(
|
|
1031
|
+
"flex touch-none p-px transition-colors select-none",
|
|
1032
|
+
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
|
|
1033
|
+
orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
|
|
1034
|
+
className
|
|
1035
|
+
),
|
|
1036
|
+
...props,
|
|
1037
|
+
children: /* @__PURE__ */ jsx19(
|
|
1038
|
+
ScrollAreaPrimitive.ScrollAreaThumb,
|
|
1039
|
+
{
|
|
1040
|
+
"data-slot": "scroll-area-thumb",
|
|
1041
|
+
className: "relative flex-1 rounded-full bg-border"
|
|
1042
|
+
}
|
|
1043
|
+
)
|
|
1044
|
+
}
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// src/components/ui/dialog.tsx
|
|
1049
|
+
import { XIcon } from "lucide-react";
|
|
1050
|
+
import { Dialog as DialogPrimitive } from "radix-ui";
|
|
1051
|
+
import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1052
|
+
function Dialog({ ...props }) {
|
|
1053
|
+
return /* @__PURE__ */ jsx20(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
|
|
1054
|
+
}
|
|
1055
|
+
function DialogTrigger({ ...props }) {
|
|
1056
|
+
return /* @__PURE__ */ jsx20(DialogPrimitive.Trigger, { "data-slot": "dialog-trigger", ...props });
|
|
1057
|
+
}
|
|
1058
|
+
function DialogPortal({ ...props }) {
|
|
1059
|
+
return /* @__PURE__ */ jsx20(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
|
|
1060
|
+
}
|
|
1061
|
+
function DialogClose({ ...props }) {
|
|
1062
|
+
return /* @__PURE__ */ jsx20(DialogPrimitive.Close, { "data-slot": "dialog-close", ...props });
|
|
1063
|
+
}
|
|
1064
|
+
function DialogOverlay({
|
|
1065
|
+
className,
|
|
1066
|
+
...props
|
|
1067
|
+
}) {
|
|
1068
|
+
return /* @__PURE__ */ jsx20(
|
|
1069
|
+
DialogPrimitive.Overlay,
|
|
1070
|
+
{
|
|
1071
|
+
"data-slot": "dialog-overlay",
|
|
1072
|
+
className: cn(
|
|
1073
|
+
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
1074
|
+
className
|
|
1075
|
+
),
|
|
1076
|
+
...props
|
|
1077
|
+
}
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
function DialogContent({
|
|
1081
|
+
className,
|
|
1082
|
+
children,
|
|
1083
|
+
showCloseButton = true,
|
|
1084
|
+
...props
|
|
1085
|
+
}) {
|
|
1086
|
+
return /* @__PURE__ */ jsxs14(DialogPortal, { "data-slot": "dialog-portal", children: [
|
|
1087
|
+
/* @__PURE__ */ jsx20(DialogOverlay, {}),
|
|
1088
|
+
/* @__PURE__ */ jsxs14(
|
|
1089
|
+
DialogPrimitive.Content,
|
|
1090
|
+
{
|
|
1091
|
+
"data-slot": "dialog-content",
|
|
1092
|
+
className: cn(
|
|
1093
|
+
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
|
|
1094
|
+
className
|
|
1095
|
+
),
|
|
1096
|
+
...props,
|
|
1097
|
+
children: [
|
|
1098
|
+
children,
|
|
1099
|
+
showCloseButton && /* @__PURE__ */ jsxs14(
|
|
1100
|
+
DialogPrimitive.Close,
|
|
1101
|
+
{
|
|
1102
|
+
"data-slot": "dialog-close",
|
|
1103
|
+
className: "absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
1104
|
+
children: [
|
|
1105
|
+
/* @__PURE__ */ jsx20(XIcon, {}),
|
|
1106
|
+
/* @__PURE__ */ jsx20("span", { className: "sr-only", children: "Close" })
|
|
1107
|
+
]
|
|
1108
|
+
}
|
|
1109
|
+
)
|
|
1110
|
+
]
|
|
1111
|
+
}
|
|
1112
|
+
)
|
|
1113
|
+
] });
|
|
1114
|
+
}
|
|
1115
|
+
function DialogHeader({ className, ...props }) {
|
|
1116
|
+
return /* @__PURE__ */ jsx20(
|
|
1117
|
+
"div",
|
|
1118
|
+
{
|
|
1119
|
+
"data-slot": "dialog-header",
|
|
1120
|
+
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
|
|
1121
|
+
...props
|
|
1122
|
+
}
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
function DialogFooter({
|
|
1126
|
+
className,
|
|
1127
|
+
showCloseButton = false,
|
|
1128
|
+
children,
|
|
1129
|
+
...props
|
|
1130
|
+
}) {
|
|
1131
|
+
return /* @__PURE__ */ jsxs14(
|
|
1132
|
+
"div",
|
|
1133
|
+
{
|
|
1134
|
+
"data-slot": "dialog-footer",
|
|
1135
|
+
className: cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className),
|
|
1136
|
+
...props,
|
|
1137
|
+
children: [
|
|
1138
|
+
children,
|
|
1139
|
+
showCloseButton && /* @__PURE__ */ jsx20(DialogPrimitive.Close, { asChild: true, children: /* @__PURE__ */ jsx20(Button, { variant: "outline", children: "Close" }) })
|
|
1140
|
+
]
|
|
1141
|
+
}
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
function DialogTitle({ className, ...props }) {
|
|
1145
|
+
return /* @__PURE__ */ jsx20(
|
|
1146
|
+
DialogPrimitive.Title,
|
|
1147
|
+
{
|
|
1148
|
+
"data-slot": "dialog-title",
|
|
1149
|
+
className: cn("text-lg leading-none font-semibold", className),
|
|
1150
|
+
...props
|
|
1151
|
+
}
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
function DialogDescription({
|
|
1155
|
+
className,
|
|
1156
|
+
...props
|
|
1157
|
+
}) {
|
|
1158
|
+
return /* @__PURE__ */ jsx20(
|
|
1159
|
+
DialogPrimitive.Description,
|
|
1160
|
+
{
|
|
1161
|
+
"data-slot": "dialog-description",
|
|
1162
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
1163
|
+
...props
|
|
1164
|
+
}
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// src/components/ui/dropdown-menu.tsx
|
|
1169
|
+
import { CheckIcon as CheckIcon2, ChevronRightIcon, CircleIcon } from "lucide-react";
|
|
1170
|
+
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
1171
|
+
import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1172
|
+
function DropdownMenu({ ...props }) {
|
|
1173
|
+
return /* @__PURE__ */ jsx21(DropdownMenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
|
|
1174
|
+
}
|
|
1175
|
+
function DropdownMenuTrigger({
|
|
1176
|
+
...props
|
|
1177
|
+
}) {
|
|
1178
|
+
return /* @__PURE__ */ jsx21(DropdownMenuPrimitive.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
|
|
1179
|
+
}
|
|
1180
|
+
function DropdownMenuContent({
|
|
1181
|
+
className,
|
|
1182
|
+
sideOffset = 4,
|
|
1183
|
+
...props
|
|
1184
|
+
}) {
|
|
1185
|
+
return /* @__PURE__ */ jsx21(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx21(
|
|
1186
|
+
DropdownMenuPrimitive.Content,
|
|
1187
|
+
{
|
|
1188
|
+
"data-slot": "dropdown-menu-content",
|
|
1189
|
+
sideOffset,
|
|
1190
|
+
className: cn(
|
|
1191
|
+
"z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
1192
|
+
className
|
|
1193
|
+
),
|
|
1194
|
+
...props
|
|
1195
|
+
}
|
|
1196
|
+
) });
|
|
1197
|
+
}
|
|
1198
|
+
function DropdownMenuItem({
|
|
1199
|
+
className,
|
|
1200
|
+
inset,
|
|
1201
|
+
variant = "default",
|
|
1202
|
+
...props
|
|
1203
|
+
}) {
|
|
1204
|
+
return /* @__PURE__ */ jsx21(
|
|
1205
|
+
DropdownMenuPrimitive.Item,
|
|
1206
|
+
{
|
|
1207
|
+
"data-slot": "dropdown-menu-item",
|
|
1208
|
+
"data-inset": inset,
|
|
1209
|
+
"data-variant": variant,
|
|
1210
|
+
className: cn(
|
|
1211
|
+
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
|
|
1212
|
+
className
|
|
1213
|
+
),
|
|
1214
|
+
...props
|
|
1215
|
+
}
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
function DropdownMenuSeparator({
|
|
1219
|
+
className,
|
|
1220
|
+
...props
|
|
1221
|
+
}) {
|
|
1222
|
+
return /* @__PURE__ */ jsx21(
|
|
1223
|
+
DropdownMenuPrimitive.Separator,
|
|
1224
|
+
{
|
|
1225
|
+
"data-slot": "dropdown-menu-separator",
|
|
1226
|
+
className: cn("-mx-1 my-1 h-px bg-border", className),
|
|
1227
|
+
...props
|
|
1228
|
+
}
|
|
1229
|
+
);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// src/components/ui/label.tsx
|
|
1233
|
+
import { Label as LabelPrimitive } from "radix-ui";
|
|
1234
|
+
import { jsx as jsx22 } from "react/jsx-runtime";
|
|
1235
|
+
function Label({ className, ...props }) {
|
|
1236
|
+
return /* @__PURE__ */ jsx22(
|
|
1237
|
+
LabelPrimitive.Root,
|
|
1238
|
+
{
|
|
1239
|
+
"data-slot": "label",
|
|
1240
|
+
className: cn(
|
|
1241
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
1242
|
+
className
|
|
1243
|
+
),
|
|
1244
|
+
...props
|
|
1245
|
+
}
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// src/components/ui/switch.tsx
|
|
1250
|
+
import { Switch as SwitchPrimitive } from "radix-ui";
|
|
1251
|
+
import { jsx as jsx23 } from "react/jsx-runtime";
|
|
1252
|
+
function Switch({ className, ...props }) {
|
|
1253
|
+
return /* @__PURE__ */ jsx23(
|
|
1254
|
+
SwitchPrimitive.Root,
|
|
1255
|
+
{
|
|
1256
|
+
"data-slot": "switch",
|
|
1257
|
+
className: cn(
|
|
1258
|
+
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
|
1259
|
+
className
|
|
1260
|
+
),
|
|
1261
|
+
...props,
|
|
1262
|
+
children: /* @__PURE__ */ jsx23(
|
|
1263
|
+
SwitchPrimitive.Thumb,
|
|
1264
|
+
{
|
|
1265
|
+
className: cn(
|
|
1266
|
+
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
|
|
1267
|
+
)
|
|
1268
|
+
}
|
|
1269
|
+
)
|
|
1270
|
+
}
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// src/components/ui/textarea.tsx
|
|
1275
|
+
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
1276
|
+
function Textarea({ className, ...props }) {
|
|
1277
|
+
return /* @__PURE__ */ jsx24(
|
|
1278
|
+
"textarea",
|
|
1279
|
+
{
|
|
1280
|
+
"data-slot": "textarea",
|
|
1281
|
+
className: cn(
|
|
1282
|
+
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
|
|
1283
|
+
className
|
|
1284
|
+
),
|
|
1285
|
+
...props
|
|
1286
|
+
}
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// src/components/ui/popover.tsx
|
|
1291
|
+
import { Popover as PopoverPrimitive } from "radix-ui";
|
|
1292
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
1293
|
+
function Popover({ ...props }) {
|
|
1294
|
+
return /* @__PURE__ */ jsx25(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
|
|
1295
|
+
}
|
|
1296
|
+
function PopoverTrigger({ ...props }) {
|
|
1297
|
+
return /* @__PURE__ */ jsx25(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
|
|
1298
|
+
}
|
|
1299
|
+
function PopoverContent({
|
|
1300
|
+
className,
|
|
1301
|
+
align = "center",
|
|
1302
|
+
sideOffset = 4,
|
|
1303
|
+
...props
|
|
1304
|
+
}) {
|
|
1305
|
+
return /* @__PURE__ */ jsx25(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx25(
|
|
1306
|
+
PopoverPrimitive.Content,
|
|
1307
|
+
{
|
|
1308
|
+
"data-slot": "popover-content",
|
|
1309
|
+
align,
|
|
1310
|
+
sideOffset,
|
|
1311
|
+
className: cn(
|
|
1312
|
+
"z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
1313
|
+
className
|
|
1314
|
+
),
|
|
1315
|
+
...props
|
|
1316
|
+
}
|
|
1317
|
+
) });
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// src/components/ui/tabs.tsx
|
|
1321
|
+
import { Tabs as TabsPrimitive } from "radix-ui";
|
|
1322
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
1323
|
+
function Tabs({ ...props }) {
|
|
1324
|
+
return /* @__PURE__ */ jsx26(TabsPrimitive.Root, { "data-slot": "tabs", ...props });
|
|
1325
|
+
}
|
|
1326
|
+
function TabsList({ className, ...props }) {
|
|
1327
|
+
return /* @__PURE__ */ jsx26(
|
|
1328
|
+
TabsPrimitive.List,
|
|
1329
|
+
{
|
|
1330
|
+
"data-slot": "tabs-list",
|
|
1331
|
+
className: cn(
|
|
1332
|
+
"inline-flex items-center justify-center gap-1 rounded-lg bg-muted p-1 text-muted-foreground data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch data-[orientation=vertical]:justify-start data-[orientation=vertical]:gap-0 data-[orientation=vertical]:rounded-none data-[orientation=vertical]:bg-transparent data-[orientation=vertical]:p-0",
|
|
1333
|
+
className
|
|
1334
|
+
),
|
|
1335
|
+
...props
|
|
1336
|
+
}
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
function TabsTrigger({ className, ...props }) {
|
|
1340
|
+
return /* @__PURE__ */ jsx26(
|
|
1341
|
+
TabsPrimitive.Trigger,
|
|
1342
|
+
{
|
|
1343
|
+
"data-slot": "tabs-trigger",
|
|
1344
|
+
className: cn(
|
|
1345
|
+
"inline-flex items-center justify-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm data-[orientation=vertical]:justify-start data-[orientation=vertical]:rounded-none data-[orientation=vertical]:border-l-2 data-[orientation=vertical]:border-l-transparent data-[orientation=vertical]:px-3 data-[orientation=vertical]:py-2.5 data-[orientation=vertical]:text-sm data-[orientation=vertical]:font-normal data-[orientation=vertical]:text-muted-foreground data-[orientation=vertical]:shadow-none data-[orientation=vertical]:hover:bg-muted data-[orientation=vertical]:data-[state=active]:border-l-primary data-[orientation=vertical]:data-[state=active]:bg-accent data-[orientation=vertical]:data-[state=active]:font-medium data-[orientation=vertical]:data-[state=active]:text-accent-foreground",
|
|
1346
|
+
className
|
|
1347
|
+
),
|
|
1348
|
+
...props
|
|
1349
|
+
}
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
function TabsContent({ className, ...props }) {
|
|
1353
|
+
return /* @__PURE__ */ jsx26(
|
|
1354
|
+
TabsPrimitive.Content,
|
|
1355
|
+
{
|
|
1356
|
+
"data-slot": "tabs-content",
|
|
1357
|
+
className: cn(
|
|
1358
|
+
"flex-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
1359
|
+
className
|
|
1360
|
+
),
|
|
1361
|
+
...props
|
|
1362
|
+
}
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// src/components/common/ValueInput.tsx
|
|
1367
|
+
import { useRef as useRef2, useState as useState4 } from "react";
|
|
1368
|
+
import { Fragment, jsx as jsx27, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1369
|
+
function detectType(value) {
|
|
1370
|
+
if (typeof value === "boolean") return "boolean";
|
|
1371
|
+
if (typeof value === "number") return "number";
|
|
1372
|
+
if (typeof value === "object" && value !== null) return "json";
|
|
1373
|
+
return "string";
|
|
1374
|
+
}
|
|
1375
|
+
function ValueInput({ value, onChange, placeholder }) {
|
|
1376
|
+
const [type, setType] = useState4(() => detectType(value));
|
|
1377
|
+
const [jsonText, setJsonText] = useState4(
|
|
1378
|
+
() => type === "json" ? JSON.stringify(value, null, 2) : ""
|
|
1379
|
+
);
|
|
1380
|
+
const [jsonError, setJsonError] = useState4(null);
|
|
1381
|
+
const prevValueRef = useRef2(value);
|
|
1382
|
+
if (prevValueRef.current !== value) {
|
|
1383
|
+
prevValueRef.current = value;
|
|
1384
|
+
const newType = detectType(value);
|
|
1385
|
+
if (newType !== type) {
|
|
1386
|
+
setType(newType);
|
|
1387
|
+
}
|
|
1388
|
+
if (newType === "json") {
|
|
1389
|
+
try {
|
|
1390
|
+
setJsonText(JSON.stringify(value, null, 2));
|
|
1391
|
+
setJsonError(null);
|
|
1392
|
+
} catch {
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
function handleTypeChange(newType) {
|
|
1397
|
+
const t = newType;
|
|
1398
|
+
setType(t);
|
|
1399
|
+
setJsonError(null);
|
|
1400
|
+
switch (t) {
|
|
1401
|
+
case "string":
|
|
1402
|
+
onChange(String(value ?? ""));
|
|
1403
|
+
break;
|
|
1404
|
+
case "number":
|
|
1405
|
+
onChange(Number(value) || 0);
|
|
1406
|
+
break;
|
|
1407
|
+
case "boolean":
|
|
1408
|
+
onChange(Boolean(value));
|
|
1409
|
+
break;
|
|
1410
|
+
case "json":
|
|
1411
|
+
try {
|
|
1412
|
+
const text = JSON.stringify(value, null, 2);
|
|
1413
|
+
setJsonText(text);
|
|
1414
|
+
onChange(value);
|
|
1415
|
+
} catch {
|
|
1416
|
+
setJsonText("{}");
|
|
1417
|
+
onChange({});
|
|
1418
|
+
}
|
|
1419
|
+
break;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
function handleJsonBlur() {
|
|
1423
|
+
try {
|
|
1424
|
+
const parsed = JSON.parse(jsonText);
|
|
1425
|
+
setJsonError(null);
|
|
1426
|
+
onChange(parsed);
|
|
1427
|
+
} catch {
|
|
1428
|
+
setJsonError("Invalid JSON");
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
function handlePrettify() {
|
|
1432
|
+
try {
|
|
1433
|
+
const parsed = JSON.parse(jsonText);
|
|
1434
|
+
setJsonText(JSON.stringify(parsed, null, 2));
|
|
1435
|
+
setJsonError(null);
|
|
1436
|
+
} catch {
|
|
1437
|
+
setJsonError("Invalid JSON");
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return /* @__PURE__ */ jsxs16("div", { className: "flex items-start gap-2", children: [
|
|
1441
|
+
/* @__PURE__ */ jsxs16(Select, { value: type, onValueChange: handleTypeChange, children: [
|
|
1442
|
+
/* @__PURE__ */ jsx27(SelectTrigger, { className: "h-9 w-[110px] shrink-0 text-sm", children: /* @__PURE__ */ jsx27(SelectValue, {}) }),
|
|
1443
|
+
/* @__PURE__ */ jsxs16(SelectContent, { children: [
|
|
1444
|
+
/* @__PURE__ */ jsx27(SelectItem, { value: "string", children: "String" }),
|
|
1445
|
+
/* @__PURE__ */ jsx27(SelectItem, { value: "number", children: "Number" }),
|
|
1446
|
+
/* @__PURE__ */ jsx27(SelectItem, { value: "boolean", children: "Boolean" }),
|
|
1447
|
+
/* @__PURE__ */ jsx27(SelectItem, { value: "json", children: "JSON" })
|
|
1448
|
+
] })
|
|
1449
|
+
] }),
|
|
1450
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex-1", children: [
|
|
1451
|
+
type === "string" && /* @__PURE__ */ jsx27(
|
|
1452
|
+
Input,
|
|
1453
|
+
{
|
|
1454
|
+
className: "h-9 font-mono text-sm",
|
|
1455
|
+
value: String(value ?? ""),
|
|
1456
|
+
placeholder,
|
|
1457
|
+
onChange: (e) => onChange(e.target.value)
|
|
1458
|
+
}
|
|
1459
|
+
),
|
|
1460
|
+
type === "number" && /* @__PURE__ */ jsx27(
|
|
1461
|
+
Input,
|
|
1462
|
+
{
|
|
1463
|
+
className: "h-9 font-mono text-sm",
|
|
1464
|
+
type: "number",
|
|
1465
|
+
value: String(value ?? 0),
|
|
1466
|
+
onChange: (e) => onChange(Number(e.target.value))
|
|
1467
|
+
}
|
|
1468
|
+
),
|
|
1469
|
+
type === "boolean" && /* @__PURE__ */ jsxs16(Select, { value: String(Boolean(value)), onValueChange: (v) => onChange(v === "true"), children: [
|
|
1470
|
+
/* @__PURE__ */ jsx27(SelectTrigger, { className: "h-9 text-sm", children: /* @__PURE__ */ jsx27(SelectValue, {}) }),
|
|
1471
|
+
/* @__PURE__ */ jsxs16(SelectContent, { children: [
|
|
1472
|
+
/* @__PURE__ */ jsx27(SelectItem, { value: "true", children: "true" }),
|
|
1473
|
+
/* @__PURE__ */ jsx27(SelectItem, { value: "false", children: "false" })
|
|
1474
|
+
] })
|
|
1475
|
+
] }),
|
|
1476
|
+
type === "json" && /* @__PURE__ */ jsxs16(Fragment, { children: [
|
|
1477
|
+
/* @__PURE__ */ jsx27(
|
|
1478
|
+
Textarea,
|
|
1479
|
+
{
|
|
1480
|
+
className: "font-mono text-sm",
|
|
1481
|
+
rows: 4,
|
|
1482
|
+
value: jsonText,
|
|
1483
|
+
onChange: (e) => setJsonText(e.target.value),
|
|
1484
|
+
onBlur: handleJsonBlur
|
|
1485
|
+
}
|
|
1486
|
+
),
|
|
1487
|
+
/* @__PURE__ */ jsxs16("div", { className: "mt-1 flex items-center gap-2", children: [
|
|
1488
|
+
jsonError && /* @__PURE__ */ jsx27("p", { className: "text-xs text-destructive", children: jsonError }),
|
|
1489
|
+
/* @__PURE__ */ jsx27(
|
|
1490
|
+
Button,
|
|
1491
|
+
{
|
|
1492
|
+
type: "button",
|
|
1493
|
+
variant: "ghost",
|
|
1494
|
+
size: "sm",
|
|
1495
|
+
className: "ml-auto h-6 px-2 text-xs",
|
|
1496
|
+
onClick: handlePrettify,
|
|
1497
|
+
children: "Prettify"
|
|
1498
|
+
}
|
|
1499
|
+
)
|
|
1500
|
+
] })
|
|
1501
|
+
] })
|
|
1502
|
+
] })
|
|
1503
|
+
] });
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// src/components/common/ValidationMessage.tsx
|
|
1507
|
+
import { jsx as jsx28, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1508
|
+
function ValidationMessage({ errors }) {
|
|
1509
|
+
if (!errors || errors.length === 0) return null;
|
|
1510
|
+
return /* @__PURE__ */ jsx28("div", { className: "space-y-1", children: errors.map((err, i) => /* @__PURE__ */ jsxs17("p", { className: "text-xs text-destructive", children: [
|
|
1511
|
+
err.path.length > 0 && /* @__PURE__ */ jsxs17("span", { className: "font-mono text-destructive/70", children: [
|
|
1512
|
+
err.path.join("."),
|
|
1513
|
+
" "
|
|
1514
|
+
] }),
|
|
1515
|
+
err.message
|
|
1516
|
+
] }, i)) });
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// src/components/common/ThemeToggle.tsx
|
|
1520
|
+
import { Moon, Sun, Monitor } from "lucide-react";
|
|
1521
|
+
import { jsx as jsx29 } from "react/jsx-runtime";
|
|
1522
|
+
var icons = {
|
|
1523
|
+
light: Sun,
|
|
1524
|
+
dark: Moon,
|
|
1525
|
+
system: Monitor
|
|
1526
|
+
};
|
|
1527
|
+
var next = {
|
|
1528
|
+
light: "dark",
|
|
1529
|
+
dark: "system",
|
|
1530
|
+
system: "light"
|
|
1531
|
+
};
|
|
1532
|
+
function ThemeToggle({ theme, onToggle }) {
|
|
1533
|
+
const Icon = icons[theme];
|
|
1534
|
+
return /* @__PURE__ */ jsx29(
|
|
1535
|
+
Button,
|
|
1536
|
+
{
|
|
1537
|
+
variant: "ghost",
|
|
1538
|
+
size: "icon",
|
|
1539
|
+
onClick: () => onToggle(next[theme]),
|
|
1540
|
+
"aria-label": `Switch to ${next[theme]} theme`,
|
|
1541
|
+
children: /* @__PURE__ */ jsx29(Icon, { className: "h-4 w-4" })
|
|
1542
|
+
}
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// src/components/common/ErrorBoundary.tsx
|
|
1547
|
+
import { Component } from "react";
|
|
1548
|
+
import { jsx as jsx30, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1549
|
+
function defaultOnError(error, info) {
|
|
1550
|
+
console.error("ErrorBoundary caught an error:", error, info);
|
|
1551
|
+
}
|
|
1552
|
+
var ErrorBoundary = class extends Component {
|
|
1553
|
+
constructor(props) {
|
|
1554
|
+
super(props);
|
|
1555
|
+
this.state = { error: null };
|
|
1556
|
+
}
|
|
1557
|
+
static getDerivedStateFromError(error) {
|
|
1558
|
+
return { error };
|
|
1559
|
+
}
|
|
1560
|
+
componentDidCatch(error, info) {
|
|
1561
|
+
const onError = this.props.onError ?? defaultOnError;
|
|
1562
|
+
onError(error, info);
|
|
1563
|
+
}
|
|
1564
|
+
handleRetry = () => {
|
|
1565
|
+
this.setState({ error: null });
|
|
1566
|
+
};
|
|
1567
|
+
render() {
|
|
1568
|
+
if (this.state.error) {
|
|
1569
|
+
if (this.props.fallback) {
|
|
1570
|
+
return this.props.fallback;
|
|
1571
|
+
}
|
|
1572
|
+
return /* @__PURE__ */ jsxs18(
|
|
1573
|
+
"div",
|
|
1574
|
+
{
|
|
1575
|
+
role: "alert",
|
|
1576
|
+
className: "flex flex-col items-center justify-center gap-3 p-6 text-center",
|
|
1577
|
+
children: [
|
|
1578
|
+
/* @__PURE__ */ jsx30("p", { className: "text-sm font-medium text-destructive", children: "Something went wrong" }),
|
|
1579
|
+
/* @__PURE__ */ jsx30("p", { className: "text-xs text-muted-foreground", children: this.state.error.message }),
|
|
1580
|
+
/* @__PURE__ */ jsx30(
|
|
1581
|
+
"button",
|
|
1582
|
+
{
|
|
1583
|
+
type: "button",
|
|
1584
|
+
className: "rounded-md border border-border bg-background px-3 py-1.5 text-sm font-medium hover:bg-accent",
|
|
1585
|
+
onClick: this.handleRetry,
|
|
1586
|
+
children: "Try again"
|
|
1587
|
+
}
|
|
1588
|
+
)
|
|
1589
|
+
]
|
|
1590
|
+
}
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
return this.props.children;
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1597
|
+
// src/components/ui/alert-dialog.tsx
|
|
1598
|
+
import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
|
|
1599
|
+
import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1600
|
+
function AlertDialog({ ...props }) {
|
|
1601
|
+
return /* @__PURE__ */ jsx31(AlertDialogPrimitive.Root, { "data-slot": "alert-dialog", ...props });
|
|
1602
|
+
}
|
|
1603
|
+
function AlertDialogTrigger({
|
|
1604
|
+
...props
|
|
1605
|
+
}) {
|
|
1606
|
+
return /* @__PURE__ */ jsx31(AlertDialogPrimitive.Trigger, { "data-slot": "alert-dialog-trigger", ...props });
|
|
1607
|
+
}
|
|
1608
|
+
function AlertDialogPortal({ ...props }) {
|
|
1609
|
+
return /* @__PURE__ */ jsx31(AlertDialogPrimitive.Portal, { "data-slot": "alert-dialog-portal", ...props });
|
|
1610
|
+
}
|
|
1611
|
+
function AlertDialogOverlay({
|
|
1612
|
+
className,
|
|
1613
|
+
...props
|
|
1614
|
+
}) {
|
|
1615
|
+
return /* @__PURE__ */ jsx31(
|
|
1616
|
+
AlertDialogPrimitive.Overlay,
|
|
1617
|
+
{
|
|
1618
|
+
"data-slot": "alert-dialog-overlay",
|
|
1619
|
+
className: cn(
|
|
1620
|
+
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
1621
|
+
className
|
|
1622
|
+
),
|
|
1623
|
+
...props
|
|
1624
|
+
}
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
function AlertDialogContent({
|
|
1628
|
+
className,
|
|
1629
|
+
...props
|
|
1630
|
+
}) {
|
|
1631
|
+
return /* @__PURE__ */ jsxs19(AlertDialogPortal, { children: [
|
|
1632
|
+
/* @__PURE__ */ jsx31(AlertDialogOverlay, {}),
|
|
1633
|
+
/* @__PURE__ */ jsx31(
|
|
1634
|
+
AlertDialogPrimitive.Content,
|
|
1635
|
+
{
|
|
1636
|
+
"data-slot": "alert-dialog-content",
|
|
1637
|
+
className: cn(
|
|
1638
|
+
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
|
|
1639
|
+
className
|
|
1640
|
+
),
|
|
1641
|
+
...props
|
|
1642
|
+
}
|
|
1643
|
+
)
|
|
1644
|
+
] });
|
|
1645
|
+
}
|
|
1646
|
+
function AlertDialogHeader({ className, ...props }) {
|
|
1647
|
+
return /* @__PURE__ */ jsx31(
|
|
1648
|
+
"div",
|
|
1649
|
+
{
|
|
1650
|
+
"data-slot": "alert-dialog-header",
|
|
1651
|
+
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
|
|
1652
|
+
...props
|
|
1653
|
+
}
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
function AlertDialogFooter({ className, ...props }) {
|
|
1657
|
+
return /* @__PURE__ */ jsx31(
|
|
1658
|
+
"div",
|
|
1659
|
+
{
|
|
1660
|
+
"data-slot": "alert-dialog-footer",
|
|
1661
|
+
className: cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className),
|
|
1662
|
+
...props
|
|
1663
|
+
}
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
function AlertDialogTitle({
|
|
1667
|
+
className,
|
|
1668
|
+
...props
|
|
1669
|
+
}) {
|
|
1670
|
+
return /* @__PURE__ */ jsx31(
|
|
1671
|
+
AlertDialogPrimitive.Title,
|
|
1672
|
+
{
|
|
1673
|
+
"data-slot": "alert-dialog-title",
|
|
1674
|
+
className: cn("text-lg font-semibold", className),
|
|
1675
|
+
...props
|
|
1676
|
+
}
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
function AlertDialogDescription({
|
|
1680
|
+
className,
|
|
1681
|
+
...props
|
|
1682
|
+
}) {
|
|
1683
|
+
return /* @__PURE__ */ jsx31(
|
|
1684
|
+
AlertDialogPrimitive.Description,
|
|
1685
|
+
{
|
|
1686
|
+
"data-slot": "alert-dialog-description",
|
|
1687
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
1688
|
+
...props
|
|
1689
|
+
}
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
function AlertDialogAction({
|
|
1693
|
+
className,
|
|
1694
|
+
...props
|
|
1695
|
+
}) {
|
|
1696
|
+
return /* @__PURE__ */ jsx31(AlertDialogPrimitive.Action, { className: cn(buttonVariants(), className), ...props });
|
|
1697
|
+
}
|
|
1698
|
+
function AlertDialogCancel({
|
|
1699
|
+
className,
|
|
1700
|
+
...props
|
|
1701
|
+
}) {
|
|
1702
|
+
return /* @__PURE__ */ jsx31(
|
|
1703
|
+
AlertDialogPrimitive.Cancel,
|
|
1704
|
+
{
|
|
1705
|
+
className: cn(buttonVariants({ variant: "outline" }), className),
|
|
1706
|
+
...props
|
|
1707
|
+
}
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
// src/components/common/ConfirmDialog.tsx
|
|
1712
|
+
import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1713
|
+
function ConfirmDialog({
|
|
1714
|
+
title,
|
|
1715
|
+
description,
|
|
1716
|
+
actionLabel,
|
|
1717
|
+
onConfirm,
|
|
1718
|
+
open,
|
|
1719
|
+
onOpenChange,
|
|
1720
|
+
children
|
|
1721
|
+
}) {
|
|
1722
|
+
return /* @__PURE__ */ jsxs20(AlertDialog, { open, onOpenChange, children: [
|
|
1723
|
+
/* @__PURE__ */ jsx32(AlertDialogTrigger, { asChild: true, children }),
|
|
1724
|
+
/* @__PURE__ */ jsxs20(AlertDialogContent, { children: [
|
|
1725
|
+
/* @__PURE__ */ jsxs20(AlertDialogHeader, { children: [
|
|
1726
|
+
/* @__PURE__ */ jsx32(AlertDialogTitle, { children: title }),
|
|
1727
|
+
/* @__PURE__ */ jsx32(AlertDialogDescription, { children: description })
|
|
1728
|
+
] }),
|
|
1729
|
+
/* @__PURE__ */ jsxs20(AlertDialogFooter, { children: [
|
|
1730
|
+
/* @__PURE__ */ jsx32(AlertDialogCancel, { children: "Cancel" }),
|
|
1731
|
+
/* @__PURE__ */ jsx32(
|
|
1732
|
+
AlertDialogAction,
|
|
1733
|
+
{
|
|
1734
|
+
className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
1735
|
+
onClick: onConfirm,
|
|
1736
|
+
children: actionLabel
|
|
1737
|
+
}
|
|
1738
|
+
)
|
|
1739
|
+
] })
|
|
1740
|
+
] })
|
|
1741
|
+
] });
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
// src/components/condition-builder/ConditionBuilder.tsx
|
|
1745
|
+
import { Fragment as Fragment5 } from "react";
|
|
1746
|
+
|
|
1747
|
+
// src/components/condition-builder/ConditionBlock.tsx
|
|
1748
|
+
import { memo as memo2 } from "react";
|
|
1749
|
+
import { isAndCondition, isOrCondition } from "showwhat";
|
|
1750
|
+
import { X as X2 } from "lucide-react";
|
|
1751
|
+
|
|
1752
|
+
// src/components/condition-builder/CustomConditionEditor.tsx
|
|
1753
|
+
import { useCallback as useCallback8, useRef as useRef3, useState as useState5 } from "react";
|
|
1754
|
+
import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1755
|
+
function extractArgs(condition) {
|
|
1756
|
+
return Object.fromEntries(Object.entries(condition).filter(([k]) => k !== "type" && k !== "id"));
|
|
1757
|
+
}
|
|
1758
|
+
function argsToText(args) {
|
|
1759
|
+
if (Object.keys(args).length === 0) return "";
|
|
1760
|
+
return JSON.stringify(args, null, 2);
|
|
1761
|
+
}
|
|
1762
|
+
function CustomConditionEditor({ condition, onChange }) {
|
|
1763
|
+
const rec = condition;
|
|
1764
|
+
const [text, setText] = useState5(() => argsToText(extractArgs(rec)));
|
|
1765
|
+
const [jsonError, setJsonError] = useState5(null);
|
|
1766
|
+
const focusedRef = useRef3(false);
|
|
1767
|
+
const prevConditionRef = useRef3(condition);
|
|
1768
|
+
if (prevConditionRef.current !== condition) {
|
|
1769
|
+
prevConditionRef.current = condition;
|
|
1770
|
+
if (!focusedRef.current) {
|
|
1771
|
+
const derived = argsToText(extractArgs(rec));
|
|
1772
|
+
if (derived !== text) {
|
|
1773
|
+
setText(derived);
|
|
1774
|
+
setJsonError(null);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
const handleArgsBlur = useCallback8(() => {
|
|
1779
|
+
focusedRef.current = false;
|
|
1780
|
+
const trimmed = text.trim();
|
|
1781
|
+
if (trimmed === "") {
|
|
1782
|
+
setJsonError(null);
|
|
1783
|
+
onChange({ type: rec.type, ...rec.id ? { id: rec.id } : {} });
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
try {
|
|
1787
|
+
const parsed = JSON.parse(trimmed);
|
|
1788
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1789
|
+
setJsonError("Args must be a JSON object");
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
setJsonError(null);
|
|
1793
|
+
onChange({
|
|
1794
|
+
type: rec.type,
|
|
1795
|
+
...rec.id ? { id: rec.id } : {},
|
|
1796
|
+
...parsed
|
|
1797
|
+
});
|
|
1798
|
+
} catch {
|
|
1799
|
+
setJsonError("Invalid JSON");
|
|
1800
|
+
}
|
|
1801
|
+
}, [text, rec.type, rec.id, onChange]);
|
|
1802
|
+
const handleTypeChange = useCallback8(
|
|
1803
|
+
(e) => {
|
|
1804
|
+
onChange({ ...rec, type: e.target.value });
|
|
1805
|
+
},
|
|
1806
|
+
[rec, onChange]
|
|
1807
|
+
);
|
|
1808
|
+
return /* @__PURE__ */ jsxs21("div", { className: "flex-1 space-y-2", children: [
|
|
1809
|
+
/* @__PURE__ */ jsxs21("div", { className: "space-y-1", children: [
|
|
1810
|
+
/* @__PURE__ */ jsx33(Label, { className: "text-xs text-muted-foreground", children: "Type" }),
|
|
1811
|
+
/* @__PURE__ */ jsx33(
|
|
1812
|
+
Input,
|
|
1813
|
+
{
|
|
1814
|
+
className: "h-8 font-mono text-sm",
|
|
1815
|
+
value: String(rec.type ?? ""),
|
|
1816
|
+
placeholder: "e.g. geoLocation, percentage",
|
|
1817
|
+
onChange: handleTypeChange
|
|
1818
|
+
}
|
|
1819
|
+
)
|
|
1820
|
+
] }),
|
|
1821
|
+
/* @__PURE__ */ jsxs21("div", { className: "space-y-1", children: [
|
|
1822
|
+
/* @__PURE__ */ jsx33(Label, { className: "text-xs text-muted-foreground", children: "Args" }),
|
|
1823
|
+
/* @__PURE__ */ jsx33(
|
|
1824
|
+
Textarea,
|
|
1825
|
+
{
|
|
1826
|
+
className: "font-mono text-sm",
|
|
1827
|
+
rows: 3,
|
|
1828
|
+
value: text,
|
|
1829
|
+
placeholder: 'e.g. {"region": "us-east", "threshold": 50} (optional)',
|
|
1830
|
+
onChange: (e) => {
|
|
1831
|
+
setText(e.target.value);
|
|
1832
|
+
setJsonError(null);
|
|
1833
|
+
},
|
|
1834
|
+
onFocus: () => {
|
|
1835
|
+
focusedRef.current = true;
|
|
1836
|
+
},
|
|
1837
|
+
onBlur: handleArgsBlur
|
|
1838
|
+
}
|
|
1839
|
+
),
|
|
1840
|
+
jsonError && /* @__PURE__ */ jsx33("p", { className: "mt-1 text-xs text-destructive", children: jsonError })
|
|
1841
|
+
] })
|
|
1842
|
+
] });
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
// src/components/condition-builder/ConditionExtensionsContext.tsx
|
|
1846
|
+
import { createContext, useContext } from "react";
|
|
1847
|
+
var ConditionExtensionsContext = createContext(null);
|
|
1848
|
+
var ConditionExtensionsProvider = ConditionExtensionsContext.Provider;
|
|
1849
|
+
function useConditionExtensions() {
|
|
1850
|
+
return useContext(ConditionExtensionsContext);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// src/components/condition-builder/ConditionValueEditor.tsx
|
|
1854
|
+
import { jsx as jsx34 } from "react/jsx-runtime";
|
|
1855
|
+
function ConditionValueEditor({ condition, onChange }) {
|
|
1856
|
+
const extensions = useConditionExtensions();
|
|
1857
|
+
switch (condition.type) {
|
|
1858
|
+
case "string":
|
|
1859
|
+
return /* @__PURE__ */ jsx34(StringConditionEditor, { condition, onChange });
|
|
1860
|
+
case "number":
|
|
1861
|
+
return /* @__PURE__ */ jsx34(NumberConditionEditor, { condition, onChange });
|
|
1862
|
+
case "datetime":
|
|
1863
|
+
return /* @__PURE__ */ jsx34(DatetimeConditionEditor, { condition, onChange });
|
|
1864
|
+
case "bool":
|
|
1865
|
+
return /* @__PURE__ */ jsx34(BoolConditionEditor, { condition, onChange });
|
|
1866
|
+
case "env":
|
|
1867
|
+
return /* @__PURE__ */ jsx34(EnvConditionEditor, { condition, onChange });
|
|
1868
|
+
case "startAt":
|
|
1869
|
+
return /* @__PURE__ */ jsx34(StartAtConditionEditor, { condition, onChange });
|
|
1870
|
+
case "endAt":
|
|
1871
|
+
return /* @__PURE__ */ jsx34(EndAtConditionEditor, { condition, onChange });
|
|
1872
|
+
default: {
|
|
1873
|
+
const OverrideEditor = extensions?.editorOverrides.get(condition.type);
|
|
1874
|
+
if (OverrideEditor) {
|
|
1875
|
+
return /* @__PURE__ */ jsx34(OverrideEditor, { condition, onChange });
|
|
1876
|
+
}
|
|
1877
|
+
return /* @__PURE__ */ jsx34(CustomConditionEditor, { condition, onChange });
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
// src/components/condition-builder/ConditionGroup.tsx
|
|
1883
|
+
import { X } from "lucide-react";
|
|
1884
|
+
|
|
1885
|
+
// src/components/condition-builder/AddConditionMenu.tsx
|
|
1886
|
+
import { Plus } from "lucide-react";
|
|
1887
|
+
|
|
1888
|
+
// src/components/condition-builder/utils.ts
|
|
1889
|
+
function buildDefaultCondition(type, id, extraTypes) {
|
|
1890
|
+
if (type === "and") {
|
|
1891
|
+
return buildAndCondition([], id);
|
|
1892
|
+
}
|
|
1893
|
+
if (type === "or") {
|
|
1894
|
+
return buildOrCondition([], id);
|
|
1895
|
+
}
|
|
1896
|
+
if (type === "__custom") {
|
|
1897
|
+
return buildCustomCondition({ type: "", ...id ? { id } : {} });
|
|
1898
|
+
}
|
|
1899
|
+
const meta8 = BUILTIN_CONDITION_TYPES.find((m) => m.type === type) ?? extraTypes?.find((m) => m.type === type);
|
|
1900
|
+
if (meta8) {
|
|
1901
|
+
return buildCustomCondition({
|
|
1902
|
+
...meta8.defaults,
|
|
1903
|
+
type: meta8.type,
|
|
1904
|
+
...id ? { id } : {}
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
return buildCustomCondition({ type: "", ...id ? { id } : {} });
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// src/components/condition-builder/AddConditionMenu.tsx
|
|
1911
|
+
import { Fragment as Fragment2, jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1912
|
+
function AddConditionMenu({ onAdd }) {
|
|
1913
|
+
const extensions = useConditionExtensions();
|
|
1914
|
+
const extraTypes = extensions?.extraConditionTypes ?? [];
|
|
1915
|
+
const primitives = BUILTIN_CONDITION_TYPES.filter(
|
|
1916
|
+
(m) => ["string", "number", "datetime", "bool"].includes(m.type)
|
|
1917
|
+
);
|
|
1918
|
+
const sugar = BUILTIN_CONDITION_TYPES.filter((m) => ["env", "startAt", "endAt"].includes(m.type));
|
|
1919
|
+
return /* @__PURE__ */ jsxs22(DropdownMenu, { children: [
|
|
1920
|
+
/* @__PURE__ */ jsxs22(DropdownMenuTrigger, { className: "inline-flex shrink-0 items-center justify-center gap-1.5 rounded px-3 h-8 text-sm font-medium hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", children: [
|
|
1921
|
+
/* @__PURE__ */ jsx35(Plus, { className: "mr-1 h-3.5 w-3.5" }),
|
|
1922
|
+
"Add condition"
|
|
1923
|
+
] }),
|
|
1924
|
+
/* @__PURE__ */ jsxs22(DropdownMenuContent, { align: "start", children: [
|
|
1925
|
+
primitives.map((meta8) => /* @__PURE__ */ jsx35(DropdownMenuItem, { onSelect: () => onAdd(meta8.type), children: meta8.label }, meta8.type)),
|
|
1926
|
+
/* @__PURE__ */ jsx35(DropdownMenuSeparator, {}),
|
|
1927
|
+
sugar.map((meta8) => /* @__PURE__ */ jsx35(DropdownMenuItem, { onSelect: () => onAdd(meta8.type), children: meta8.label }, meta8.type)),
|
|
1928
|
+
extraTypes.length > 0 && /* @__PURE__ */ jsxs22(Fragment2, { children: [
|
|
1929
|
+
/* @__PURE__ */ jsx35(DropdownMenuSeparator, {}),
|
|
1930
|
+
extraTypes.map((meta8) => /* @__PURE__ */ jsx35(DropdownMenuItem, { onSelect: () => onAdd(meta8.type), children: meta8.label }, meta8.type))
|
|
1931
|
+
] }),
|
|
1932
|
+
/* @__PURE__ */ jsx35(DropdownMenuSeparator, {}),
|
|
1933
|
+
/* @__PURE__ */ jsx35(DropdownMenuItem, { onSelect: () => onAdd("and"), children: "AND Group" }),
|
|
1934
|
+
/* @__PURE__ */ jsx35(DropdownMenuItem, { onSelect: () => onAdd("or"), children: "OR Group" }),
|
|
1935
|
+
/* @__PURE__ */ jsx35(DropdownMenuSeparator, {}),
|
|
1936
|
+
/* @__PURE__ */ jsx35(DropdownMenuItem, { onSelect: () => onAdd("__custom"), children: "Custom" })
|
|
1937
|
+
] })
|
|
1938
|
+
] });
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
// src/components/condition-builder/MoveButtons.tsx
|
|
1942
|
+
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
1943
|
+
import { Fragment as Fragment3, jsx as jsx36, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1944
|
+
function MoveButtons({ onMoveUp, onMoveDown, size = "h-8 w-8" }) {
|
|
1945
|
+
if (!onMoveUp && !onMoveDown) return null;
|
|
1946
|
+
return /* @__PURE__ */ jsxs23(Fragment3, { children: [
|
|
1947
|
+
/* @__PURE__ */ jsx36(
|
|
1948
|
+
Button,
|
|
1949
|
+
{
|
|
1950
|
+
variant: "ghost",
|
|
1951
|
+
size: "icon",
|
|
1952
|
+
className: `${size} text-muted-foreground hover:text-foreground`,
|
|
1953
|
+
disabled: !onMoveUp,
|
|
1954
|
+
onClick: onMoveUp,
|
|
1955
|
+
"aria-label": "Move up",
|
|
1956
|
+
children: /* @__PURE__ */ jsx36(ChevronUp, { className: "h-3.5 w-3.5" })
|
|
1957
|
+
}
|
|
1958
|
+
),
|
|
1959
|
+
/* @__PURE__ */ jsx36(
|
|
1960
|
+
Button,
|
|
1961
|
+
{
|
|
1962
|
+
variant: "ghost",
|
|
1963
|
+
size: "icon",
|
|
1964
|
+
className: `${size} text-muted-foreground hover:text-foreground`,
|
|
1965
|
+
disabled: !onMoveDown,
|
|
1966
|
+
onClick: onMoveDown,
|
|
1967
|
+
"aria-label": "Move down",
|
|
1968
|
+
children: /* @__PURE__ */ jsx36(ChevronDown, { className: "h-3.5 w-3.5" })
|
|
1969
|
+
}
|
|
1970
|
+
)
|
|
1971
|
+
] });
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
// src/utils/validation-errors.ts
|
|
1975
|
+
function filterErrorsByPath(errors, pathKey, index) {
|
|
1976
|
+
if (!errors) return void 0;
|
|
1977
|
+
const filtered = errors.filter((err) => err.path[0] === pathKey && err.path[1] === index);
|
|
1978
|
+
if (filtered.length === 0) return void 0;
|
|
1979
|
+
return filtered.map((err) => ({ ...err, path: err.path.slice(2) }));
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
// src/components/condition-builder/useConditionArray.ts
|
|
1983
|
+
import { useCallback as useCallback9, useMemo as useMemo8 } from "react";
|
|
1984
|
+
function useConditionArray(conditions, onChange) {
|
|
1985
|
+
const extensions = useConditionExtensions();
|
|
1986
|
+
const extraTypes = extensions?.extraConditionTypes;
|
|
1987
|
+
const withIds = useMemo8(() => ensureIds(conditions), [conditions]);
|
|
1988
|
+
const handleConditionChange = useCallback9(
|
|
1989
|
+
(index, updated) => {
|
|
1990
|
+
const next2 = [...withIds];
|
|
1991
|
+
next2[index] = updated;
|
|
1992
|
+
onChange(next2);
|
|
1993
|
+
},
|
|
1994
|
+
[withIds, onChange]
|
|
1995
|
+
);
|
|
1996
|
+
const handleConditionRemove = useCallback9(
|
|
1997
|
+
(index) => {
|
|
1998
|
+
onChange(withIds.filter((_, i) => i !== index));
|
|
1999
|
+
},
|
|
2000
|
+
[withIds, onChange]
|
|
2001
|
+
);
|
|
2002
|
+
const handleAddCondition = useCallback9(
|
|
2003
|
+
(type) => {
|
|
2004
|
+
const newCondition = buildDefaultCondition(
|
|
2005
|
+
type,
|
|
2006
|
+
`${AUTO_ID_PREFIX}${crypto.randomUUID()}`,
|
|
2007
|
+
extraTypes
|
|
2008
|
+
);
|
|
2009
|
+
onChange([...withIds, newCondition]);
|
|
2010
|
+
},
|
|
2011
|
+
[withIds, onChange, extraTypes]
|
|
2012
|
+
);
|
|
2013
|
+
const handleMoveUp = useCallback9(
|
|
2014
|
+
(index) => {
|
|
2015
|
+
if (index <= 0) return;
|
|
2016
|
+
const next2 = [...withIds];
|
|
2017
|
+
[next2[index - 1], next2[index]] = [next2[index], next2[index - 1]];
|
|
2018
|
+
onChange(next2);
|
|
2019
|
+
},
|
|
2020
|
+
[withIds, onChange]
|
|
2021
|
+
);
|
|
2022
|
+
const handleMoveDown = useCallback9(
|
|
2023
|
+
(index) => {
|
|
2024
|
+
if (index >= withIds.length - 1) return;
|
|
2025
|
+
const next2 = [...withIds];
|
|
2026
|
+
[next2[index], next2[index + 1]] = [next2[index + 1], next2[index]];
|
|
2027
|
+
onChange(next2);
|
|
2028
|
+
},
|
|
2029
|
+
[withIds, onChange]
|
|
2030
|
+
);
|
|
2031
|
+
return {
|
|
2032
|
+
conditions: withIds,
|
|
2033
|
+
handleConditionChange,
|
|
2034
|
+
handleConditionRemove,
|
|
2035
|
+
handleMoveUp,
|
|
2036
|
+
handleMoveDown,
|
|
2037
|
+
handleAddCondition
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// src/components/condition-builder/ConditionGroup.tsx
|
|
2042
|
+
import { Fragment as Fragment4, memo } from "react";
|
|
2043
|
+
import { jsx as jsx37, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2044
|
+
var ConditionGroup = memo(function ConditionGroup2({
|
|
2045
|
+
type,
|
|
2046
|
+
conditions: rawConditions,
|
|
2047
|
+
onChange,
|
|
2048
|
+
onRemove,
|
|
2049
|
+
onMoveUp,
|
|
2050
|
+
onMoveDown,
|
|
2051
|
+
depth = 0,
|
|
2052
|
+
errors
|
|
2053
|
+
}) {
|
|
2054
|
+
const {
|
|
2055
|
+
conditions,
|
|
2056
|
+
handleConditionChange,
|
|
2057
|
+
handleConditionRemove,
|
|
2058
|
+
handleMoveUp,
|
|
2059
|
+
handleMoveDown,
|
|
2060
|
+
handleAddCondition
|
|
2061
|
+
} = useConditionArray(rawConditions, onChange);
|
|
2062
|
+
return /* @__PURE__ */ jsxs24(
|
|
2063
|
+
"div",
|
|
2064
|
+
{
|
|
2065
|
+
className: cn(
|
|
2066
|
+
"border-l-3 pl-3 pr-2 py-2 rounded-r-md",
|
|
2067
|
+
type === "and" ? "border-primary/30" : "border-amber-500/30"
|
|
2068
|
+
),
|
|
2069
|
+
style: {
|
|
2070
|
+
backgroundColor: `oklch(from var(--color-muted) l c h / ${Math.min(0.2 + depth * 0.1, 0.5)})`
|
|
2071
|
+
},
|
|
2072
|
+
children: [
|
|
2073
|
+
/* @__PURE__ */ jsxs24("div", { className: "mb-2 flex items-center gap-2", children: [
|
|
2074
|
+
/* @__PURE__ */ jsxs24(
|
|
2075
|
+
Badge,
|
|
2076
|
+
{
|
|
2077
|
+
variant: "outline",
|
|
2078
|
+
className: "select-none font-mono text-xs bg-muted/50 text-muted-foreground border-border",
|
|
2079
|
+
children: [
|
|
2080
|
+
"L",
|
|
2081
|
+
depth,
|
|
2082
|
+
" | ",
|
|
2083
|
+
type.toUpperCase()
|
|
2084
|
+
]
|
|
2085
|
+
}
|
|
2086
|
+
),
|
|
2087
|
+
/* @__PURE__ */ jsxs24("span", { className: "text-sm text-muted-foreground", children: [
|
|
2088
|
+
conditions.length,
|
|
2089
|
+
" condition",
|
|
2090
|
+
conditions.length !== 1 ? "s" : ""
|
|
2091
|
+
] }),
|
|
2092
|
+
/* @__PURE__ */ jsx37("div", { className: "flex-1" }),
|
|
2093
|
+
/* @__PURE__ */ jsxs24("div", { className: "flex shrink-0 gap-0.5", children: [
|
|
2094
|
+
/* @__PURE__ */ jsx37(MoveButtons, { onMoveUp, onMoveDown, size: "h-6 w-6" }),
|
|
2095
|
+
/* @__PURE__ */ jsx37(
|
|
2096
|
+
Button,
|
|
2097
|
+
{
|
|
2098
|
+
variant: "ghost",
|
|
2099
|
+
size: "icon",
|
|
2100
|
+
className: "h-6 w-6 text-destructive/60 hover:bg-destructive/10 hover:text-destructive",
|
|
2101
|
+
"aria-label": "Remove condition group",
|
|
2102
|
+
onClick: onRemove,
|
|
2103
|
+
children: /* @__PURE__ */ jsx37(X, { className: "h-3.5 w-3.5" })
|
|
2104
|
+
}
|
|
2105
|
+
)
|
|
2106
|
+
] })
|
|
2107
|
+
] }),
|
|
2108
|
+
/* @__PURE__ */ jsx37("div", { className: "space-y-1.5", children: conditions.map((c, i) => /* @__PURE__ */ jsxs24(Fragment4, { children: [
|
|
2109
|
+
i > 0 && /* @__PURE__ */ jsx37("div", { className: "flex", children: /* @__PURE__ */ jsx37(
|
|
2110
|
+
Badge,
|
|
2111
|
+
{
|
|
2112
|
+
variant: "outline",
|
|
2113
|
+
className: cn(
|
|
2114
|
+
"select-none font-mono text-xs",
|
|
2115
|
+
type === "and" ? "bg-primary/10 text-primary border-primary/20" : "bg-amber-500/10 text-amber-600 border-amber-500/20 dark:text-amber-400"
|
|
2116
|
+
),
|
|
2117
|
+
children: type.toUpperCase()
|
|
2118
|
+
}
|
|
2119
|
+
) }),
|
|
2120
|
+
/* @__PURE__ */ jsx37(
|
|
2121
|
+
ConditionBlock,
|
|
2122
|
+
{
|
|
2123
|
+
condition: c,
|
|
2124
|
+
onChange: (updated) => handleConditionChange(i, updated),
|
|
2125
|
+
onRemove: () => handleConditionRemove(i),
|
|
2126
|
+
onMoveUp: i > 0 ? () => handleMoveUp(i) : void 0,
|
|
2127
|
+
onMoveDown: i < conditions.length - 1 ? () => handleMoveDown(i) : void 0,
|
|
2128
|
+
depth: depth + 1,
|
|
2129
|
+
errors: filterErrorsByPath(errors, "conditions", i)
|
|
2130
|
+
}
|
|
2131
|
+
)
|
|
2132
|
+
] }, c.id)) }),
|
|
2133
|
+
/* @__PURE__ */ jsx37("div", { className: "mt-2", children: /* @__PURE__ */ jsx37(AddConditionMenu, { onAdd: handleAddCondition }) })
|
|
2134
|
+
]
|
|
2135
|
+
}
|
|
2136
|
+
);
|
|
2137
|
+
});
|
|
2138
|
+
|
|
2139
|
+
// src/components/condition-builder/ConditionBlock.tsx
|
|
2140
|
+
import { jsx as jsx38, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2141
|
+
var GENERIC_ZOD_ERROR = "Invalid input";
|
|
2142
|
+
var ConditionBlock = memo2(function ConditionBlock2({
|
|
2143
|
+
condition,
|
|
2144
|
+
onChange,
|
|
2145
|
+
onRemove,
|
|
2146
|
+
onMoveUp,
|
|
2147
|
+
onMoveDown,
|
|
2148
|
+
depth = 0,
|
|
2149
|
+
errors
|
|
2150
|
+
}) {
|
|
2151
|
+
function handleChange(updated) {
|
|
2152
|
+
onChange(updated);
|
|
2153
|
+
}
|
|
2154
|
+
if (isAndCondition(condition) || isOrCondition(condition)) {
|
|
2155
|
+
return /* @__PURE__ */ jsx38(
|
|
2156
|
+
ConditionGroup,
|
|
2157
|
+
{
|
|
2158
|
+
type: condition.type,
|
|
2159
|
+
conditions: condition.conditions,
|
|
2160
|
+
onChange: (conditions) => handleChange(
|
|
2161
|
+
condition.type === "and" ? buildAndCondition(conditions, condition.id) : buildOrCondition(conditions, condition.id)
|
|
2162
|
+
),
|
|
2163
|
+
onRemove,
|
|
2164
|
+
onMoveUp,
|
|
2165
|
+
onMoveDown,
|
|
2166
|
+
depth,
|
|
2167
|
+
errors
|
|
2168
|
+
}
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
2171
|
+
const extensions = useConditionExtensions();
|
|
2172
|
+
const meta8 = getConditionMeta(condition.type) ?? extensions?.extraConditionTypes.find((m) => m.type === condition.type);
|
|
2173
|
+
const label = meta8?.label ?? (condition.type || "Custom");
|
|
2174
|
+
return /* @__PURE__ */ jsxs25("div", { className: "border border-border bg-card p-2 space-y-2", children: [
|
|
2175
|
+
/* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2", children: [
|
|
2176
|
+
/* @__PURE__ */ jsx38("span", { className: "flex h-8 shrink-0 items-center text-sm font-medium text-muted-foreground", children: label }),
|
|
2177
|
+
/* @__PURE__ */ jsx38("div", { className: "flex-1" }),
|
|
2178
|
+
/* @__PURE__ */ jsxs25("div", { className: "flex shrink-0 gap-0.5", children: [
|
|
2179
|
+
/* @__PURE__ */ jsx38(MoveButtons, { onMoveUp, onMoveDown }),
|
|
2180
|
+
/* @__PURE__ */ jsx38(
|
|
2181
|
+
Button,
|
|
2182
|
+
{
|
|
2183
|
+
variant: "ghost",
|
|
2184
|
+
size: "icon",
|
|
2185
|
+
className: "h-8 w-8 text-destructive/60 hover:bg-destructive/10 hover:text-destructive",
|
|
2186
|
+
onClick: onRemove,
|
|
2187
|
+
"aria-label": "Remove condition",
|
|
2188
|
+
children: /* @__PURE__ */ jsx38(X2, { className: "h-3.5 w-3.5" })
|
|
2189
|
+
}
|
|
2190
|
+
)
|
|
2191
|
+
] })
|
|
2192
|
+
] }),
|
|
2193
|
+
/* @__PURE__ */ jsx38(ConditionValueEditor, { condition, onChange: handleChange }),
|
|
2194
|
+
(errors?.length ?? 0) > 0 && /* @__PURE__ */ jsx38("div", { className: "space-y-0.5", children: errors.map((err, i) => {
|
|
2195
|
+
const field = err.path.length > 0 ? err.path.join(".") : null;
|
|
2196
|
+
const msg = err.message === GENERIC_ZOD_ERROR ? "Invalid condition \u2014 check required fields" : err.message;
|
|
2197
|
+
return /* @__PURE__ */ jsxs25("p", { className: "text-xs text-destructive", children: [
|
|
2198
|
+
field && /* @__PURE__ */ jsxs25("span", { className: "font-mono text-destructive/70", children: [
|
|
2199
|
+
field,
|
|
2200
|
+
": "
|
|
2201
|
+
] }),
|
|
2202
|
+
msg
|
|
2203
|
+
] }, i);
|
|
2204
|
+
}) })
|
|
2205
|
+
] });
|
|
2206
|
+
});
|
|
2207
|
+
|
|
2208
|
+
// src/components/condition-builder/ConditionBuilder.tsx
|
|
2209
|
+
import { jsx as jsx39, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2210
|
+
function ConditionBuilder({
|
|
2211
|
+
conditions: rawConditions,
|
|
2212
|
+
onChange,
|
|
2213
|
+
validationErrors
|
|
2214
|
+
}) {
|
|
2215
|
+
const {
|
|
2216
|
+
conditions,
|
|
2217
|
+
handleConditionChange,
|
|
2218
|
+
handleConditionRemove,
|
|
2219
|
+
handleMoveUp,
|
|
2220
|
+
handleMoveDown,
|
|
2221
|
+
handleAddCondition
|
|
2222
|
+
} = useConditionArray(rawConditions, onChange);
|
|
2223
|
+
return /* @__PURE__ */ jsx39(ErrorBoundary, { children: /* @__PURE__ */ jsxs26("div", { className: "space-y-1.5", children: [
|
|
2224
|
+
conditions.length > 0 && /* @__PURE__ */ jsxs26("div", { className: "border-l-3 pl-3 py-2 bg-muted/20 rounded-r-md border-primary/30", children: [
|
|
2225
|
+
/* @__PURE__ */ jsx39("div", { className: "mb-2 flex items-center gap-2", children: /* @__PURE__ */ jsxs26("span", { className: "text-sm text-muted-foreground", children: [
|
|
2226
|
+
conditions.length,
|
|
2227
|
+
" condition",
|
|
2228
|
+
conditions.length !== 1 ? "s" : ""
|
|
2229
|
+
] }) }),
|
|
2230
|
+
/* @__PURE__ */ jsx39("div", { className: "space-y-1.5", children: conditions.map((c, i) => /* @__PURE__ */ jsxs26(Fragment5, { children: [
|
|
2231
|
+
i > 0 && /* @__PURE__ */ jsx39("div", { className: "flex", children: /* @__PURE__ */ jsx39(
|
|
2232
|
+
Badge,
|
|
2233
|
+
{
|
|
2234
|
+
variant: "outline",
|
|
2235
|
+
className: "select-none font-mono text-xs bg-primary/10 text-primary border-primary/20",
|
|
2236
|
+
children: "AND"
|
|
2237
|
+
}
|
|
2238
|
+
) }),
|
|
2239
|
+
/* @__PURE__ */ jsx39(
|
|
2240
|
+
ConditionBlock,
|
|
2241
|
+
{
|
|
2242
|
+
condition: c,
|
|
2243
|
+
onChange: (updated) => handleConditionChange(i, updated),
|
|
2244
|
+
onRemove: () => handleConditionRemove(i),
|
|
2245
|
+
onMoveUp: i > 0 ? () => handleMoveUp(i) : void 0,
|
|
2246
|
+
onMoveDown: i < conditions.length - 1 ? () => handleMoveDown(i) : void 0,
|
|
2247
|
+
depth: 1,
|
|
2248
|
+
errors: filterErrorsByPath(validationErrors, "conditions", i)
|
|
2249
|
+
}
|
|
2250
|
+
)
|
|
2251
|
+
] }, c.id)) })
|
|
2252
|
+
] }),
|
|
2253
|
+
/* @__PURE__ */ jsx39(AddConditionMenu, { onAdd: handleAddCondition })
|
|
2254
|
+
] }) });
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
// src/components/variation-editor/VariationCard.tsx
|
|
2258
|
+
import { memo as memo3, useState as useState6 } from "react";
|
|
2259
|
+
import { Collapsible as CollapsiblePrimitive } from "radix-ui";
|
|
2260
|
+
import { ChevronRight, Eye, GripVertical, Trash2 } from "lucide-react";
|
|
2261
|
+
|
|
2262
|
+
// src/utils/condition-summary.ts
|
|
2263
|
+
import { isAndCondition as isAndCondition2, isOrCondition as isOrCondition2 } from "showwhat";
|
|
2264
|
+
function formatLeafOperator(c) {
|
|
2265
|
+
const opField = "op" in c ? String(c.op) : "";
|
|
2266
|
+
const raw = "value" in c ? c.value : "";
|
|
2267
|
+
const opSymbols = {
|
|
2268
|
+
eq: "=",
|
|
2269
|
+
neq: "!=",
|
|
2270
|
+
in: "in",
|
|
2271
|
+
nin: "not in",
|
|
2272
|
+
regex: "~",
|
|
2273
|
+
gt: ">",
|
|
2274
|
+
gte: ">=",
|
|
2275
|
+
lt: "<",
|
|
2276
|
+
lte: "<="
|
|
2277
|
+
};
|
|
2278
|
+
const opSymbol = opSymbols[opField] ?? "=";
|
|
2279
|
+
if (c.type === "string" || c.type === "env") {
|
|
2280
|
+
const vals = Array.isArray(raw) ? raw.map(String) : [String(raw)];
|
|
2281
|
+
if (opField === "regex") {
|
|
2282
|
+
return { op: "~", val: vals.map((v) => `/${v}/`).join(", ") };
|
|
2283
|
+
}
|
|
2284
|
+
return { op: opSymbol, val: vals.map((v) => `"${v}"`).join(", ") };
|
|
2285
|
+
}
|
|
2286
|
+
if (c.type === "number") {
|
|
2287
|
+
if (Array.isArray(raw)) {
|
|
2288
|
+
return { op: opSymbol, val: raw.map(String).join(", ") };
|
|
2289
|
+
}
|
|
2290
|
+
return { op: opSymbol, val: String(raw) };
|
|
2291
|
+
}
|
|
2292
|
+
if (c.type === "bool") {
|
|
2293
|
+
return { op: "=", val: String(raw) };
|
|
2294
|
+
}
|
|
2295
|
+
if (c.type === "datetime") {
|
|
2296
|
+
return { op: opSymbol, val: `"${String(raw)}"` };
|
|
2297
|
+
}
|
|
2298
|
+
if (c.type === "startAt") return { op: ">=", val: `"${String(raw)}"` };
|
|
2299
|
+
if (c.type === "endAt") return { op: "<", val: `"${String(raw)}"` };
|
|
2300
|
+
const value = raw ? String(raw) : "";
|
|
2301
|
+
return { op: "=", val: value ? `"${value}"` : "" };
|
|
2302
|
+
}
|
|
2303
|
+
function formatOne(c, indent) {
|
|
2304
|
+
const prefix = " ".repeat(indent);
|
|
2305
|
+
if (isAndCondition2(c)) {
|
|
2306
|
+
if (c.conditions.length === 0) return [`${prefix}(empty AND group)`];
|
|
2307
|
+
const lines = [];
|
|
2308
|
+
c.conditions.forEach((sub, i) => {
|
|
2309
|
+
const subLines = formatOne(sub, indent + 1);
|
|
2310
|
+
if (i > 0) lines.push(`${prefix} AND`);
|
|
2311
|
+
lines.push(...subLines);
|
|
2312
|
+
});
|
|
2313
|
+
return [`${prefix}(`, ...lines, `${prefix})`];
|
|
2314
|
+
}
|
|
2315
|
+
if (isOrCondition2(c)) {
|
|
2316
|
+
if (c.conditions.length === 0) return [`${prefix}(empty OR group)`];
|
|
2317
|
+
const lines = [];
|
|
2318
|
+
c.conditions.forEach((sub, i) => {
|
|
2319
|
+
const subLines = formatOne(sub, indent + 1);
|
|
2320
|
+
if (i > 0) lines.push(`${prefix} OR`);
|
|
2321
|
+
lines.push(...subLines);
|
|
2322
|
+
});
|
|
2323
|
+
return [`${prefix}(`, ...lines, `${prefix})`];
|
|
2324
|
+
}
|
|
2325
|
+
const meta8 = getConditionMeta(c.type);
|
|
2326
|
+
const label = meta8?.label ?? c.type;
|
|
2327
|
+
const { op, val } = formatLeafOperator(c);
|
|
2328
|
+
const key = "key" in c ? String(c.key) : "";
|
|
2329
|
+
if (key && val) return [`${prefix}${key} ${op} ${val}`];
|
|
2330
|
+
if (val) return [`${prefix}${label} ${op} ${val}`];
|
|
2331
|
+
return [`${prefix}${label}`];
|
|
2332
|
+
}
|
|
2333
|
+
function formatConditionSummary(conditions) {
|
|
2334
|
+
if (conditions.length === 0) return null;
|
|
2335
|
+
const lines = [];
|
|
2336
|
+
conditions.forEach((c, i) => {
|
|
2337
|
+
if (i > 0) lines.push(" AND");
|
|
2338
|
+
lines.push(...formatOne(c, 1));
|
|
2339
|
+
});
|
|
2340
|
+
return `When ${lines.join("\n").trimStart()}`;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
// src/components/variation-editor/VariationCard.tsx
|
|
2344
|
+
import { jsx as jsx40, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2345
|
+
var VariationCard = memo3(function VariationCard2({
|
|
2346
|
+
variation,
|
|
2347
|
+
index,
|
|
2348
|
+
validationErrors,
|
|
2349
|
+
onChange,
|
|
2350
|
+
onRemove,
|
|
2351
|
+
dragHandleProps
|
|
2352
|
+
}) {
|
|
2353
|
+
const [open, setOpen] = useState6(false);
|
|
2354
|
+
const conditionCount = variation.conditions?.length ?? 0;
|
|
2355
|
+
return /* @__PURE__ */ jsx40(CollapsiblePrimitive.Root, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs27("div", { className: "rounded-lg border border-border/50 bg-card transition-colors hover:border-primary/30", children: [
|
|
2356
|
+
/* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-2 px-3 py-2.5", children: [
|
|
2357
|
+
/* @__PURE__ */ jsx40(
|
|
2358
|
+
"button",
|
|
2359
|
+
{
|
|
2360
|
+
type: "button",
|
|
2361
|
+
className: "cursor-grab touch-none text-muted-foreground/50 hover:text-muted-foreground",
|
|
2362
|
+
"aria-label": "Drag to reorder",
|
|
2363
|
+
...dragHandleProps,
|
|
2364
|
+
children: /* @__PURE__ */ jsx40(GripVertical, { className: "h-4 w-4" })
|
|
2365
|
+
}
|
|
2366
|
+
),
|
|
2367
|
+
/* @__PURE__ */ jsx40(CollapsiblePrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs27(
|
|
2368
|
+
"button",
|
|
2369
|
+
{
|
|
2370
|
+
type: "button",
|
|
2371
|
+
className: "flex flex-1 items-center gap-2 text-left hover:cursor-pointer",
|
|
2372
|
+
children: [
|
|
2373
|
+
/* @__PURE__ */ jsx40(Badge, { variant: "secondary", className: "font-mono text-xs", children: index }),
|
|
2374
|
+
/* @__PURE__ */ jsx40("span", { className: "flex-1 truncate text-sm text-muted-foreground", children: variation.description || String(variation.value ?? "") }),
|
|
2375
|
+
conditionCount > 0 && /* @__PURE__ */ jsxs27("span", { className: "text-xs text-muted-foreground/60", children: [
|
|
2376
|
+
conditionCount,
|
|
2377
|
+
" ",
|
|
2378
|
+
conditionCount === 1 ? "condition" : "conditions"
|
|
2379
|
+
] }),
|
|
2380
|
+
/* @__PURE__ */ jsx40(
|
|
2381
|
+
ChevronRight,
|
|
2382
|
+
{
|
|
2383
|
+
className: `h-4 w-4 text-muted-foreground/60 transition-transform ${open ? "rotate-90" : ""}`
|
|
2384
|
+
}
|
|
2385
|
+
)
|
|
2386
|
+
]
|
|
2387
|
+
}
|
|
2388
|
+
) })
|
|
2389
|
+
] }),
|
|
2390
|
+
/* @__PURE__ */ jsx40(CollapsiblePrimitive.Content, { children: /* @__PURE__ */ jsxs27("div", { className: "space-y-4 border-t border-border/40 px-4 py-4", children: [
|
|
2391
|
+
/* @__PURE__ */ jsxs27("div", { className: "flex items-center justify-between", children: [
|
|
2392
|
+
/* @__PURE__ */ jsx40(Label, { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", children: "Value" }),
|
|
2393
|
+
/* @__PURE__ */ jsx40(
|
|
2394
|
+
ConfirmDialog,
|
|
2395
|
+
{
|
|
2396
|
+
title: "Remove variation?",
|
|
2397
|
+
description: `This will delete variation ${index} and all its conditions. This action cannot be undone.`,
|
|
2398
|
+
actionLabel: "Remove",
|
|
2399
|
+
onConfirm: onRemove,
|
|
2400
|
+
children: /* @__PURE__ */ jsx40(
|
|
2401
|
+
Button,
|
|
2402
|
+
{
|
|
2403
|
+
variant: "ghost",
|
|
2404
|
+
size: "icon-xs",
|
|
2405
|
+
className: "shrink-0 text-destructive/60 hover:bg-destructive/10 hover:text-destructive",
|
|
2406
|
+
"aria-label": "Remove variation",
|
|
2407
|
+
children: /* @__PURE__ */ jsx40(Trash2, { className: "h-3.5 w-3.5" })
|
|
2408
|
+
}
|
|
2409
|
+
)
|
|
2410
|
+
}
|
|
2411
|
+
)
|
|
2412
|
+
] }),
|
|
2413
|
+
/* @__PURE__ */ jsx40("div", { children: /* @__PURE__ */ jsx40(
|
|
2414
|
+
ValueInput,
|
|
2415
|
+
{
|
|
2416
|
+
value: variation.value,
|
|
2417
|
+
onChange: (value) => onChange({ ...variation, value })
|
|
2418
|
+
}
|
|
2419
|
+
) }),
|
|
2420
|
+
/* @__PURE__ */ jsx40(
|
|
2421
|
+
"input",
|
|
2422
|
+
{
|
|
2423
|
+
className: "w-full border-none bg-transparent text-sm text-muted-foreground placeholder:text-muted-foreground/50 focus:text-foreground focus:outline-none",
|
|
2424
|
+
value: variation.description ?? "",
|
|
2425
|
+
placeholder: "Add a description (optional)...",
|
|
2426
|
+
onChange: (e) => onChange({
|
|
2427
|
+
...variation,
|
|
2428
|
+
description: e.target.value || void 0
|
|
2429
|
+
})
|
|
2430
|
+
}
|
|
2431
|
+
),
|
|
2432
|
+
/* @__PURE__ */ jsx40("div", { className: "border-t border-border/40 pt-4", children: /* @__PURE__ */ jsxs27("div", { className: "rounded-lg border border-border/40 bg-muted/30 p-3", children: [
|
|
2433
|
+
/* @__PURE__ */ jsxs27("div", { className: "mb-1.5 flex items-center gap-1.5", children: [
|
|
2434
|
+
/* @__PURE__ */ jsx40(Label, { className: "text-sm font-medium", children: "Conditions" }),
|
|
2435
|
+
conditionCount > 0 && /* @__PURE__ */ jsxs27(Dialog, { children: [
|
|
2436
|
+
/* @__PURE__ */ jsx40(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsx40(
|
|
2437
|
+
"button",
|
|
2438
|
+
{
|
|
2439
|
+
type: "button",
|
|
2440
|
+
className: "inline-flex items-center justify-center rounded p-0.5 text-muted-foreground/60 hover:text-muted-foreground",
|
|
2441
|
+
children: /* @__PURE__ */ jsx40(Eye, { className: "h-3.5 w-3.5" })
|
|
2442
|
+
}
|
|
2443
|
+
) }),
|
|
2444
|
+
/* @__PURE__ */ jsxs27(DialogContent, { children: [
|
|
2445
|
+
/* @__PURE__ */ jsxs27(DialogHeader, { children: [
|
|
2446
|
+
/* @__PURE__ */ jsx40(DialogTitle, { children: "Condition Summary" }),
|
|
2447
|
+
/* @__PURE__ */ jsx40(DialogDescription, { children: "Evaluation logic for this variation" })
|
|
2448
|
+
] }),
|
|
2449
|
+
/* @__PURE__ */ jsx40("pre", { className: "rounded-md bg-muted p-4 font-mono text-xs whitespace-pre overflow-auto max-h-80", children: formatConditionSummary(variation.conditions ?? []) })
|
|
2450
|
+
] })
|
|
2451
|
+
] })
|
|
2452
|
+
] }),
|
|
2453
|
+
/* @__PURE__ */ jsx40(
|
|
2454
|
+
ConditionBuilder,
|
|
2455
|
+
{
|
|
2456
|
+
conditions: variation.conditions ?? [],
|
|
2457
|
+
onChange: (conditions) => onChange({
|
|
2458
|
+
...variation,
|
|
2459
|
+
conditions: conditions.length > 0 ? conditions : void 0
|
|
2460
|
+
}),
|
|
2461
|
+
validationErrors
|
|
2462
|
+
}
|
|
2463
|
+
)
|
|
2464
|
+
] }) }),
|
|
2465
|
+
/* @__PURE__ */ jsx40(
|
|
2466
|
+
ValidationMessage,
|
|
2467
|
+
{
|
|
2468
|
+
errors: validationErrors?.filter((err) => err.path[0] !== "conditions")
|
|
2469
|
+
}
|
|
2470
|
+
)
|
|
2471
|
+
] }) })
|
|
2472
|
+
] }) });
|
|
2473
|
+
});
|
|
2474
|
+
|
|
2475
|
+
// src/components/variation-editor/VariationList.tsx
|
|
2476
|
+
import { useCallback as useCallback10, useMemo as useMemo9 } from "react";
|
|
2477
|
+
import {
|
|
2478
|
+
DndContext,
|
|
2479
|
+
closestCenter,
|
|
2480
|
+
KeyboardSensor,
|
|
2481
|
+
PointerSensor,
|
|
2482
|
+
useSensor,
|
|
2483
|
+
useSensors
|
|
2484
|
+
} from "@dnd-kit/core";
|
|
2485
|
+
import {
|
|
2486
|
+
SortableContext,
|
|
2487
|
+
verticalListSortingStrategy,
|
|
2488
|
+
useSortable,
|
|
2489
|
+
arrayMove
|
|
2490
|
+
} from "@dnd-kit/sortable";
|
|
2491
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
2492
|
+
import { jsx as jsx41 } from "react/jsx-runtime";
|
|
2493
|
+
function SortableVariation({
|
|
2494
|
+
id,
|
|
2495
|
+
variation,
|
|
2496
|
+
index,
|
|
2497
|
+
validationErrors,
|
|
2498
|
+
onChange,
|
|
2499
|
+
onRemove
|
|
2500
|
+
}) {
|
|
2501
|
+
const {
|
|
2502
|
+
attributes,
|
|
2503
|
+
listeners,
|
|
2504
|
+
setNodeRef,
|
|
2505
|
+
setActivatorNodeRef,
|
|
2506
|
+
transform,
|
|
2507
|
+
transition,
|
|
2508
|
+
isDragging
|
|
2509
|
+
} = useSortable({ id });
|
|
2510
|
+
const style = {
|
|
2511
|
+
transform: CSS.Transform.toString(transform),
|
|
2512
|
+
transition,
|
|
2513
|
+
opacity: isDragging ? 0.5 : 1
|
|
2514
|
+
};
|
|
2515
|
+
return /* @__PURE__ */ jsx41(
|
|
2516
|
+
"div",
|
|
2517
|
+
{
|
|
2518
|
+
ref: setNodeRef,
|
|
2519
|
+
style: {
|
|
2520
|
+
...style,
|
|
2521
|
+
animationDelay: `${index * 50}ms`
|
|
2522
|
+
},
|
|
2523
|
+
className: "animate-fade-up",
|
|
2524
|
+
children: /* @__PURE__ */ jsx41(
|
|
2525
|
+
VariationCard,
|
|
2526
|
+
{
|
|
2527
|
+
variation,
|
|
2528
|
+
index,
|
|
2529
|
+
validationErrors,
|
|
2530
|
+
onChange,
|
|
2531
|
+
onRemove,
|
|
2532
|
+
dragHandleProps: { ref: setActivatorNodeRef, ...listeners, ...attributes }
|
|
2533
|
+
}
|
|
2534
|
+
)
|
|
2535
|
+
}
|
|
2536
|
+
);
|
|
2537
|
+
}
|
|
2538
|
+
function VariationList({
|
|
2539
|
+
variations: rawVariations,
|
|
2540
|
+
validationErrors,
|
|
2541
|
+
onChange
|
|
2542
|
+
}) {
|
|
2543
|
+
const sensors = useSensors(
|
|
2544
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
|
|
2545
|
+
useSensor(KeyboardSensor)
|
|
2546
|
+
);
|
|
2547
|
+
const variations = useMemo9(() => ensureIds(rawVariations), [rawVariations]);
|
|
2548
|
+
const sortableIds = useMemo9(() => variations.map((v) => v.id), [variations]);
|
|
2549
|
+
const handleDragEnd = useCallback10(
|
|
2550
|
+
(event) => {
|
|
2551
|
+
const { active, over } = event;
|
|
2552
|
+
if (!over || active.id === over.id) return;
|
|
2553
|
+
const oldIndex = sortableIds.indexOf(String(active.id));
|
|
2554
|
+
const newIndex = sortableIds.indexOf(String(over.id));
|
|
2555
|
+
if (oldIndex === -1 || newIndex === -1) return;
|
|
2556
|
+
onChange(arrayMove(variations, oldIndex, newIndex));
|
|
2557
|
+
},
|
|
2558
|
+
[sortableIds, variations, onChange]
|
|
2559
|
+
);
|
|
2560
|
+
const handleVariationChange = useCallback10(
|
|
2561
|
+
(index, updated) => {
|
|
2562
|
+
const next2 = [...variations];
|
|
2563
|
+
next2[index] = updated;
|
|
2564
|
+
onChange(next2);
|
|
2565
|
+
},
|
|
2566
|
+
[variations, onChange]
|
|
2567
|
+
);
|
|
2568
|
+
const handleRemove = useCallback10(
|
|
2569
|
+
(index) => {
|
|
2570
|
+
onChange(variations.filter((_, i) => i !== index));
|
|
2571
|
+
},
|
|
2572
|
+
[variations, onChange]
|
|
2573
|
+
);
|
|
2574
|
+
return /* @__PURE__ */ jsx41("div", { className: "space-y-3", children: /* @__PURE__ */ jsx41(DndContext, { sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: /* @__PURE__ */ jsx41(SortableContext, { items: sortableIds, strategy: verticalListSortingStrategy, children: variations.map((v, i) => /* @__PURE__ */ jsx41(
|
|
2575
|
+
SortableVariation,
|
|
2576
|
+
{
|
|
2577
|
+
id: v.id,
|
|
2578
|
+
variation: v,
|
|
2579
|
+
index: i,
|
|
2580
|
+
validationErrors: filterErrorsByPath(validationErrors, "variations", i),
|
|
2581
|
+
onChange: (updated) => handleVariationChange(i, updated),
|
|
2582
|
+
onRemove: () => handleRemove(i)
|
|
2583
|
+
},
|
|
2584
|
+
v.id
|
|
2585
|
+
)) }) }) });
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
// src/components/definition-editor/DefinitionEditor.tsx
|
|
2589
|
+
import { useRef as useRef4, useState as useState7 } from "react";
|
|
2590
|
+
import { AlertTriangle, Plus as Plus2, Save, Undo2 } from "lucide-react";
|
|
2591
|
+
import { jsx as jsx42, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
2592
|
+
function DefinitionEditor({
|
|
2593
|
+
definitionKey,
|
|
2594
|
+
definition,
|
|
2595
|
+
validationErrors,
|
|
2596
|
+
isDirty,
|
|
2597
|
+
isPending,
|
|
2598
|
+
onUpdate,
|
|
2599
|
+
onRename,
|
|
2600
|
+
onSave,
|
|
2601
|
+
onDiscard
|
|
2602
|
+
}) {
|
|
2603
|
+
const [editingKey, setEditingKey] = useState7(false);
|
|
2604
|
+
const [keyDraft, setKeyDraft] = useState7(definitionKey);
|
|
2605
|
+
const prevKeyRef = useRef4(definitionKey);
|
|
2606
|
+
if (prevKeyRef.current !== definitionKey) {
|
|
2607
|
+
prevKeyRef.current = definitionKey;
|
|
2608
|
+
setKeyDraft(definitionKey);
|
|
2609
|
+
}
|
|
2610
|
+
async function handleKeySubmit() {
|
|
2611
|
+
const trimmed = keyDraft.trim();
|
|
2612
|
+
if (trimmed && trimmed !== definitionKey) {
|
|
2613
|
+
try {
|
|
2614
|
+
await onRename(trimmed);
|
|
2615
|
+
setEditingKey(false);
|
|
2616
|
+
} catch {
|
|
2617
|
+
}
|
|
2618
|
+
} else {
|
|
2619
|
+
setKeyDraft(definitionKey);
|
|
2620
|
+
setEditingKey(false);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
function handleAddVariation() {
|
|
2624
|
+
onUpdate({
|
|
2625
|
+
...definition,
|
|
2626
|
+
variations: [...definition.variations, { id: crypto.randomUUID(), value: "" }]
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
const errorCount = validationErrors?.length ?? 0;
|
|
2630
|
+
return /* @__PURE__ */ jsxs28("div", { className: "flex h-full flex-col overflow-hidden", children: [
|
|
2631
|
+
/* @__PURE__ */ jsxs28("div", { className: "flex shrink-0 items-center justify-end gap-2 border-b border-border bg-muted/30 px-4 py-2", children: [
|
|
2632
|
+
/* @__PURE__ */ jsxs28(
|
|
2633
|
+
Button,
|
|
2634
|
+
{
|
|
2635
|
+
variant: isDirty ? "default" : "ghost",
|
|
2636
|
+
size: "sm",
|
|
2637
|
+
disabled: !isDirty || isPending,
|
|
2638
|
+
onClick: onSave,
|
|
2639
|
+
children: [
|
|
2640
|
+
/* @__PURE__ */ jsx42(Save, { className: "mr-1.5 h-4 w-4" }),
|
|
2641
|
+
"Save"
|
|
2642
|
+
]
|
|
2643
|
+
}
|
|
2644
|
+
),
|
|
2645
|
+
onDiscard ? /* @__PURE__ */ jsx42(
|
|
2646
|
+
ConfirmDialog,
|
|
2647
|
+
{
|
|
2648
|
+
title: "Discard changes?",
|
|
2649
|
+
description: "This will revert all unsaved changes to this definition. This action cannot be undone.",
|
|
2650
|
+
actionLabel: "Discard",
|
|
2651
|
+
onConfirm: onDiscard,
|
|
2652
|
+
children: /* @__PURE__ */ jsxs28(Button, { variant: "ghost", size: "sm", disabled: !isDirty || isPending, children: [
|
|
2653
|
+
/* @__PURE__ */ jsx42(Undo2, { className: "mr-1.5 h-4 w-4" }),
|
|
2654
|
+
"Discard"
|
|
2655
|
+
] })
|
|
2656
|
+
}
|
|
2657
|
+
) : /* @__PURE__ */ jsxs28(Button, { variant: "ghost", size: "sm", disabled: true, children: [
|
|
2658
|
+
/* @__PURE__ */ jsx42(Undo2, { className: "mr-1.5 h-4 w-4" }),
|
|
2659
|
+
"Discard"
|
|
2660
|
+
] })
|
|
2661
|
+
] }),
|
|
2662
|
+
errorCount > 0 && /* @__PURE__ */ jsx42("div", { className: "shrink-0 border-b border-destructive/30 bg-destructive/10 px-4 py-2", children: /* @__PURE__ */ jsxs28("p", { className: "flex items-center gap-1.5 text-xs font-medium text-destructive", children: [
|
|
2663
|
+
/* @__PURE__ */ jsx42(AlertTriangle, { className: "h-3.5 w-3.5" }),
|
|
2664
|
+
errorCount,
|
|
2665
|
+
" validation error",
|
|
2666
|
+
errorCount !== 1 ? "s" : "",
|
|
2667
|
+
" \u2014 fix the highlighted fields below"
|
|
2668
|
+
] }) }),
|
|
2669
|
+
/* @__PURE__ */ jsx42("div", { className: "shrink-0 border-b border-border/50 mx-auto w-full max-w-3xl px-8 pt-8 pb-4 space-y-3", children: /* @__PURE__ */ jsxs28("div", { className: "space-y-3", children: [
|
|
2670
|
+
/* @__PURE__ */ jsxs28("div", { className: "flex items-start justify-between gap-4", children: [
|
|
2671
|
+
/* @__PURE__ */ jsx42("div", { className: "flex-1", children: editingKey ? /* @__PURE__ */ jsx42(
|
|
2672
|
+
Input,
|
|
2673
|
+
{
|
|
2674
|
+
className: "h-auto border-none bg-transparent px-0 font-mono text-2xl font-semibold shadow-none focus-visible:ring-0",
|
|
2675
|
+
value: keyDraft,
|
|
2676
|
+
autoFocus: true,
|
|
2677
|
+
onChange: (e) => setKeyDraft(e.target.value),
|
|
2678
|
+
onBlur: handleKeySubmit,
|
|
2679
|
+
onKeyDown: (e) => {
|
|
2680
|
+
if (e.key === "Enter") handleKeySubmit();
|
|
2681
|
+
if (e.key === "Escape") {
|
|
2682
|
+
setKeyDraft(definitionKey);
|
|
2683
|
+
setEditingKey(false);
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
) : /* @__PURE__ */ jsx42(
|
|
2688
|
+
"button",
|
|
2689
|
+
{
|
|
2690
|
+
type: "button",
|
|
2691
|
+
className: "block w-full rounded px-0 py-0 text-left font-mono text-2xl font-semibold hover:text-foreground/70",
|
|
2692
|
+
"aria-label": "Rename definition key",
|
|
2693
|
+
onClick: () => {
|
|
2694
|
+
setKeyDraft(definitionKey);
|
|
2695
|
+
setEditingKey(true);
|
|
2696
|
+
},
|
|
2697
|
+
children: definitionKey
|
|
2698
|
+
}
|
|
2699
|
+
) }),
|
|
2700
|
+
/* @__PURE__ */ jsxs28("div", { className: "flex shrink-0 items-center gap-2 pt-1.5", children: [
|
|
2701
|
+
/* @__PURE__ */ jsx42(Label, { htmlFor: "definition-active", className: "text-xs text-muted-foreground", children: "Active" }),
|
|
2702
|
+
/* @__PURE__ */ jsx42(
|
|
2703
|
+
Switch,
|
|
2704
|
+
{
|
|
2705
|
+
id: "definition-active",
|
|
2706
|
+
checked: definition.active !== false,
|
|
2707
|
+
onCheckedChange: (checked) => onUpdate({
|
|
2708
|
+
...definition,
|
|
2709
|
+
active: checked ? void 0 : false
|
|
2710
|
+
})
|
|
2711
|
+
}
|
|
2712
|
+
)
|
|
2713
|
+
] })
|
|
2714
|
+
] }),
|
|
2715
|
+
/* @__PURE__ */ jsx42(
|
|
2716
|
+
"input",
|
|
2717
|
+
{
|
|
2718
|
+
className: "w-full border-none bg-transparent text-sm text-muted-foreground placeholder:text-muted-foreground/50 focus:text-foreground focus:outline-none",
|
|
2719
|
+
value: definition.description ?? "",
|
|
2720
|
+
placeholder: "Add a description (optional)...",
|
|
2721
|
+
"aria-label": "Definition description",
|
|
2722
|
+
onChange: (e) => onUpdate({
|
|
2723
|
+
...definition,
|
|
2724
|
+
description: e.target.value || void 0
|
|
2725
|
+
})
|
|
2726
|
+
}
|
|
2727
|
+
)
|
|
2728
|
+
] }) }),
|
|
2729
|
+
/* @__PURE__ */ jsx42("div", { className: "min-h-0 flex-1 overflow-auto", children: /* @__PURE__ */ jsx42("div", { className: "mx-auto w-full max-w-3xl px-8 py-8 space-y-4", children: /* @__PURE__ */ jsxs28("div", { className: "space-y-4", children: [
|
|
2730
|
+
/* @__PURE__ */ jsxs28("div", { className: "flex items-center justify-between", children: [
|
|
2731
|
+
/* @__PURE__ */ jsx42("span", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", children: "Variations" }),
|
|
2732
|
+
/* @__PURE__ */ jsxs28(
|
|
2733
|
+
Button,
|
|
2734
|
+
{
|
|
2735
|
+
variant: "ghost",
|
|
2736
|
+
size: "sm",
|
|
2737
|
+
className: "h-7 text-xs text-muted-foreground",
|
|
2738
|
+
onClick: handleAddVariation,
|
|
2739
|
+
children: [
|
|
2740
|
+
/* @__PURE__ */ jsx42(Plus2, { className: "mr-1 h-3 w-3" }),
|
|
2741
|
+
"Add"
|
|
2742
|
+
]
|
|
2743
|
+
}
|
|
2744
|
+
)
|
|
2745
|
+
] }),
|
|
2746
|
+
/* @__PURE__ */ jsx42(
|
|
2747
|
+
VariationList,
|
|
2748
|
+
{
|
|
2749
|
+
variations: definition.variations,
|
|
2750
|
+
validationErrors,
|
|
2751
|
+
onChange: (variations) => onUpdate({ ...definition, variations })
|
|
2752
|
+
}
|
|
2753
|
+
),
|
|
2754
|
+
definition.variations.length > 2 && /* @__PURE__ */ jsxs28(
|
|
2755
|
+
"button",
|
|
2756
|
+
{
|
|
2757
|
+
type: "button",
|
|
2758
|
+
className: "flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-border/60 py-2.5 text-xs text-muted-foreground hover:border-border hover:text-foreground",
|
|
2759
|
+
onClick: handleAddVariation,
|
|
2760
|
+
children: [
|
|
2761
|
+
/* @__PURE__ */ jsx42(Plus2, { className: "h-3 w-3" }),
|
|
2762
|
+
"Add variation"
|
|
2763
|
+
]
|
|
2764
|
+
}
|
|
2765
|
+
)
|
|
2766
|
+
] }) }) })
|
|
2767
|
+
] });
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
// src/components/definition-list/DefinitionList.tsx
|
|
2771
|
+
import { useState as useState9 } from "react";
|
|
2772
|
+
import { Plus as Plus3, Search, X as X3 } from "lucide-react";
|
|
2773
|
+
|
|
2774
|
+
// src/components/definition-list/DefinitionListItem.tsx
|
|
2775
|
+
import { memo as memo4, useState as useState8 } from "react";
|
|
2776
|
+
import { Trash2 as Trash22 } from "lucide-react";
|
|
2777
|
+
import { Fragment as Fragment6, jsx as jsx43, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
2778
|
+
var DefinitionListItem = memo4(function DefinitionListItem2({
|
|
2779
|
+
definitionKey,
|
|
2780
|
+
variationCount,
|
|
2781
|
+
isActive,
|
|
2782
|
+
hasErrors,
|
|
2783
|
+
isSelected,
|
|
2784
|
+
isDirty,
|
|
2785
|
+
onSelect,
|
|
2786
|
+
onRemove
|
|
2787
|
+
}) {
|
|
2788
|
+
const [confirmOpen, setConfirmOpen] = useState8(false);
|
|
2789
|
+
return /* @__PURE__ */ jsxs29(
|
|
2790
|
+
"div",
|
|
2791
|
+
{
|
|
2792
|
+
tabIndex: 0,
|
|
2793
|
+
className: cn(
|
|
2794
|
+
"group flex w-full items-center gap-2 rounded border-l-2 px-3 py-2.5 text-left text-sm transition-colors cursor-pointer",
|
|
2795
|
+
isSelected ? "border-l-primary bg-accent text-accent-foreground" : "border-l-transparent hover:bg-muted"
|
|
2796
|
+
),
|
|
2797
|
+
onClick: onSelect,
|
|
2798
|
+
onKeyDown: (e) => {
|
|
2799
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
2800
|
+
e.preventDefault();
|
|
2801
|
+
onSelect();
|
|
2802
|
+
}
|
|
2803
|
+
},
|
|
2804
|
+
children: [
|
|
2805
|
+
/* @__PURE__ */ jsx43(
|
|
2806
|
+
"span",
|
|
2807
|
+
{
|
|
2808
|
+
role: "status",
|
|
2809
|
+
"aria-label": `${definitionKey} is ${hasErrors ? "error" : isActive ? "active" : "inactive"}${isDirty ? ", unsaved changes" : ""}`,
|
|
2810
|
+
className: cn(
|
|
2811
|
+
"h-2 w-2 shrink-0 rounded-full border-[1.5px]",
|
|
2812
|
+
hasErrors ? isDirty ? "border-status-error bg-transparent" : "border-status-error bg-status-error" : isActive ? isDirty ? "border-status-active bg-transparent" : "border-status-active bg-status-active" : isDirty ? "border-status-inactive bg-transparent" : "border-status-inactive bg-status-inactive"
|
|
2813
|
+
)
|
|
2814
|
+
}
|
|
2815
|
+
),
|
|
2816
|
+
/* @__PURE__ */ jsx43("span", { className: "flex-1 truncate font-mono text-sm", children: definitionKey }),
|
|
2817
|
+
/* @__PURE__ */ jsx43(Badge, { variant: "secondary", className: "text-xs tabular-nums", children: variationCount }),
|
|
2818
|
+
/* @__PURE__ */ jsx43(
|
|
2819
|
+
ConfirmDialog,
|
|
2820
|
+
{
|
|
2821
|
+
open: confirmOpen,
|
|
2822
|
+
onOpenChange: setConfirmOpen,
|
|
2823
|
+
title: "Delete definition?",
|
|
2824
|
+
description: /* @__PURE__ */ jsxs29(Fragment6, { children: [
|
|
2825
|
+
"This will permanently delete ",
|
|
2826
|
+
/* @__PURE__ */ jsx43("strong", { className: "font-mono", children: definitionKey }),
|
|
2827
|
+
" and all its variations. This action cannot be undone."
|
|
2828
|
+
] }),
|
|
2829
|
+
actionLabel: "Delete",
|
|
2830
|
+
onConfirm: onRemove,
|
|
2831
|
+
children: /* @__PURE__ */ jsx43(
|
|
2832
|
+
Button,
|
|
2833
|
+
{
|
|
2834
|
+
variant: "ghost",
|
|
2835
|
+
size: "icon-xs",
|
|
2836
|
+
className: "text-destructive/60 opacity-0 hover:bg-destructive/10 hover:text-destructive group-hover:opacity-100",
|
|
2837
|
+
"aria-label": `Remove ${definitionKey}`,
|
|
2838
|
+
onClick: (e) => {
|
|
2839
|
+
e.stopPropagation();
|
|
2840
|
+
setConfirmOpen(true);
|
|
2841
|
+
},
|
|
2842
|
+
children: /* @__PURE__ */ jsx43(Trash22, { className: "h-3.5 w-3.5" })
|
|
2843
|
+
}
|
|
2844
|
+
)
|
|
2845
|
+
}
|
|
2846
|
+
)
|
|
2847
|
+
]
|
|
2848
|
+
}
|
|
2849
|
+
);
|
|
2850
|
+
});
|
|
2851
|
+
|
|
2852
|
+
// src/components/definition-list/DefinitionList.tsx
|
|
2853
|
+
import { jsx as jsx44, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
2854
|
+
function DefinitionList({
|
|
2855
|
+
definitions,
|
|
2856
|
+
selectedKey,
|
|
2857
|
+
validationErrors,
|
|
2858
|
+
dirtyKeys,
|
|
2859
|
+
onSelect,
|
|
2860
|
+
onAdd,
|
|
2861
|
+
onRemove
|
|
2862
|
+
}) {
|
|
2863
|
+
const [search, setSearch] = useState9("");
|
|
2864
|
+
const [adding, setAdding] = useState9(false);
|
|
2865
|
+
const [newKey, setNewKey] = useState9("");
|
|
2866
|
+
const keys = Object.keys(definitions).filter(
|
|
2867
|
+
(k) => k.toLowerCase().includes(search.toLowerCase())
|
|
2868
|
+
);
|
|
2869
|
+
async function handleAdd() {
|
|
2870
|
+
const trimmed = newKey.trim();
|
|
2871
|
+
if (trimmed && !(trimmed in definitions)) {
|
|
2872
|
+
try {
|
|
2873
|
+
await onAdd(trimmed);
|
|
2874
|
+
setNewKey("");
|
|
2875
|
+
setAdding(false);
|
|
2876
|
+
} catch {
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
return /* @__PURE__ */ jsxs30("div", { className: "flex h-full flex-col", children: [
|
|
2881
|
+
/* @__PURE__ */ jsx44("div", { className: "p-3", children: /* @__PURE__ */ jsxs30("div", { className: "relative", children: [
|
|
2882
|
+
/* @__PURE__ */ jsx44(Search, { className: "absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
|
|
2883
|
+
/* @__PURE__ */ jsx44(
|
|
2884
|
+
Input,
|
|
2885
|
+
{
|
|
2886
|
+
className: "h-9 pl-8 text-sm",
|
|
2887
|
+
placeholder: "Search definitions...",
|
|
2888
|
+
value: search,
|
|
2889
|
+
onChange: (e) => setSearch(e.target.value)
|
|
2890
|
+
}
|
|
2891
|
+
)
|
|
2892
|
+
] }) }),
|
|
2893
|
+
/* @__PURE__ */ jsx44(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsxs30("div", { className: "px-2", children: [
|
|
2894
|
+
keys.map((key) => /* @__PURE__ */ jsx44(
|
|
2895
|
+
DefinitionListItem,
|
|
2896
|
+
{
|
|
2897
|
+
definitionKey: key,
|
|
2898
|
+
variationCount: definitions[key].variations.length,
|
|
2899
|
+
isActive: definitions[key].active !== false,
|
|
2900
|
+
hasErrors: !!(validationErrors?.[key] && validationErrors[key].length > 0),
|
|
2901
|
+
isDirty: dirtyKeys?.includes(key),
|
|
2902
|
+
isSelected: key === selectedKey,
|
|
2903
|
+
onSelect: () => onSelect(key),
|
|
2904
|
+
onRemove: () => onRemove(key)
|
|
2905
|
+
},
|
|
2906
|
+
key
|
|
2907
|
+
)),
|
|
2908
|
+
keys.length === 0 && /* @__PURE__ */ jsx44("p", { className: "px-3 py-8 text-center text-sm text-muted-foreground", children: search ? "No definitions match" : "No definitions" })
|
|
2909
|
+
] }) }),
|
|
2910
|
+
/* @__PURE__ */ jsx44("div", { className: "border-t border-border p-3", children: adding ? /* @__PURE__ */ jsxs30("div", { className: "space-y-2", children: [
|
|
2911
|
+
/* @__PURE__ */ jsx44(
|
|
2912
|
+
Input,
|
|
2913
|
+
{
|
|
2914
|
+
className: "h-9 w-full font-mono text-sm",
|
|
2915
|
+
placeholder: "definition-key",
|
|
2916
|
+
value: newKey,
|
|
2917
|
+
autoFocus: true,
|
|
2918
|
+
onChange: (e) => setNewKey(e.target.value),
|
|
2919
|
+
onKeyDown: (e) => {
|
|
2920
|
+
if (e.key === "Enter") handleAdd();
|
|
2921
|
+
if (e.key === "Escape") setAdding(false);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
),
|
|
2925
|
+
/* @__PURE__ */ jsxs30("div", { className: "flex gap-2", children: [
|
|
2926
|
+
/* @__PURE__ */ jsx44(Button, { size: "sm", className: "h-8 flex-1", onClick: handleAdd, children: "Save" }),
|
|
2927
|
+
/* @__PURE__ */ jsx44(
|
|
2928
|
+
Button,
|
|
2929
|
+
{
|
|
2930
|
+
variant: "ghost",
|
|
2931
|
+
size: "sm",
|
|
2932
|
+
className: "h-8",
|
|
2933
|
+
"aria-label": "Cancel adding definition",
|
|
2934
|
+
onClick: () => {
|
|
2935
|
+
setAdding(false);
|
|
2936
|
+
setNewKey("");
|
|
2937
|
+
},
|
|
2938
|
+
children: /* @__PURE__ */ jsx44(X3, { className: "h-4 w-4" })
|
|
2939
|
+
}
|
|
2940
|
+
)
|
|
2941
|
+
] })
|
|
2942
|
+
] }) : /* @__PURE__ */ jsxs30(
|
|
2943
|
+
Button,
|
|
2944
|
+
{
|
|
2945
|
+
variant: "outline",
|
|
2946
|
+
size: "sm",
|
|
2947
|
+
className: "h-9 w-full",
|
|
2948
|
+
"aria-label": "Add new definition",
|
|
2949
|
+
onClick: () => setAdding(true),
|
|
2950
|
+
children: [
|
|
2951
|
+
/* @__PURE__ */ jsx44(Plus3, { className: "mr-1.5 h-4 w-4" }),
|
|
2952
|
+
"New definition"
|
|
2953
|
+
]
|
|
2954
|
+
}
|
|
2955
|
+
) })
|
|
2956
|
+
] });
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
// src/components/condition-builder/preset-ui.tsx
|
|
2960
|
+
import { useCallback as useCallback11, useMemo as useMemo10 } from "react";
|
|
2961
|
+
import { PRIMITIVE_TYPES } from "showwhat";
|
|
2962
|
+
import { jsx as jsx45, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
2963
|
+
var TYPE_DEFAULTS = {
|
|
2964
|
+
string: { op: "eq", value: "" },
|
|
2965
|
+
number: { op: "eq", value: 0 },
|
|
2966
|
+
bool: { value: true },
|
|
2967
|
+
datetime: { op: "eq", value: (/* @__PURE__ */ new Date()).toISOString() }
|
|
2968
|
+
};
|
|
2969
|
+
function capitalize(s) {
|
|
2970
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
2971
|
+
}
|
|
2972
|
+
function createPresetConditionMeta(presets) {
|
|
2973
|
+
return Object.entries(presets).map(([name, preset]) => {
|
|
2974
|
+
const isBuiltin = PRIMITIVE_TYPES.has(preset.type);
|
|
2975
|
+
const description = isBuiltin ? `Match the ${preset.key} key (${preset.type})` : `Custom ${preset.type} condition`;
|
|
2976
|
+
const baseDefaults = isBuiltin ? { ...TYPE_DEFAULTS[preset.type], key: preset.key } : {};
|
|
2977
|
+
return {
|
|
2978
|
+
type: name,
|
|
2979
|
+
label: capitalize(name),
|
|
2980
|
+
description,
|
|
2981
|
+
defaults: { ...baseDefaults, ...preset.defaults, type: name }
|
|
2982
|
+
};
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
function createPresetEditor(presetName, builtinType, presetKey) {
|
|
2986
|
+
function PresetConditionEditor({ condition, onChange }) {
|
|
2987
|
+
const rec = useMemo10(() => condition, [condition]);
|
|
2988
|
+
const update = useCallback11(
|
|
2989
|
+
(field, value) => {
|
|
2990
|
+
onChange(
|
|
2991
|
+
buildCustomCondition({ ...rec, [field]: value, key: presetKey, type: presetName })
|
|
2992
|
+
);
|
|
2993
|
+
},
|
|
2994
|
+
[rec, onChange]
|
|
2995
|
+
);
|
|
2996
|
+
switch (builtinType) {
|
|
2997
|
+
case "string": {
|
|
2998
|
+
const op = rec.op;
|
|
2999
|
+
const isArray = op === "in" || op === "nin";
|
|
3000
|
+
const isRegex = op === "regex";
|
|
3001
|
+
const handleOpChange = (newOp) => {
|
|
3002
|
+
const isArrayOp = newOp === "in" || newOp === "nin";
|
|
3003
|
+
const currentValue = rec.value;
|
|
3004
|
+
const coercedValue = isArrayOp ? Array.isArray(currentValue) ? currentValue : currentValue ? [String(currentValue)] : [] : Array.isArray(currentValue) ? currentValue[0] ?? "" : currentValue;
|
|
3005
|
+
onChange(
|
|
3006
|
+
buildCustomCondition({
|
|
3007
|
+
...rec,
|
|
3008
|
+
op: newOp,
|
|
3009
|
+
value: coercedValue,
|
|
3010
|
+
key: presetKey,
|
|
3011
|
+
type: presetName
|
|
3012
|
+
})
|
|
3013
|
+
);
|
|
3014
|
+
};
|
|
3015
|
+
return /* @__PURE__ */ jsxs31(ConditionRow, { children: [
|
|
3016
|
+
/* @__PURE__ */ jsx45(KeyInput, { value: presetKey, disabled: true }),
|
|
3017
|
+
/* @__PURE__ */ jsx45(
|
|
3018
|
+
OperatorSelect,
|
|
3019
|
+
{
|
|
3020
|
+
value: String(rec.op ?? "eq"),
|
|
3021
|
+
onChange: handleOpChange,
|
|
3022
|
+
options: OP_OPTIONS
|
|
3023
|
+
}
|
|
3024
|
+
),
|
|
3025
|
+
isArray ? /* @__PURE__ */ jsx45(
|
|
3026
|
+
TagInput,
|
|
3027
|
+
{
|
|
3028
|
+
value: rec.value ?? "",
|
|
3029
|
+
onChange: (v) => update("value", v),
|
|
3030
|
+
placeholder: `e.g. ${presetKey} value`
|
|
3031
|
+
}
|
|
3032
|
+
) : isRegex ? /* @__PURE__ */ jsx45(
|
|
3033
|
+
Input,
|
|
3034
|
+
{
|
|
3035
|
+
className: "h-8 font-mono text-sm",
|
|
3036
|
+
value: String(rec.value ?? ""),
|
|
3037
|
+
placeholder: "e.g. ^test.*$",
|
|
3038
|
+
onChange: (e) => update("value", e.target.value)
|
|
3039
|
+
}
|
|
3040
|
+
) : /* @__PURE__ */ jsx45(
|
|
3041
|
+
Input,
|
|
3042
|
+
{
|
|
3043
|
+
className: "h-8 text-sm",
|
|
3044
|
+
value: String(rec.value ?? ""),
|
|
3045
|
+
placeholder: `e.g. ${presetKey} value`,
|
|
3046
|
+
onChange: (e) => update("value", e.target.value)
|
|
3047
|
+
}
|
|
3048
|
+
)
|
|
3049
|
+
] });
|
|
3050
|
+
}
|
|
3051
|
+
case "number": {
|
|
3052
|
+
const numOp = rec.op;
|
|
3053
|
+
const isNumArray = numOp === "in" || numOp === "nin";
|
|
3054
|
+
const handleNumOpChange = (newOp) => {
|
|
3055
|
+
const isArrayOp = newOp === "in" || newOp === "nin";
|
|
3056
|
+
const currentValue = rec.value;
|
|
3057
|
+
const coercedValue = isArrayOp ? Array.isArray(currentValue) ? currentValue : currentValue !== void 0 && currentValue !== "" ? [Number(currentValue)] : [] : Array.isArray(currentValue) ? currentValue[0] ?? 0 : currentValue;
|
|
3058
|
+
onChange(
|
|
3059
|
+
buildCustomCondition({
|
|
3060
|
+
...rec,
|
|
3061
|
+
op: newOp,
|
|
3062
|
+
value: coercedValue,
|
|
3063
|
+
key: presetKey,
|
|
3064
|
+
type: presetName
|
|
3065
|
+
})
|
|
3066
|
+
);
|
|
3067
|
+
};
|
|
3068
|
+
return /* @__PURE__ */ jsxs31(ConditionRow, { children: [
|
|
3069
|
+
/* @__PURE__ */ jsx45(KeyInput, { value: presetKey, disabled: true }),
|
|
3070
|
+
/* @__PURE__ */ jsx45(
|
|
3071
|
+
OperatorSelect,
|
|
3072
|
+
{
|
|
3073
|
+
value: String(rec.op ?? "eq"),
|
|
3074
|
+
onChange: handleNumOpChange,
|
|
3075
|
+
options: OP_OPTIONS2
|
|
3076
|
+
}
|
|
3077
|
+
),
|
|
3078
|
+
isNumArray ? /* @__PURE__ */ jsx45(
|
|
3079
|
+
NumberTagInput,
|
|
3080
|
+
{
|
|
3081
|
+
value: rec.value ?? [],
|
|
3082
|
+
onChange: (v) => update("value", v),
|
|
3083
|
+
placeholder: `e.g. ${presetKey} value`
|
|
3084
|
+
}
|
|
3085
|
+
) : /* @__PURE__ */ jsx45(
|
|
3086
|
+
Input,
|
|
3087
|
+
{
|
|
3088
|
+
type: "number",
|
|
3089
|
+
className: "h-8 font-mono text-sm",
|
|
3090
|
+
value: rec.value !== void 0 ? String(rec.value) : "",
|
|
3091
|
+
placeholder: "e.g. 100",
|
|
3092
|
+
onChange: (e) => update("value", e.target.value === "" ? "" : Number(e.target.value))
|
|
3093
|
+
}
|
|
3094
|
+
)
|
|
3095
|
+
] });
|
|
3096
|
+
}
|
|
3097
|
+
case "bool":
|
|
3098
|
+
return /* @__PURE__ */ jsxs31(ConditionRow, { children: [
|
|
3099
|
+
/* @__PURE__ */ jsx45(KeyInput, { value: presetKey, disabled: true }),
|
|
3100
|
+
/* @__PURE__ */ jsx45(OperatorSelect, { value: "eq", options: OP_OPTIONS4, disabled: true }),
|
|
3101
|
+
/* @__PURE__ */ jsxs31(
|
|
3102
|
+
Select,
|
|
3103
|
+
{
|
|
3104
|
+
value: String(rec.value ?? "true"),
|
|
3105
|
+
onValueChange: (v) => update("value", v === "true"),
|
|
3106
|
+
children: [
|
|
3107
|
+
/* @__PURE__ */ jsx45(SelectTrigger, { className: "h-8 text-sm", children: /* @__PURE__ */ jsx45(SelectValue, {}) }),
|
|
3108
|
+
/* @__PURE__ */ jsxs31(SelectContent, { children: [
|
|
3109
|
+
/* @__PURE__ */ jsx45(SelectItem, { value: "true", children: "true" }),
|
|
3110
|
+
/* @__PURE__ */ jsx45(SelectItem, { value: "false", children: "false" })
|
|
3111
|
+
] })
|
|
3112
|
+
]
|
|
3113
|
+
}
|
|
3114
|
+
)
|
|
3115
|
+
] });
|
|
3116
|
+
case "datetime":
|
|
3117
|
+
return /* @__PURE__ */ jsxs31(ConditionRow, { children: [
|
|
3118
|
+
/* @__PURE__ */ jsx45(KeyInput, { value: presetKey, disabled: true }),
|
|
3119
|
+
/* @__PURE__ */ jsx45(
|
|
3120
|
+
OperatorSelect,
|
|
3121
|
+
{
|
|
3122
|
+
value: String(rec.op ?? "eq"),
|
|
3123
|
+
onChange: (v) => update("op", v),
|
|
3124
|
+
options: OP_OPTIONS3
|
|
3125
|
+
}
|
|
3126
|
+
),
|
|
3127
|
+
/* @__PURE__ */ jsx45(DateTimeInput, { value: String(rec.value ?? ""), onChange: (v) => update("value", v) })
|
|
3128
|
+
] });
|
|
3129
|
+
}
|
|
3130
|
+
return null;
|
|
3131
|
+
}
|
|
3132
|
+
PresetConditionEditor.displayName = `PresetConditionEditor(${presetName})`;
|
|
3133
|
+
return PresetConditionEditor;
|
|
3134
|
+
}
|
|
3135
|
+
function createPresetUI(presets) {
|
|
3136
|
+
const extraConditionTypes = createPresetConditionMeta(presets);
|
|
3137
|
+
const editorOverrides = /* @__PURE__ */ new Map();
|
|
3138
|
+
for (const [name, preset] of Object.entries(presets)) {
|
|
3139
|
+
if (PRIMITIVE_TYPES.has(preset.type) && preset.key) {
|
|
3140
|
+
editorOverrides.set(name, createPresetEditor(name, preset.type, preset.key));
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
return { extraConditionTypes, editorOverrides };
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
// src/configurator/Configurator.tsx
|
|
3147
|
+
import { useMemo as useMemo12 } from "react";
|
|
3148
|
+
|
|
3149
|
+
// src/configurator/context.ts
|
|
3150
|
+
import { createContext as createContext2, useCallback as useCallback12, useContext as useContext2, useSyncExternalStore, useState as useState10 } from "react";
|
|
3151
|
+
var StoreSourceContext = createContext2(null);
|
|
3152
|
+
var ActionStateContext = createContext2(null);
|
|
3153
|
+
function useStoreSource() {
|
|
3154
|
+
const source = useContext2(StoreSourceContext);
|
|
3155
|
+
if (source) return source;
|
|
3156
|
+
throw new Error("useConfiguratorStore must be used within a <Configurator> component");
|
|
3157
|
+
}
|
|
3158
|
+
function useConfiguratorStore() {
|
|
3159
|
+
const source = useStoreSource();
|
|
3160
|
+
return useSyncExternalStore(source.subscribe, source.getSnapshot);
|
|
3161
|
+
}
|
|
3162
|
+
function useActionState() {
|
|
3163
|
+
const ctx = useContext2(ActionStateContext);
|
|
3164
|
+
if (ctx) return ctx;
|
|
3165
|
+
throw new Error("useActionState must be used within a <Configurator> component");
|
|
3166
|
+
}
|
|
3167
|
+
function useStoreRef() {
|
|
3168
|
+
const source = useStoreSource();
|
|
3169
|
+
return useCallback12(() => source.getSnapshot(), [source]);
|
|
3170
|
+
}
|
|
3171
|
+
function useActionRunner() {
|
|
3172
|
+
const [state, setState] = useState10({ pending: false, error: null });
|
|
3173
|
+
const run = useCallback12(async (action) => {
|
|
3174
|
+
setState({ pending: true, error: null });
|
|
3175
|
+
try {
|
|
3176
|
+
await action();
|
|
3177
|
+
setState({ pending: false, error: null });
|
|
3178
|
+
} catch (err) {
|
|
3179
|
+
setState({ pending: false, error: err instanceof Error ? err : new Error(String(err)) });
|
|
3180
|
+
throw err;
|
|
3181
|
+
}
|
|
3182
|
+
}, []);
|
|
3183
|
+
const clearError = useCallback12(() => setState((s) => ({ ...s, error: null })), []);
|
|
3184
|
+
return {
|
|
3185
|
+
actionState: state,
|
|
3186
|
+
runAction: run,
|
|
3187
|
+
clearError
|
|
3188
|
+
};
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
// src/configurator/useConfiguratorSelector.ts
|
|
3192
|
+
import { useContext as useContext3, useCallback as useCallback13, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
3193
|
+
function useConfiguratorSelector(selector) {
|
|
3194
|
+
const source = useContext3(StoreSourceContext);
|
|
3195
|
+
if (!source) {
|
|
3196
|
+
throw new Error("useConfiguratorSelector must be used within a <Configurator> component");
|
|
3197
|
+
}
|
|
3198
|
+
const getSnapshot = useCallback13(() => selector(source.getSnapshot()), [source, selector]);
|
|
3199
|
+
return useSyncExternalStore2(source.subscribe, getSnapshot);
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
// src/configurator/PreviewPanel.tsx
|
|
3203
|
+
import { useCallback as useCallback14, useEffect, useMemo as useMemo11, useRef as useRef5, useState as useState11 } from "react";
|
|
3204
|
+
import { ChevronRight as ChevronRight2, Eye as Eye2, Loader2, Maximize2, Play } from "lucide-react";
|
|
3205
|
+
import { resolve } from "showwhat";
|
|
3206
|
+
import { DefinitionInactiveError, DefinitionNotFoundError, VariationNotFoundError } from "showwhat";
|
|
3207
|
+
|
|
3208
|
+
// src/configurator/selectors.ts
|
|
3209
|
+
var selectDefinitions = (s) => s.definitions;
|
|
3210
|
+
var selectSelectedKey = (s) => s.selectedKey;
|
|
3211
|
+
var selectValidationErrors = (s) => s.validationErrors;
|
|
3212
|
+
var selectDirtyKeys = (s) => s.dirtyKeys;
|
|
3213
|
+
var selectRevision = (s) => s.revision;
|
|
3214
|
+
|
|
3215
|
+
// src/configurator/fallback-context.ts
|
|
3216
|
+
import { createContext as createContext3, useContext as useContext4 } from "react";
|
|
3217
|
+
var FallbackEvaluatorContext = createContext3(null);
|
|
3218
|
+
var FallbackEvaluatorProvider = FallbackEvaluatorContext.Provider;
|
|
3219
|
+
function useFallbackEvaluator() {
|
|
3220
|
+
return useContext4(FallbackEvaluatorContext);
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
// src/configurator/PreviewPanel.tsx
|
|
3224
|
+
import { Fragment as Fragment7, jsx as jsx46, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
3225
|
+
function ResultBadge({ result, onViewMeta }) {
|
|
3226
|
+
switch (result.status) {
|
|
3227
|
+
case "success":
|
|
3228
|
+
return /* @__PURE__ */ jsxs32("div", { className: "space-y-2", children: [
|
|
3229
|
+
/* @__PURE__ */ jsxs32("div", { className: "flex items-center justify-between", children: [
|
|
3230
|
+
/* @__PURE__ */ jsx46(Badge, { className: "bg-status-active/15 text-green-700 hover:bg-status-active/15 dark:text-green-400", children: "Matched" }),
|
|
3231
|
+
onViewMeta ? /* @__PURE__ */ jsxs32(
|
|
3232
|
+
"button",
|
|
3233
|
+
{
|
|
3234
|
+
type: "button",
|
|
3235
|
+
className: "flex items-center gap-1 text-xs text-muted-foreground/60 hover:text-muted-foreground hover:cursor-pointer",
|
|
3236
|
+
onClick: onViewMeta,
|
|
3237
|
+
"aria-label": "View evaluation meta",
|
|
3238
|
+
children: [
|
|
3239
|
+
/* @__PURE__ */ jsx46(Eye2, { className: "size-3" }),
|
|
3240
|
+
/* @__PURE__ */ jsx46("span", { children: "Details" }),
|
|
3241
|
+
/* @__PURE__ */ jsx46("span", { children: "|" }),
|
|
3242
|
+
/* @__PURE__ */ jsxs32("span", { children: [
|
|
3243
|
+
"Variation #",
|
|
3244
|
+
result.meta.variation.index
|
|
3245
|
+
] })
|
|
3246
|
+
]
|
|
3247
|
+
}
|
|
3248
|
+
) : /* @__PURE__ */ jsxs32("span", { className: "text-xs text-muted-foreground", children: [
|
|
3249
|
+
"Variation #",
|
|
3250
|
+
result.meta.variation.index
|
|
3251
|
+
] })
|
|
3252
|
+
] }),
|
|
3253
|
+
/* @__PURE__ */ jsx46("div", { className: "rounded-md border border-border bg-background p-2", children: /* @__PURE__ */ jsx46("pre", { className: "whitespace-pre-wrap break-all font-mono text-xs", children: typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2) }) })
|
|
3254
|
+
] });
|
|
3255
|
+
case "inactive":
|
|
3256
|
+
return /* @__PURE__ */ jsxs32("div", { className: "space-y-2", children: [
|
|
3257
|
+
/* @__PURE__ */ jsx46(Badge, { className: "bg-amber-500/15 text-amber-600 hover:bg-amber-500/15", children: "Inactive" }),
|
|
3258
|
+
/* @__PURE__ */ jsx46("p", { className: "text-xs text-muted-foreground", children: result.message })
|
|
3259
|
+
] });
|
|
3260
|
+
case "no-match":
|
|
3261
|
+
return /* @__PURE__ */ jsxs32("div", { className: "space-y-2", children: [
|
|
3262
|
+
/* @__PURE__ */ jsx46(Badge, { className: "bg-orange-500/15 text-orange-600 hover:bg-orange-500/15", children: "No Match" }),
|
|
3263
|
+
/* @__PURE__ */ jsx46("p", { className: "text-xs text-muted-foreground", children: result.message })
|
|
3264
|
+
] });
|
|
3265
|
+
case "error":
|
|
3266
|
+
return /* @__PURE__ */ jsxs32("div", { className: "space-y-2", children: [
|
|
3267
|
+
/* @__PURE__ */ jsx46(Badge, { variant: "destructive", children: "Error" }),
|
|
3268
|
+
/* @__PURE__ */ jsx46("p", { className: "text-xs text-destructive", children: result.message })
|
|
3269
|
+
] });
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
function parseContextJson(text) {
|
|
3273
|
+
const trimmed = text.trim();
|
|
3274
|
+
if (!trimmed) return {};
|
|
3275
|
+
const parsed = JSON.parse(trimmed);
|
|
3276
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3277
|
+
throw new SyntaxError("Context must be a JSON object");
|
|
3278
|
+
}
|
|
3279
|
+
const context = {};
|
|
3280
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
3281
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
3282
|
+
context[key] = value;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
return context;
|
|
3286
|
+
}
|
|
3287
|
+
function parseEvaluatorOverrides(text) {
|
|
3288
|
+
const overrides = {};
|
|
3289
|
+
for (const line of text.split("\n")) {
|
|
3290
|
+
const trimmed = line.trim();
|
|
3291
|
+
if (!trimmed) continue;
|
|
3292
|
+
const idx = trimmed.indexOf(":");
|
|
3293
|
+
if (idx < 1) continue;
|
|
3294
|
+
const type = trimmed.slice(0, idx).trim();
|
|
3295
|
+
const value = trimmed.slice(idx + 1).trim().toLowerCase();
|
|
3296
|
+
if (type && (value === "true" || value === "false")) {
|
|
3297
|
+
overrides[type] = value === "true";
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
return overrides;
|
|
3301
|
+
}
|
|
3302
|
+
function JsonEditorDialog({
|
|
3303
|
+
open,
|
|
3304
|
+
onOpenChange,
|
|
3305
|
+
value,
|
|
3306
|
+
onSave
|
|
3307
|
+
}) {
|
|
3308
|
+
const [draft, setDraft] = useState11(value);
|
|
3309
|
+
const [formatError, setFormatError] = useState11(null);
|
|
3310
|
+
useEffect(() => {
|
|
3311
|
+
if (open) {
|
|
3312
|
+
setDraft(value);
|
|
3313
|
+
setFormatError(null);
|
|
3314
|
+
}
|
|
3315
|
+
}, [open, value]);
|
|
3316
|
+
function handleFormat() {
|
|
3317
|
+
const trimmed = draft.trim();
|
|
3318
|
+
if (!trimmed) return;
|
|
3319
|
+
try {
|
|
3320
|
+
const parsed = JSON.parse(trimmed);
|
|
3321
|
+
setDraft(JSON.stringify(parsed, null, 2));
|
|
3322
|
+
setFormatError(null);
|
|
3323
|
+
} catch {
|
|
3324
|
+
setFormatError("Invalid JSON");
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
function handleSave() {
|
|
3328
|
+
onSave(draft);
|
|
3329
|
+
onOpenChange(false);
|
|
3330
|
+
}
|
|
3331
|
+
return /* @__PURE__ */ jsx46(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs32(DialogContent, { className: "sm:max-w-2xl", children: [
|
|
3332
|
+
/* @__PURE__ */ jsxs32(DialogHeader, { children: [
|
|
3333
|
+
/* @__PURE__ */ jsx46(DialogTitle, { children: "Edit Context" }),
|
|
3334
|
+
/* @__PURE__ */ jsx46(DialogDescription, { children: "JSON object with context values for resolving the definition." })
|
|
3335
|
+
] }),
|
|
3336
|
+
/* @__PURE__ */ jsx46(
|
|
3337
|
+
Textarea,
|
|
3338
|
+
{
|
|
3339
|
+
placeholder: '{\n "env": "production",\n "region": "us-east-1"\n}',
|
|
3340
|
+
value: draft,
|
|
3341
|
+
onChange: (e) => {
|
|
3342
|
+
setDraft(e.target.value);
|
|
3343
|
+
setFormatError(null);
|
|
3344
|
+
},
|
|
3345
|
+
className: `min-h-64 font-mono text-sm ${formatError ? "border-destructive" : ""}`,
|
|
3346
|
+
rows: 14
|
|
3347
|
+
}
|
|
3348
|
+
),
|
|
3349
|
+
formatError && /* @__PURE__ */ jsx46("p", { className: "text-xs text-destructive", children: formatError }),
|
|
3350
|
+
/* @__PURE__ */ jsxs32(DialogFooter, { children: [
|
|
3351
|
+
/* @__PURE__ */ jsx46(Button, { variant: "outline", size: "sm", onClick: handleFormat, children: "Format" }),
|
|
3352
|
+
/* @__PURE__ */ jsx46(Button, { size: "sm", onClick: handleSave, children: "Apply" })
|
|
3353
|
+
] })
|
|
3354
|
+
] }) });
|
|
3355
|
+
}
|
|
3356
|
+
function PreviewPanel() {
|
|
3357
|
+
const definitions = useConfiguratorSelector(selectDefinitions);
|
|
3358
|
+
const selectedKey = useConfiguratorSelector(selectSelectedKey);
|
|
3359
|
+
const externalFallback = useFallbackEvaluator();
|
|
3360
|
+
const [contextText, setContextText] = useState11("");
|
|
3361
|
+
const [evaluatorText, setEvaluatorText] = useState11("");
|
|
3362
|
+
const [contextError, setContextError] = useState11(null);
|
|
3363
|
+
const [simulatorOpen, setSimulatorOpen] = useState11(false);
|
|
3364
|
+
const [jsonEditorOpen, setJsonEditorOpen] = useState11(false);
|
|
3365
|
+
const [previewResult, setPreviewResult] = useState11(null);
|
|
3366
|
+
const [isResolving, setIsResolving] = useState11(false);
|
|
3367
|
+
const [metaDialogOpen, setMetaDialogOpen] = useState11(false);
|
|
3368
|
+
const abortControllerRef = useRef5(null);
|
|
3369
|
+
useEffect(() => {
|
|
3370
|
+
abortControllerRef.current?.abort();
|
|
3371
|
+
abortControllerRef.current = null;
|
|
3372
|
+
setPreviewResult(null);
|
|
3373
|
+
return () => {
|
|
3374
|
+
abortControllerRef.current?.abort();
|
|
3375
|
+
};
|
|
3376
|
+
}, [selectedKey]);
|
|
3377
|
+
const fallback = useMemo11(() => {
|
|
3378
|
+
const overrides = parseEvaluatorOverrides(evaluatorText);
|
|
3379
|
+
const hasOverrides = Object.keys(overrides).length > 0;
|
|
3380
|
+
if (!hasOverrides && !externalFallback) return void 0;
|
|
3381
|
+
return async (args) => {
|
|
3382
|
+
const type = args.condition.type;
|
|
3383
|
+
if (type in overrides) return overrides[type];
|
|
3384
|
+
if (externalFallback) return externalFallback(args);
|
|
3385
|
+
return false;
|
|
3386
|
+
};
|
|
3387
|
+
}, [evaluatorText, externalFallback]);
|
|
3388
|
+
const handleResolve = useCallback14(async () => {
|
|
3389
|
+
if (!selectedKey || !definitions[selectedKey]) {
|
|
3390
|
+
setPreviewResult({ status: "error", message: "No definition selected" });
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3393
|
+
abortControllerRef.current?.abort();
|
|
3394
|
+
const controller = new AbortController();
|
|
3395
|
+
abortControllerRef.current = controller;
|
|
3396
|
+
setIsResolving(true);
|
|
3397
|
+
setContextError(null);
|
|
3398
|
+
let context;
|
|
3399
|
+
try {
|
|
3400
|
+
context = parseContextJson(contextText);
|
|
3401
|
+
} catch {
|
|
3402
|
+
setPreviewResult({ status: "error", message: "Invalid JSON in context" });
|
|
3403
|
+
setIsResolving(false);
|
|
3404
|
+
return;
|
|
3405
|
+
}
|
|
3406
|
+
try {
|
|
3407
|
+
const result = await resolve({
|
|
3408
|
+
definitions: { [selectedKey]: definitions[selectedKey] },
|
|
3409
|
+
context,
|
|
3410
|
+
options: fallback ? { fallback } : void 0
|
|
3411
|
+
});
|
|
3412
|
+
if (controller.signal.aborted) return;
|
|
3413
|
+
const resolution = result[selectedKey];
|
|
3414
|
+
setPreviewResult({
|
|
3415
|
+
status: "success",
|
|
3416
|
+
value: resolution.value,
|
|
3417
|
+
meta: resolution.meta
|
|
3418
|
+
});
|
|
3419
|
+
} catch (err) {
|
|
3420
|
+
if (controller.signal.aborted) return;
|
|
3421
|
+
if (err instanceof DefinitionInactiveError) {
|
|
3422
|
+
setPreviewResult({
|
|
3423
|
+
status: "inactive",
|
|
3424
|
+
message: `"${selectedKey}" is inactive`
|
|
3425
|
+
});
|
|
3426
|
+
} else if (err instanceof VariationNotFoundError) {
|
|
3427
|
+
setPreviewResult({
|
|
3428
|
+
status: "no-match",
|
|
3429
|
+
message: "No variation matched the given context"
|
|
3430
|
+
});
|
|
3431
|
+
} else if (err instanceof DefinitionNotFoundError) {
|
|
3432
|
+
setPreviewResult({
|
|
3433
|
+
status: "error",
|
|
3434
|
+
message: `Definition "${selectedKey}" not found`
|
|
3435
|
+
});
|
|
3436
|
+
} else {
|
|
3437
|
+
setPreviewResult({
|
|
3438
|
+
status: "error",
|
|
3439
|
+
message: err instanceof Error ? err.message : "Unknown error"
|
|
3440
|
+
});
|
|
3441
|
+
}
|
|
3442
|
+
} finally {
|
|
3443
|
+
if (!controller.signal.aborted) {
|
|
3444
|
+
setIsResolving(false);
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
}, [selectedKey, definitions, contextText, fallback]);
|
|
3448
|
+
return /* @__PURE__ */ jsxs32("div", { className: "flex w-80 shrink-0 flex-col border-l border-border bg-muted/30", children: [
|
|
3449
|
+
/* @__PURE__ */ jsx46("div", { className: "flex h-12 items-center border-b border-border px-3", children: /* @__PURE__ */ jsx46("span", { className: "text-sm font-semibold", children: "Preview" }) }),
|
|
3450
|
+
/* @__PURE__ */ jsx46(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsxs32("div", { className: "space-y-4 p-3", children: [
|
|
3451
|
+
/* @__PURE__ */ jsxs32("div", { children: [
|
|
3452
|
+
/* @__PURE__ */ jsx46(Label, { className: "text-xs text-muted-foreground", children: "Definition" }),
|
|
3453
|
+
/* @__PURE__ */ jsx46("p", { className: "mt-0.5 truncate font-mono text-sm", children: selectedKey ?? "None selected" })
|
|
3454
|
+
] }),
|
|
3455
|
+
/* @__PURE__ */ jsx46(Separator, {}),
|
|
3456
|
+
/* @__PURE__ */ jsxs32("div", { children: [
|
|
3457
|
+
/* @__PURE__ */ jsxs32("div", { className: "flex items-center justify-between", children: [
|
|
3458
|
+
/* @__PURE__ */ jsx46(Label, { className: "text-xs text-muted-foreground", children: "Context" }),
|
|
3459
|
+
/* @__PURE__ */ jsxs32(
|
|
3460
|
+
"button",
|
|
3461
|
+
{
|
|
3462
|
+
type: "button",
|
|
3463
|
+
className: "flex items-center gap-1 text-[10px] text-muted-foreground hover:text-foreground",
|
|
3464
|
+
onClick: () => setJsonEditorOpen(true),
|
|
3465
|
+
children: [
|
|
3466
|
+
/* @__PURE__ */ jsx46(Maximize2, { className: "h-3 w-3" }),
|
|
3467
|
+
"Edit"
|
|
3468
|
+
]
|
|
3469
|
+
}
|
|
3470
|
+
)
|
|
3471
|
+
] }),
|
|
3472
|
+
/* @__PURE__ */ jsx46(
|
|
3473
|
+
"button",
|
|
3474
|
+
{
|
|
3475
|
+
type: "button",
|
|
3476
|
+
className: `mt-1.5 w-full cursor-pointer rounded-md border bg-transparent px-3 py-2 text-left transition-colors hover:border-ring ${contextError ? "border-destructive" : "border-input"}`,
|
|
3477
|
+
onClick: () => setJsonEditorOpen(true),
|
|
3478
|
+
children: contextText.trim() ? /* @__PURE__ */ jsx46("pre", { className: "max-h-28 overflow-hidden font-mono text-xs text-foreground", children: contextText }) : /* @__PURE__ */ jsx46("span", { className: "font-mono text-xs text-muted-foreground", children: '{ "env": "production" }' })
|
|
3479
|
+
}
|
|
3480
|
+
),
|
|
3481
|
+
contextError && /* @__PURE__ */ jsx46("p", { className: "mt-1 text-[10px] text-destructive", children: contextError }),
|
|
3482
|
+
/* @__PURE__ */ jsx46(
|
|
3483
|
+
JsonEditorDialog,
|
|
3484
|
+
{
|
|
3485
|
+
open: jsonEditorOpen,
|
|
3486
|
+
onOpenChange: setJsonEditorOpen,
|
|
3487
|
+
value: contextText,
|
|
3488
|
+
onSave: (v) => {
|
|
3489
|
+
setContextText(v);
|
|
3490
|
+
setContextError(null);
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
)
|
|
3494
|
+
] }),
|
|
3495
|
+
/* @__PURE__ */ jsx46(Separator, {}),
|
|
3496
|
+
/* @__PURE__ */ jsxs32("div", { children: [
|
|
3497
|
+
/* @__PURE__ */ jsxs32(
|
|
3498
|
+
"button",
|
|
3499
|
+
{
|
|
3500
|
+
type: "button",
|
|
3501
|
+
className: "flex w-full items-center gap-1 text-xs text-muted-foreground hover:text-foreground",
|
|
3502
|
+
onClick: () => setSimulatorOpen((o) => !o),
|
|
3503
|
+
children: [
|
|
3504
|
+
/* @__PURE__ */ jsx46(
|
|
3505
|
+
ChevronRight2,
|
|
3506
|
+
{
|
|
3507
|
+
className: `h-3.5 w-3.5 transition-transform ${simulatorOpen ? "rotate-90" : ""}`
|
|
3508
|
+
}
|
|
3509
|
+
),
|
|
3510
|
+
"Condition Simulator"
|
|
3511
|
+
]
|
|
3512
|
+
}
|
|
3513
|
+
),
|
|
3514
|
+
simulatorOpen && /* @__PURE__ */ jsxs32("div", { className: "mt-2", children: [
|
|
3515
|
+
/* @__PURE__ */ jsx46(
|
|
3516
|
+
Textarea,
|
|
3517
|
+
{
|
|
3518
|
+
placeholder: "tier:true\ngeo:false",
|
|
3519
|
+
value: evaluatorText,
|
|
3520
|
+
onChange: (e) => setEvaluatorText(e.target.value),
|
|
3521
|
+
className: "min-h-20 font-mono text-xs",
|
|
3522
|
+
rows: 6
|
|
3523
|
+
}
|
|
3524
|
+
),
|
|
3525
|
+
/* @__PURE__ */ jsx46("p", { className: "mt-1 text-[10px] text-muted-foreground", children: "Simulate unregistered condition types. One type:true|false per line" })
|
|
3526
|
+
] })
|
|
3527
|
+
] }),
|
|
3528
|
+
/* @__PURE__ */ jsxs32(
|
|
3529
|
+
Button,
|
|
3530
|
+
{
|
|
3531
|
+
size: "sm",
|
|
3532
|
+
className: "w-full",
|
|
3533
|
+
onClick: handleResolve,
|
|
3534
|
+
disabled: !selectedKey || isResolving,
|
|
3535
|
+
children: [
|
|
3536
|
+
isResolving ? /* @__PURE__ */ jsx46(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx46(Play, { className: "mr-1.5 h-4 w-4" }),
|
|
3537
|
+
"Resolve"
|
|
3538
|
+
]
|
|
3539
|
+
}
|
|
3540
|
+
),
|
|
3541
|
+
previewResult && /* @__PURE__ */ jsxs32(Fragment7, { children: [
|
|
3542
|
+
/* @__PURE__ */ jsx46(Separator, {}),
|
|
3543
|
+
/* @__PURE__ */ jsxs32("div", { className: "animate-slide-in-right", children: [
|
|
3544
|
+
/* @__PURE__ */ jsx46(Label, { className: "text-xs text-muted-foreground", children: "Result" }),
|
|
3545
|
+
/* @__PURE__ */ jsx46("div", { className: "mt-1.5", children: /* @__PURE__ */ jsx46(
|
|
3546
|
+
ResultBadge,
|
|
3547
|
+
{
|
|
3548
|
+
result: previewResult,
|
|
3549
|
+
onViewMeta: previewResult.status === "success" ? () => setMetaDialogOpen(true) : void 0
|
|
3550
|
+
}
|
|
3551
|
+
) }),
|
|
3552
|
+
previewResult.status === "success" && /* @__PURE__ */ jsx46(Dialog, { open: metaDialogOpen, onOpenChange: setMetaDialogOpen, children: /* @__PURE__ */ jsxs32(DialogContent, { className: "sm:max-w-lg", children: [
|
|
3553
|
+
/* @__PURE__ */ jsxs32(DialogHeader, { children: [
|
|
3554
|
+
/* @__PURE__ */ jsx46(DialogTitle, { children: "Evaluation Meta" }),
|
|
3555
|
+
/* @__PURE__ */ jsx46(DialogDescription, { children: "Full resolution metadata from the last evaluation." })
|
|
3556
|
+
] }),
|
|
3557
|
+
/* @__PURE__ */ jsx46(ScrollArea, { className: "max-h-96", children: /* @__PURE__ */ jsx46("pre", { className: "whitespace-pre-wrap break-all font-mono text-xs", children: JSON.stringify(previewResult.meta, null, 2) }) })
|
|
3558
|
+
] }) })
|
|
3559
|
+
] })
|
|
3560
|
+
] })
|
|
3561
|
+
] }) })
|
|
3562
|
+
] });
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
// src/configurator/Configurator.tsx
|
|
3566
|
+
import { Fragment as Fragment8, jsx as jsx47, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
3567
|
+
function useNormalizedSource(store) {
|
|
3568
|
+
return useMemo12(() => {
|
|
3569
|
+
if (isStoreSource(store)) {
|
|
3570
|
+
return store;
|
|
3571
|
+
}
|
|
3572
|
+
const snapshot = store;
|
|
3573
|
+
return {
|
|
3574
|
+
getSnapshot: () => snapshot,
|
|
3575
|
+
subscribe: () => () => {
|
|
3576
|
+
}
|
|
3577
|
+
};
|
|
3578
|
+
}, [store]);
|
|
3579
|
+
}
|
|
3580
|
+
function isStoreSource(obj) {
|
|
3581
|
+
return typeof obj === "object" && obj !== null && "getSnapshot" in obj && "subscribe" in obj && typeof obj.getSnapshot === "function" && typeof obj.subscribe === "function";
|
|
3582
|
+
}
|
|
3583
|
+
function ErrorBanner() {
|
|
3584
|
+
const { actionState, clearError } = useActionState();
|
|
3585
|
+
if (!actionState.error) return null;
|
|
3586
|
+
return /* @__PURE__ */ jsxs33(
|
|
3587
|
+
"div",
|
|
3588
|
+
{
|
|
3589
|
+
role: "alert",
|
|
3590
|
+
className: "flex items-center justify-between border-b border-destructive/30 bg-destructive/10 px-4 py-2",
|
|
3591
|
+
children: [
|
|
3592
|
+
/* @__PURE__ */ jsxs33("p", { className: "text-xs font-medium text-destructive", children: [
|
|
3593
|
+
"Action failed: ",
|
|
3594
|
+
actionState.error.message
|
|
3595
|
+
] }),
|
|
3596
|
+
/* @__PURE__ */ jsx47("button", { type: "button", className: "text-xs text-destructive underline", onClick: clearError, children: "Dismiss" })
|
|
3597
|
+
]
|
|
3598
|
+
}
|
|
3599
|
+
);
|
|
3600
|
+
}
|
|
3601
|
+
function EditorLayout({ emptyState }) {
|
|
3602
|
+
const definitions = useConfiguratorSelector(selectDefinitions);
|
|
3603
|
+
const selectedKey = useConfiguratorSelector(selectSelectedKey);
|
|
3604
|
+
const validationErrors = useConfiguratorSelector(selectValidationErrors);
|
|
3605
|
+
const dirtyKeys = useConfiguratorSelector(selectDirtyKeys);
|
|
3606
|
+
const revision = useConfiguratorSelector(selectRevision);
|
|
3607
|
+
const getStore = useStoreRef();
|
|
3608
|
+
const { actionState, runAction } = useActionState();
|
|
3609
|
+
const selectedDefinition = selectedKey ? definitions[selectedKey] : null;
|
|
3610
|
+
if (Object.keys(definitions).length === 0 && emptyState) {
|
|
3611
|
+
return /* @__PURE__ */ jsx47(Fragment8, { children: emptyState });
|
|
3612
|
+
}
|
|
3613
|
+
return /* @__PURE__ */ jsxs33("div", { className: "flex h-full flex-col", children: [
|
|
3614
|
+
/* @__PURE__ */ jsx47(ErrorBanner, {}),
|
|
3615
|
+
/* @__PURE__ */ jsxs33("div", { className: "flex flex-1 overflow-hidden", children: [
|
|
3616
|
+
/* @__PURE__ */ jsx47("div", { className: "w-72 shrink-0 border-r border-border bg-muted/30", children: /* @__PURE__ */ jsx47(
|
|
3617
|
+
DefinitionList,
|
|
3618
|
+
{
|
|
3619
|
+
definitions,
|
|
3620
|
+
selectedKey,
|
|
3621
|
+
validationErrors,
|
|
3622
|
+
dirtyKeys,
|
|
3623
|
+
onSelect: (key) => {
|
|
3624
|
+
runAction(() => getStore().selectDefinition(key)).catch(() => {
|
|
3625
|
+
});
|
|
3626
|
+
},
|
|
3627
|
+
onAdd: (key) => runAction(() => getStore().addDefinition(key)),
|
|
3628
|
+
onRemove: (key) => {
|
|
3629
|
+
runAction(() => getStore().removeDefinition(key)).catch(() => {
|
|
3630
|
+
});
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
) }),
|
|
3634
|
+
/* @__PURE__ */ jsx47("div", { className: "flex-1 overflow-hidden bg-background", children: /* @__PURE__ */ jsx47(ErrorBoundary, { children: selectedDefinition && selectedKey ? /* @__PURE__ */ jsx47("div", { className: "h-full animate-fade-up", children: /* @__PURE__ */ jsx47(
|
|
3635
|
+
DefinitionEditor,
|
|
3636
|
+
{
|
|
3637
|
+
definitionKey: selectedKey,
|
|
3638
|
+
definition: selectedDefinition,
|
|
3639
|
+
validationErrors: validationErrors[selectedKey],
|
|
3640
|
+
isDirty: dirtyKeys.includes(selectedKey),
|
|
3641
|
+
isPending: actionState.pending,
|
|
3642
|
+
onUpdate: (def) => {
|
|
3643
|
+
getStore().updateDefinition(selectedKey, def).catch(() => {
|
|
3644
|
+
});
|
|
3645
|
+
},
|
|
3646
|
+
onRename: (newKey) => runAction(() => getStore().renameDefinition(selectedKey, newKey)),
|
|
3647
|
+
onSave: () => {
|
|
3648
|
+
runAction(() => getStore().saveDefinition(selectedKey)).catch(() => {
|
|
3649
|
+
});
|
|
3650
|
+
},
|
|
3651
|
+
onDiscard: () => {
|
|
3652
|
+
runAction(() => getStore().discardDefinition(selectedKey)).catch(() => {
|
|
3653
|
+
});
|
|
3654
|
+
}
|
|
3655
|
+
},
|
|
3656
|
+
`${selectedKey}-${revision}`
|
|
3657
|
+
) }, `anim-${selectedKey}-${revision}`) : /* @__PURE__ */ jsx47("div", { className: "flex h-full items-center justify-center text-sm text-muted-foreground", children: "Select a definition to edit" }) }) }),
|
|
3658
|
+
/* @__PURE__ */ jsx47(ErrorBoundary, { children: /* @__PURE__ */ jsx47(PreviewPanel, {}) })
|
|
3659
|
+
] })
|
|
3660
|
+
] });
|
|
3661
|
+
}
|
|
3662
|
+
function Configurator({
|
|
3663
|
+
store,
|
|
3664
|
+
className,
|
|
3665
|
+
emptyState,
|
|
3666
|
+
conditionExtensions,
|
|
3667
|
+
fallbackEvaluator
|
|
3668
|
+
}) {
|
|
3669
|
+
const runner = useActionRunner();
|
|
3670
|
+
const storeSource = useNormalizedSource(store);
|
|
3671
|
+
return /* @__PURE__ */ jsx47(StoreSourceContext.Provider, { value: storeSource, children: /* @__PURE__ */ jsx47(ActionStateContext.Provider, { value: runner, children: /* @__PURE__ */ jsx47(ConditionExtensionsProvider, { value: conditionExtensions ?? null, children: /* @__PURE__ */ jsx47(FallbackEvaluatorProvider, { value: fallbackEvaluator ?? null, children: /* @__PURE__ */ jsx47("div", { className, children: /* @__PURE__ */ jsx47(EditorLayout, { emptyState }) }) }) }) }) });
|
|
3672
|
+
}
|
|
3673
|
+
export {
|
|
3674
|
+
AUTO_ID_PREFIX,
|
|
3675
|
+
ActionStateContext,
|
|
3676
|
+
BUILTIN_CONDITION_TYPES,
|
|
3677
|
+
Badge,
|
|
3678
|
+
Button,
|
|
3679
|
+
CONDITION_TYPE_MAP,
|
|
3680
|
+
ConditionBuilder,
|
|
3681
|
+
Configurator,
|
|
3682
|
+
ConfirmDialog,
|
|
3683
|
+
DateTimeInput,
|
|
3684
|
+
DefinitionEditor,
|
|
3685
|
+
DefinitionList,
|
|
3686
|
+
Dialog,
|
|
3687
|
+
DialogClose,
|
|
3688
|
+
DialogContent,
|
|
3689
|
+
DialogDescription,
|
|
3690
|
+
DialogFooter,
|
|
3691
|
+
DialogHeader,
|
|
3692
|
+
DialogTitle,
|
|
3693
|
+
DialogTrigger,
|
|
3694
|
+
DropdownMenu,
|
|
3695
|
+
DropdownMenuContent,
|
|
3696
|
+
DropdownMenuItem,
|
|
3697
|
+
DropdownMenuSeparator,
|
|
3698
|
+
DropdownMenuTrigger,
|
|
3699
|
+
ErrorBoundary,
|
|
3700
|
+
Input,
|
|
3701
|
+
Label,
|
|
3702
|
+
Popover,
|
|
3703
|
+
PopoverContent,
|
|
3704
|
+
PopoverTrigger,
|
|
3705
|
+
ScrollArea,
|
|
3706
|
+
ScrollBar,
|
|
3707
|
+
Select,
|
|
3708
|
+
SelectContent,
|
|
3709
|
+
SelectGroup,
|
|
3710
|
+
SelectItem,
|
|
3711
|
+
SelectLabel,
|
|
3712
|
+
SelectSeparator,
|
|
3713
|
+
SelectTrigger,
|
|
3714
|
+
SelectValue,
|
|
3715
|
+
Separator,
|
|
3716
|
+
StoreSourceContext,
|
|
3717
|
+
Switch,
|
|
3718
|
+
Tabs,
|
|
3719
|
+
TabsContent,
|
|
3720
|
+
TabsList,
|
|
3721
|
+
TabsTrigger,
|
|
3722
|
+
Textarea,
|
|
3723
|
+
ThemeToggle,
|
|
3724
|
+
ValidationMessage,
|
|
3725
|
+
ValueInput,
|
|
3726
|
+
VariationCard,
|
|
3727
|
+
VariationList,
|
|
3728
|
+
badgeVariants,
|
|
3729
|
+
buttonVariants,
|
|
3730
|
+
cn,
|
|
3731
|
+
createPresetConditionMeta,
|
|
3732
|
+
createPresetUI,
|
|
3733
|
+
getConditionMeta,
|
|
3734
|
+
isAutoId,
|
|
3735
|
+
stripAutoIds,
|
|
3736
|
+
useActionState,
|
|
3737
|
+
useConfiguratorSelector,
|
|
3738
|
+
useConfiguratorStore,
|
|
3739
|
+
useStoreRef
|
|
3740
|
+
};
|
|
3741
|
+
//# sourceMappingURL=index.js.map
|