@ampless/admin 1.0.0-alpha.52 → 1.0.0-alpha.54
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.
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import {
|
|
3
|
+
invalidateSiteSettingsCache
|
|
4
|
+
} from "./chunk-D72XF3Q3.js";
|
|
5
|
+
import {
|
|
6
|
+
useLocale,
|
|
7
|
+
useT
|
|
8
|
+
} from "./chunk-2SVJONQ7.js";
|
|
9
|
+
|
|
10
|
+
// src/components/plugin-settings-form.tsx
|
|
11
|
+
import { useState as useState2 } from "react";
|
|
12
|
+
import {
|
|
13
|
+
resolveLocalized as resolveLocalized2
|
|
14
|
+
} from "ampless";
|
|
15
|
+
import { Button, Input, Label as Label2, Textarea } from "@ampless/runtime/ui";
|
|
16
|
+
|
|
17
|
+
// src/lib/plugin-settings.ts
|
|
18
|
+
import {
|
|
19
|
+
getKvStore,
|
|
20
|
+
SITE_CONFIG_PK,
|
|
21
|
+
isValidPluginKey,
|
|
22
|
+
validatePluginSettingValue
|
|
23
|
+
} from "ampless";
|
|
24
|
+
function pluginSettingKey(instanceId, fieldKey) {
|
|
25
|
+
return `plugins.${instanceId}.${fieldKey}`;
|
|
26
|
+
}
|
|
27
|
+
async function setPluginPublicSetting(instanceId, field, rawValue) {
|
|
28
|
+
if (!isValidPluginKey(instanceId)) {
|
|
29
|
+
throw new Error(`Invalid plugin instanceId: "${instanceId}"`);
|
|
30
|
+
}
|
|
31
|
+
if (!isValidPluginKey(field.key)) {
|
|
32
|
+
throw new Error(`Invalid plugin field key: "${field.key}"`);
|
|
33
|
+
}
|
|
34
|
+
const validated = validatePluginSettingValue(field, rawValue, "strict");
|
|
35
|
+
if (validated === null) {
|
|
36
|
+
throw new Error(`Invalid value for plugin field "${field.key}"`);
|
|
37
|
+
}
|
|
38
|
+
const store = getKvStore();
|
|
39
|
+
await store.put(SITE_CONFIG_PK, pluginSettingKey(instanceId, field.key), validated);
|
|
40
|
+
}
|
|
41
|
+
async function deletePluginPublicSetting(instanceId, field) {
|
|
42
|
+
if (!isValidPluginKey(instanceId)) {
|
|
43
|
+
throw new Error(`Invalid plugin instanceId: "${instanceId}"`);
|
|
44
|
+
}
|
|
45
|
+
if (!isValidPluginKey(field.key)) {
|
|
46
|
+
throw new Error(`Invalid plugin field key: "${field.key}"`);
|
|
47
|
+
}
|
|
48
|
+
const store = getKvStore();
|
|
49
|
+
await store.remove(SITE_CONFIG_PK, pluginSettingKey(instanceId, field.key));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/components/repeatable-field-editor.tsx
|
|
53
|
+
import { useEffect, useState } from "react";
|
|
54
|
+
import { resolveLocalized } from "ampless";
|
|
55
|
+
import { Label } from "@ampless/runtime/ui";
|
|
56
|
+
|
|
57
|
+
// src/lib/repeatable-field.ts
|
|
58
|
+
function parseRepeatableValue(raw) {
|
|
59
|
+
if (!raw || raw.trim() === "") return [];
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(raw);
|
|
62
|
+
if (!Array.isArray(parsed)) return [];
|
|
63
|
+
return parsed.filter(
|
|
64
|
+
(item) => item !== null && typeof item === "object" && !Array.isArray(item)
|
|
65
|
+
);
|
|
66
|
+
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function serializeRepeatableValue(items) {
|
|
71
|
+
return JSON.stringify(items);
|
|
72
|
+
}
|
|
73
|
+
function subFieldValueToFormString(_subField, value) {
|
|
74
|
+
if (value === void 0 || value === null) return "";
|
|
75
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
76
|
+
if (typeof value === "number") return String(value);
|
|
77
|
+
if (typeof value === "string") return value;
|
|
78
|
+
return String(value);
|
|
79
|
+
}
|
|
80
|
+
function formStringToSubFieldValue(subField, formString) {
|
|
81
|
+
switch (subField.type) {
|
|
82
|
+
case "boolean":
|
|
83
|
+
return formString === "true";
|
|
84
|
+
case "number": {
|
|
85
|
+
if (formString.trim() === "") return void 0;
|
|
86
|
+
const n = Number(formString);
|
|
87
|
+
return Number.isNaN(n) ? void 0 : n;
|
|
88
|
+
}
|
|
89
|
+
default:
|
|
90
|
+
return formString;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function makeEmptyItem(field) {
|
|
94
|
+
const item = {};
|
|
95
|
+
for (const sf of field.fields) {
|
|
96
|
+
if (sf.default !== void 0) {
|
|
97
|
+
item[sf.key] = sf.default;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (sf.type === "boolean") {
|
|
101
|
+
item[sf.key] = false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return item;
|
|
105
|
+
}
|
|
106
|
+
function itemLabel(field, item, index) {
|
|
107
|
+
if (field.itemLabelKey) {
|
|
108
|
+
const v = item[field.itemLabelKey];
|
|
109
|
+
if (v !== void 0 && v !== null && String(v).trim() !== "") {
|
|
110
|
+
return String(v);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return `Item ${index + 1}`;
|
|
114
|
+
}
|
|
115
|
+
function canAddItem(field, currentCount) {
|
|
116
|
+
const max = field.maxItems ?? 50;
|
|
117
|
+
return currentCount < max;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/components/repeatable-field-editor.tsx
|
|
121
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
122
|
+
function RepeatableFieldEditor({
|
|
123
|
+
field,
|
|
124
|
+
id,
|
|
125
|
+
value,
|
|
126
|
+
invalid,
|
|
127
|
+
onChange
|
|
128
|
+
}) {
|
|
129
|
+
const locale = useLocale();
|
|
130
|
+
const [items, setItems] = useState(
|
|
131
|
+
() => parseRepeatableValue(value)
|
|
132
|
+
);
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
const incoming = parseRepeatableValue(value);
|
|
135
|
+
if (JSON.stringify(incoming) !== JSON.stringify(items)) {
|
|
136
|
+
setItems(incoming);
|
|
137
|
+
}
|
|
138
|
+
}, [value]);
|
|
139
|
+
function commit(next) {
|
|
140
|
+
setItems(next);
|
|
141
|
+
onChange(serializeRepeatableValue(next));
|
|
142
|
+
}
|
|
143
|
+
function updateCell(itemIdx, key, cellValue) {
|
|
144
|
+
const next = items.map(
|
|
145
|
+
(item, idx) => idx === itemIdx ? { ...item, [key]: cellValue } : item
|
|
146
|
+
);
|
|
147
|
+
commit(next);
|
|
148
|
+
}
|
|
149
|
+
function removeItem(itemIdx) {
|
|
150
|
+
commit(items.filter((_, idx) => idx !== itemIdx));
|
|
151
|
+
}
|
|
152
|
+
function addItem() {
|
|
153
|
+
if (!canAddItem(field, items.length)) return;
|
|
154
|
+
commit([...items, makeEmptyItem(field)]);
|
|
155
|
+
}
|
|
156
|
+
const addLabelText = field.addLabel ? resolveLocalized(field.addLabel, locale) : "+ Add item";
|
|
157
|
+
return /* @__PURE__ */ jsxs(
|
|
158
|
+
"div",
|
|
159
|
+
{
|
|
160
|
+
id,
|
|
161
|
+
"aria-invalid": invalid,
|
|
162
|
+
className: [
|
|
163
|
+
"space-y-3 rounded-md border p-3",
|
|
164
|
+
invalid ? "border-destructive" : "border-border"
|
|
165
|
+
].join(" "),
|
|
166
|
+
children: [
|
|
167
|
+
items.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No items yet." }),
|
|
168
|
+
items.map((item, itemIdx) => {
|
|
169
|
+
const label = itemLabel(field, item, itemIdx);
|
|
170
|
+
return /* @__PURE__ */ jsxs(
|
|
171
|
+
"div",
|
|
172
|
+
{
|
|
173
|
+
className: "space-y-2 rounded-md border border-border bg-muted/30 p-3",
|
|
174
|
+
children: [
|
|
175
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
176
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: label }),
|
|
177
|
+
/* @__PURE__ */ jsx(
|
|
178
|
+
"button",
|
|
179
|
+
{
|
|
180
|
+
type: "button",
|
|
181
|
+
className: "text-xs text-muted-foreground underline-offset-2 hover:text-destructive hover:underline",
|
|
182
|
+
onClick: () => removeItem(itemIdx),
|
|
183
|
+
"aria-label": `Remove ${label}`,
|
|
184
|
+
children: "\xD7 Remove"
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
] }),
|
|
188
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: field.fields.map((sf) => {
|
|
189
|
+
const cellId = `${id}-${itemIdx}-${sf.key}`;
|
|
190
|
+
const formString = subFieldValueToFormString(sf, item[sf.key]);
|
|
191
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
192
|
+
/* @__PURE__ */ jsxs(Label, { htmlFor: cellId, className: "text-xs", children: [
|
|
193
|
+
resolveLocalized(sf.label, locale),
|
|
194
|
+
sf.required && /* @__PURE__ */ jsx("span", { className: "ml-1 text-destructive", children: "*" })
|
|
195
|
+
] }),
|
|
196
|
+
renderScalarInput(
|
|
197
|
+
sf,
|
|
198
|
+
cellId,
|
|
199
|
+
formString,
|
|
200
|
+
/* invalid */
|
|
201
|
+
false,
|
|
202
|
+
(s) => updateCell(itemIdx, sf.key, formStringToSubFieldValue(sf, s))
|
|
203
|
+
)
|
|
204
|
+
] }, sf.key);
|
|
205
|
+
}) })
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
itemIdx
|
|
209
|
+
);
|
|
210
|
+
}),
|
|
211
|
+
/* @__PURE__ */ jsx(
|
|
212
|
+
"button",
|
|
213
|
+
{
|
|
214
|
+
type: "button",
|
|
215
|
+
className: "text-xs text-muted-foreground underline-offset-2 hover:underline disabled:cursor-not-allowed disabled:opacity-50",
|
|
216
|
+
onClick: addItem,
|
|
217
|
+
disabled: !canAddItem(field, items.length),
|
|
218
|
+
children: addLabelText
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// src/components/plugin-settings-form.tsx
|
|
227
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
228
|
+
var CACHE_REBUILD_DELAY_MS = 8e3;
|
|
229
|
+
function stringify(field, raw) {
|
|
230
|
+
if (raw === void 0 || raw === null) return "";
|
|
231
|
+
switch (field.type) {
|
|
232
|
+
case "boolean":
|
|
233
|
+
return raw === true ? "true" : raw === false ? "false" : String(raw);
|
|
234
|
+
case "json":
|
|
235
|
+
if (typeof raw === "string") return raw;
|
|
236
|
+
try {
|
|
237
|
+
return JSON.stringify(raw, null, 2);
|
|
238
|
+
} catch {
|
|
239
|
+
return "";
|
|
240
|
+
}
|
|
241
|
+
case "repeatable":
|
|
242
|
+
if (typeof raw === "string") return raw;
|
|
243
|
+
try {
|
|
244
|
+
return JSON.stringify(raw);
|
|
245
|
+
} catch {
|
|
246
|
+
return "[]";
|
|
247
|
+
}
|
|
248
|
+
default:
|
|
249
|
+
return typeof raw === "string" ? raw : String(raw);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function parse(field, raw) {
|
|
253
|
+
switch (field.type) {
|
|
254
|
+
case "boolean":
|
|
255
|
+
if (raw === "true") return true;
|
|
256
|
+
if (raw === "false") return false;
|
|
257
|
+
return null;
|
|
258
|
+
case "number": {
|
|
259
|
+
const trimmed = raw.trim();
|
|
260
|
+
if (trimmed === "") return null;
|
|
261
|
+
const n = Number(trimmed);
|
|
262
|
+
return Number.isNaN(n) ? null : n;
|
|
263
|
+
}
|
|
264
|
+
case "json": {
|
|
265
|
+
const trimmed = raw.trim();
|
|
266
|
+
if (trimmed === "") return null;
|
|
267
|
+
try {
|
|
268
|
+
return JSON.parse(trimmed);
|
|
269
|
+
} catch {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
case "repeatable": {
|
|
274
|
+
const trimmed = raw.trim();
|
|
275
|
+
if (trimmed === "") return [];
|
|
276
|
+
try {
|
|
277
|
+
const parsed = JSON.parse(trimmed);
|
|
278
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
default:
|
|
284
|
+
return raw;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function PluginSettingsForm({
|
|
288
|
+
instanceId,
|
|
289
|
+
displayName,
|
|
290
|
+
fields,
|
|
291
|
+
initialValues
|
|
292
|
+
}) {
|
|
293
|
+
const t = useT();
|
|
294
|
+
const locale = useLocale();
|
|
295
|
+
function defaultDisplay(field) {
|
|
296
|
+
return field.default !== void 0 ? stringify(field, field.default) : "";
|
|
297
|
+
}
|
|
298
|
+
const [state, setState] = useState2(() => {
|
|
299
|
+
const values = {};
|
|
300
|
+
for (const field of fields) {
|
|
301
|
+
const has = Object.prototype.hasOwnProperty.call(initialValues, field.key);
|
|
302
|
+
values[field.key] = has ? stringify(field, initialValues[field.key]) : defaultDisplay(field);
|
|
303
|
+
}
|
|
304
|
+
return { values, touched: {}, invalid: {} };
|
|
305
|
+
});
|
|
306
|
+
const [storedKeys, setStoredKeys] = useState2(
|
|
307
|
+
() => new Set(Object.keys(initialValues))
|
|
308
|
+
);
|
|
309
|
+
const [saving, setSaving] = useState2(false);
|
|
310
|
+
const [error, setError] = useState2(null);
|
|
311
|
+
const [info, setInfo] = useState2(null);
|
|
312
|
+
function update(key, value) {
|
|
313
|
+
setState((prev) => ({
|
|
314
|
+
values: { ...prev.values, [key]: value },
|
|
315
|
+
touched: { ...prev.touched, [key]: true },
|
|
316
|
+
invalid: { ...prev.invalid, [key]: false }
|
|
317
|
+
}));
|
|
318
|
+
setInfo(null);
|
|
319
|
+
}
|
|
320
|
+
function scheduleCacheInvalidation() {
|
|
321
|
+
setTimeout(async () => {
|
|
322
|
+
try {
|
|
323
|
+
await invalidateSiteSettingsCache();
|
|
324
|
+
} catch (err) {
|
|
325
|
+
console.warn("[plugin] cache invalidation failed", err);
|
|
326
|
+
}
|
|
327
|
+
}, CACHE_REBUILD_DELAY_MS);
|
|
328
|
+
}
|
|
329
|
+
async function reset(field) {
|
|
330
|
+
setError(null);
|
|
331
|
+
setInfo(null);
|
|
332
|
+
try {
|
|
333
|
+
await deletePluginPublicSetting(instanceId, field);
|
|
334
|
+
setState((prev) => ({
|
|
335
|
+
values: { ...prev.values, [field.key]: defaultDisplay(field) },
|
|
336
|
+
touched: { ...prev.touched, [field.key]: false },
|
|
337
|
+
invalid: { ...prev.invalid, [field.key]: false }
|
|
338
|
+
}));
|
|
339
|
+
setStoredKeys((prev) => {
|
|
340
|
+
if (!prev.has(field.key)) return prev;
|
|
341
|
+
const next = new Set(prev);
|
|
342
|
+
next.delete(field.key);
|
|
343
|
+
return next;
|
|
344
|
+
});
|
|
345
|
+
setInfo(t("plugins.resetDone"));
|
|
346
|
+
scheduleCacheInvalidation();
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.error("[plugin] reset failed", err);
|
|
349
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async function save(e) {
|
|
353
|
+
e.preventDefault();
|
|
354
|
+
setSaving(true);
|
|
355
|
+
setError(null);
|
|
356
|
+
setInfo(null);
|
|
357
|
+
const newInvalid = {};
|
|
358
|
+
const writes = [];
|
|
359
|
+
const writtenKeys = [];
|
|
360
|
+
for (const field of fields) {
|
|
361
|
+
if (!state.touched[field.key]) continue;
|
|
362
|
+
const raw = state.values[field.key] ?? "";
|
|
363
|
+
const parsed = parse(field, raw);
|
|
364
|
+
if (parsed === null && raw !== "") {
|
|
365
|
+
newInvalid[field.key] = true;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (parsed === null) continue;
|
|
369
|
+
writes.push(setPluginPublicSetting(instanceId, field, parsed));
|
|
370
|
+
writtenKeys.push(field.key);
|
|
371
|
+
}
|
|
372
|
+
if (Object.keys(newInvalid).length > 0) {
|
|
373
|
+
setState((prev) => ({ ...prev, invalid: newInvalid }));
|
|
374
|
+
setSaving(false);
|
|
375
|
+
setError(t("plugins.invalidValue"));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
await Promise.all(writes);
|
|
380
|
+
setInfo(t("plugins.saved"));
|
|
381
|
+
setState((prev) => ({ ...prev, touched: {}, invalid: {} }));
|
|
382
|
+
if (writtenKeys.length > 0) {
|
|
383
|
+
setStoredKeys((prev) => {
|
|
384
|
+
const next = new Set(prev);
|
|
385
|
+
for (const k of writtenKeys) next.add(k);
|
|
386
|
+
return next;
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
scheduleCacheInvalidation();
|
|
390
|
+
} catch (err) {
|
|
391
|
+
console.error("[plugin] save failed", err);
|
|
392
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
393
|
+
} finally {
|
|
394
|
+
setSaving(false);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return /* @__PURE__ */ jsxs2("form", { onSubmit: save, className: "max-w-2xl space-y-5 rounded-md border p-4", children: [
|
|
398
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
399
|
+
/* @__PURE__ */ jsx2("h2", { className: "text-base font-semibold", children: displayName ? resolveLocalized2(displayName, locale) : instanceId }),
|
|
400
|
+
/* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground", children: instanceId })
|
|
401
|
+
] }),
|
|
402
|
+
fields.map((field) => /* @__PURE__ */ jsx2(
|
|
403
|
+
FieldRow,
|
|
404
|
+
{
|
|
405
|
+
field,
|
|
406
|
+
value: state.values[field.key] ?? "",
|
|
407
|
+
invalid: !!state.invalid[field.key],
|
|
408
|
+
onChange: (v) => update(field.key, v),
|
|
409
|
+
onReset: () => void reset(field),
|
|
410
|
+
hasStoredValue: storedKeys.has(field.key)
|
|
411
|
+
},
|
|
412
|
+
field.key
|
|
413
|
+
)),
|
|
414
|
+
info && /* @__PURE__ */ jsx2("p", { className: "text-sm text-muted-foreground", children: info }),
|
|
415
|
+
error && /* @__PURE__ */ jsx2("p", { className: "text-sm text-destructive", children: error }),
|
|
416
|
+
/* @__PURE__ */ jsx2(Button, { type: "submit", disabled: saving, children: saving ? t("plugins.saving") : t("plugins.save") })
|
|
417
|
+
] });
|
|
418
|
+
}
|
|
419
|
+
function FieldRow({ field, value, invalid, onChange, onReset, hasStoredValue }) {
|
|
420
|
+
const t = useT();
|
|
421
|
+
const locale = useLocale();
|
|
422
|
+
const id = `plugin-${field.key}`;
|
|
423
|
+
const labelEl = /* @__PURE__ */ jsxs2(Label2, { htmlFor: id, className: invalid ? "text-destructive" : void 0, children: [
|
|
424
|
+
resolveLocalized2(field.label, locale),
|
|
425
|
+
field.required && /* @__PURE__ */ jsx2("span", { className: "ml-1 text-destructive", children: "*" })
|
|
426
|
+
] });
|
|
427
|
+
const description = field.description ? /* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground", children: resolveLocalized2(field.description, locale) }) : null;
|
|
428
|
+
const input = renderInput(field, id, value, invalid, onChange);
|
|
429
|
+
const placeholder = renderDefaultHint(field, locale);
|
|
430
|
+
return /* @__PURE__ */ jsxs2("div", { className: "space-y-1.5", children: [
|
|
431
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
|
|
432
|
+
labelEl,
|
|
433
|
+
hasStoredValue && /* @__PURE__ */ jsx2(
|
|
434
|
+
"button",
|
|
435
|
+
{
|
|
436
|
+
type: "button",
|
|
437
|
+
className: "text-xs text-muted-foreground underline-offset-2 hover:underline",
|
|
438
|
+
onClick: onReset,
|
|
439
|
+
children: t("plugins.resetToDefault")
|
|
440
|
+
}
|
|
441
|
+
)
|
|
442
|
+
] }),
|
|
443
|
+
description,
|
|
444
|
+
input,
|
|
445
|
+
placeholder
|
|
446
|
+
] });
|
|
447
|
+
}
|
|
448
|
+
function renderDefaultHint(field, _locale) {
|
|
449
|
+
if (field.default === void 0) return null;
|
|
450
|
+
let preview;
|
|
451
|
+
if (typeof field.default === "string") preview = field.default;
|
|
452
|
+
else if (typeof field.default === "boolean" || typeof field.default === "number") {
|
|
453
|
+
preview = String(field.default);
|
|
454
|
+
} else {
|
|
455
|
+
try {
|
|
456
|
+
preview = JSON.stringify(field.default);
|
|
457
|
+
} catch {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (!preview) return null;
|
|
462
|
+
return /* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground", children: /* @__PURE__ */ jsx2("code", { children: preview }) });
|
|
463
|
+
}
|
|
464
|
+
function renderScalarInput(field, id, value, invalid, onChange) {
|
|
465
|
+
switch (field.type) {
|
|
466
|
+
case "text":
|
|
467
|
+
case "url":
|
|
468
|
+
return /* @__PURE__ */ jsx2(
|
|
469
|
+
Input,
|
|
470
|
+
{
|
|
471
|
+
id,
|
|
472
|
+
value,
|
|
473
|
+
maxLength: field.type === "text" ? field.maxLength : void 0,
|
|
474
|
+
placeholder: "placeholder" in field ? field.placeholder : void 0,
|
|
475
|
+
onChange: (e) => onChange(e.target.value),
|
|
476
|
+
"aria-invalid": invalid,
|
|
477
|
+
type: field.type === "url" ? "url" : "text"
|
|
478
|
+
}
|
|
479
|
+
);
|
|
480
|
+
case "textarea":
|
|
481
|
+
return /* @__PURE__ */ jsx2(
|
|
482
|
+
Textarea,
|
|
483
|
+
{
|
|
484
|
+
id,
|
|
485
|
+
value,
|
|
486
|
+
rows: field.rows ?? 4,
|
|
487
|
+
maxLength: field.maxLength,
|
|
488
|
+
placeholder: field.placeholder,
|
|
489
|
+
onChange: (e) => onChange(e.target.value),
|
|
490
|
+
"aria-invalid": invalid
|
|
491
|
+
}
|
|
492
|
+
);
|
|
493
|
+
case "code":
|
|
494
|
+
return /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
|
|
495
|
+
field.language && /* @__PURE__ */ jsx2("p", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: field.language }),
|
|
496
|
+
/* @__PURE__ */ jsx2(
|
|
497
|
+
Textarea,
|
|
498
|
+
{
|
|
499
|
+
id,
|
|
500
|
+
value,
|
|
501
|
+
rows: field.rows ?? 8,
|
|
502
|
+
maxLength: field.maxLength,
|
|
503
|
+
placeholder: field.placeholder,
|
|
504
|
+
onChange: (e) => onChange(e.target.value),
|
|
505
|
+
"aria-invalid": invalid,
|
|
506
|
+
className: "font-mono text-xs"
|
|
507
|
+
}
|
|
508
|
+
)
|
|
509
|
+
] });
|
|
510
|
+
case "boolean":
|
|
511
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
|
|
512
|
+
/* @__PURE__ */ jsx2(
|
|
513
|
+
"input",
|
|
514
|
+
{
|
|
515
|
+
id,
|
|
516
|
+
type: "checkbox",
|
|
517
|
+
checked: value === "true",
|
|
518
|
+
onChange: (e) => onChange(e.target.checked ? "true" : "false"),
|
|
519
|
+
className: "h-4 w-4"
|
|
520
|
+
}
|
|
521
|
+
),
|
|
522
|
+
/* @__PURE__ */ jsx2("span", { className: "text-xs text-muted-foreground", children: value === "true" ? "on" : "off" })
|
|
523
|
+
] });
|
|
524
|
+
case "number":
|
|
525
|
+
return /* @__PURE__ */ jsx2(
|
|
526
|
+
Input,
|
|
527
|
+
{
|
|
528
|
+
id,
|
|
529
|
+
type: "number",
|
|
530
|
+
value,
|
|
531
|
+
min: field.min,
|
|
532
|
+
max: field.max,
|
|
533
|
+
step: field.step,
|
|
534
|
+
onChange: (e) => onChange(e.target.value),
|
|
535
|
+
"aria-invalid": invalid
|
|
536
|
+
}
|
|
537
|
+
);
|
|
538
|
+
case "select":
|
|
539
|
+
return /* @__PURE__ */ jsxs2(
|
|
540
|
+
"select",
|
|
541
|
+
{
|
|
542
|
+
id,
|
|
543
|
+
className: "w-full rounded-md border bg-background px-2 py-1.5 text-sm",
|
|
544
|
+
value,
|
|
545
|
+
onChange: (e) => onChange(e.target.value),
|
|
546
|
+
"aria-invalid": invalid,
|
|
547
|
+
children: [
|
|
548
|
+
/* @__PURE__ */ jsx2("option", { value: "", children: "\u2014" }),
|
|
549
|
+
field.options.map((opt) => /* @__PURE__ */ jsx2("option", { value: opt.value, children: typeof opt.label === "string" ? opt.label : opt.value }, opt.value))
|
|
550
|
+
]
|
|
551
|
+
}
|
|
552
|
+
);
|
|
553
|
+
case "json":
|
|
554
|
+
return /* @__PURE__ */ jsx2(
|
|
555
|
+
Textarea,
|
|
556
|
+
{
|
|
557
|
+
id,
|
|
558
|
+
value,
|
|
559
|
+
rows: field.rows ?? 8,
|
|
560
|
+
placeholder: field.placeholder ?? "{}",
|
|
561
|
+
onChange: (e) => onChange(e.target.value),
|
|
562
|
+
"aria-invalid": invalid,
|
|
563
|
+
className: "font-mono text-xs"
|
|
564
|
+
}
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function renderInput(field, id, value, invalid, onChange) {
|
|
569
|
+
switch (field.type) {
|
|
570
|
+
case "repeatable":
|
|
571
|
+
return /* @__PURE__ */ jsx2(
|
|
572
|
+
RepeatableFieldEditor,
|
|
573
|
+
{
|
|
574
|
+
field,
|
|
575
|
+
id,
|
|
576
|
+
value,
|
|
577
|
+
invalid,
|
|
578
|
+
onChange
|
|
579
|
+
}
|
|
580
|
+
);
|
|
581
|
+
default:
|
|
582
|
+
return renderScalarInput(field, id, value, invalid, onChange);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export {
|
|
587
|
+
PluginSettingsForm,
|
|
588
|
+
renderScalarInput
|
|
589
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { LocalizedString, PluginSettingField } from 'ampless';
|
|
2
|
+
import { LocalizedString, PluginSettingField, PluginRepeatableField } from 'ampless';
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
5
|
instanceId: string;
|
|
@@ -13,5 +13,13 @@ interface Props {
|
|
|
13
13
|
initialValues: Record<string, unknown>;
|
|
14
14
|
}
|
|
15
15
|
declare function PluginSettingsForm({ instanceId, displayName, fields, initialValues, }: Props): react_jsx_runtime.JSX.Element;
|
|
16
|
+
/**
|
|
17
|
+
* Render a scalar (non-repeatable) plugin field input. Handles the 8
|
|
18
|
+
* scalar variant types: text, url, textarea, code, boolean, number,
|
|
19
|
+
* select, json. Factored out of `renderInput` so the repeatable case
|
|
20
|
+
* can call back into it per sub-field cell without interleaving with
|
|
21
|
+
* the repeatable branch.
|
|
22
|
+
*/
|
|
23
|
+
declare function renderScalarInput(field: Exclude<PluginSettingField, PluginRepeatableField>, id: string, value: string, invalid: boolean, onChange: (v: string) => void): React.ReactNode;
|
|
16
24
|
|
|
17
|
-
export { PluginSettingsForm };
|
|
25
|
+
export { PluginSettingsForm, renderScalarInput };
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
|
-
PluginSettingsForm
|
|
4
|
-
|
|
3
|
+
PluginSettingsForm,
|
|
4
|
+
renderScalarInput
|
|
5
|
+
} from "../chunk-OPRC3VJQ.js";
|
|
5
6
|
import "../chunk-D72XF3Q3.js";
|
|
6
7
|
import "../chunk-2SVJONQ7.js";
|
|
7
8
|
import "../chunk-KYFSM7MS.js";
|
|
8
9
|
export {
|
|
9
|
-
PluginSettingsForm
|
|
10
|
+
PluginSettingsForm,
|
|
11
|
+
renderScalarInput
|
|
10
12
|
};
|