@vertebr/react 0.1.0 → 0.2.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/dist/{VertebrAdmin.d.ts → components/admin/VertebrAdmin.d.ts} +3 -1
- package/dist/components/admin/VertebrAdmin.d.ts.map +1 -0
- package/dist/{VertebrAdmin.js → components/admin/VertebrAdmin.js} +117 -33
- package/dist/components/config/ConfigForm.d.ts +19 -0
- package/dist/components/config/ConfigForm.d.ts.map +1 -0
- package/dist/components/config/ConfigForm.js +281 -0
- package/dist/{EnvSelect.d.ts → components/env/EnvSelect.d.ts} +1 -1
- package/dist/components/env/EnvSelect.d.ts.map +1 -0
- package/dist/components/gallery/GalleryBoard.d.ts +14 -0
- package/dist/components/gallery/GalleryBoard.d.ts.map +1 -0
- package/dist/components/gallery/GalleryBoard.js +14 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +6 -0
- package/dist/{MetricsBoard.d.ts → components/metrics/MetricsBoard.d.ts} +1 -1
- package/dist/components/metrics/MetricsBoard.d.ts.map +1 -0
- package/dist/components/resources/BulkImageUploadModal.d.ts +17 -0
- package/dist/components/resources/BulkImageUploadModal.d.ts.map +1 -0
- package/dist/components/resources/BulkImageUploadModal.js +47 -0
- package/dist/components/resources/ColumnVisibilityMenu.d.ts +11 -0
- package/dist/components/resources/ColumnVisibilityMenu.d.ts.map +1 -0
- package/dist/components/resources/ColumnVisibilityMenu.js +40 -0
- package/dist/components/resources/ImageUploadControl.d.ts +10 -0
- package/dist/components/resources/ImageUploadControl.d.ts.map +1 -0
- package/dist/components/resources/ImageUploadControl.js +50 -0
- package/dist/components/resources/ResourceCell.d.ts +10 -0
- package/dist/components/resources/ResourceCell.d.ts.map +1 -0
- package/dist/components/resources/ResourceCell.js +114 -0
- package/dist/{ResourceTable.d.ts → components/resources/ResourceTable.d.ts} +10 -2
- package/dist/components/resources/ResourceTable.d.ts.map +1 -0
- package/dist/components/resources/ResourceTable.js +200 -0
- package/dist/components/resources/ResourceViewModal.d.ts +11 -0
- package/dist/components/resources/ResourceViewModal.d.ts.map +1 -0
- package/dist/components/resources/ResourceViewModal.js +44 -0
- package/dist/components/theme/ThemeToggle.d.ts +7 -0
- package/dist/components/theme/ThemeToggle.d.ts.map +1 -0
- package/dist/components/theme/ThemeToggle.js +10 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/{useVertebrControls.d.ts → hooks/useVertebrControls.d.ts} +2 -2
- package/dist/hooks/useVertebrControls.d.ts.map +1 -0
- package/dist/{useVertebrControls.js → hooks/useVertebrControls.js} +7 -18
- package/dist/hooks/useVertebrTheme.d.ts +7 -0
- package/dist/hooks/useVertebrTheme.d.ts.map +1 -0
- package/dist/hooks/useVertebrTheme.js +38 -0
- package/dist/index.d.ts +15 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -6
- package/dist/styles/auth.css +39 -0
- package/dist/styles/base.css +70 -0
- package/dist/styles/buttons.css +86 -0
- package/dist/styles/cells.css +178 -0
- package/dist/styles/columns.css +103 -0
- package/dist/styles/form.css +498 -0
- package/dist/styles/gallery.css +90 -0
- package/dist/styles/index.css +18 -0
- package/dist/styles/main.css +89 -0
- package/dist/styles/modal.css +110 -0
- package/dist/styles/nav.css +92 -0
- package/dist/styles/panel.css +204 -0
- package/dist/styles/select.css +151 -0
- package/dist/styles/sidebar.css +42 -0
- package/dist/styles/states.css +169 -0
- package/dist/styles/table.css +92 -0
- package/dist/styles/theme-toggle.css +87 -0
- package/dist/styles.css +2 -997
- package/dist/themes/dark.css +55 -0
- package/dist/themes/index.css +2 -0
- package/dist/themes/light.css +102 -0
- package/package.json +8 -5
- package/dist/ConfigForm.d.ts +0 -9
- package/dist/ConfigForm.d.ts.map +0 -1
- package/dist/ConfigForm.js +0 -87
- package/dist/EnvSelect.d.ts.map +0 -1
- package/dist/MetricsBoard.d.ts.map +0 -1
- package/dist/ResourceTable.d.ts.map +0 -1
- package/dist/ResourceTable.js +0 -112
- package/dist/VertebrAdmin.d.ts.map +0 -1
- package/dist/client.d.ts +0 -32
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -65
- package/dist/types.d.ts +0 -103
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
- package/dist/useVertebrControls.d.ts.map +0 -1
- /package/dist/{EnvSelect.js → components/env/EnvSelect.js} +0 -0
- /package/dist/{MetricsBoard.js → components/metrics/MetricsBoard.js} +0 -0
|
@@ -6,6 +6,8 @@ export type VertebrAdminProps = {
|
|
|
6
6
|
envStorageKey?: string;
|
|
7
7
|
/** localStorage key for the access token */
|
|
8
8
|
tokenStorageKey?: string;
|
|
9
|
+
/** localStorage key for light/dark theme preference */
|
|
10
|
+
themeStorageKey?: string;
|
|
9
11
|
/** Optional host-provided token getter (Firebase ID token, etc.) */
|
|
10
12
|
getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;
|
|
11
13
|
/** Signed-in identity shown in the dashboard. */
|
|
@@ -13,5 +15,5 @@ export type VertebrAdminProps = {
|
|
|
13
15
|
/** Host-provided sign-out action. */
|
|
14
16
|
onSignOut?: () => void | Promise<void>;
|
|
15
17
|
};
|
|
16
|
-
export declare function VertebrAdmin({ apiBase, title, envStorageKey, tokenStorageKey, getAccessToken, authUserLabel, onSignOut, }: VertebrAdminProps): import("react").JSX.Element;
|
|
18
|
+
export declare function VertebrAdmin({ apiBase, title, envStorageKey, tokenStorageKey, themeStorageKey, getAccessToken, authUserLabel, onSignOut, }: VertebrAdminProps): import("react").JSX.Element;
|
|
17
19
|
//# sourceMappingURL=VertebrAdmin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VertebrAdmin.d.ts","sourceRoot":"","sources":["../../../src/components/admin/VertebrAdmin.tsx"],"names":[],"mappings":"AAkBA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtF,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAiCF,wBAAgB,YAAY,CAAC,EAC3B,OAAsB,EACtB,KAAuB,EACvB,aAA6B,EAC7B,eAAiC,EACjC,eAAiC,EACjC,cAAc,EACd,aAAa,EACb,SAAS,GACV,EAAE,iBAAiB,+BAolBnB"}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
-
import { createVertebrClient } from "
|
|
4
|
-
import { ConfigForm } from "
|
|
5
|
-
import { EnvSelect } from "
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
3
|
+
import { createVertebrClient, } from "@vertebr/client";
|
|
4
|
+
import { ConfigForm } from "../config/ConfigForm";
|
|
5
|
+
import { EnvSelect } from "../env/EnvSelect";
|
|
6
|
+
import { GalleryBoard } from "../gallery/GalleryBoard";
|
|
7
|
+
import { MetricsBoard } from "../metrics/MetricsBoard";
|
|
8
|
+
import { ResourcePanel } from "../resources/ResourceTable";
|
|
9
|
+
import { ThemeToggle } from "../theme/ThemeToggle";
|
|
10
|
+
import { useVertebrTheme } from "../../hooks/useVertebrTheme";
|
|
8
11
|
function readStored(key) {
|
|
9
12
|
try {
|
|
10
13
|
return localStorage.getItem(key);
|
|
@@ -29,7 +32,8 @@ function clearStored(key) {
|
|
|
29
32
|
// ignore
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
|
-
export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane", envStorageKey = "vertebr:env", tokenStorageKey = "vertebr:token", getAccessToken, authUserLabel, onSignOut, }) {
|
|
35
|
+
export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane", envStorageKey = "vertebr:env", tokenStorageKey = "vertebr:token", themeStorageKey = "vertebr:theme", getAccessToken, authUserLabel, onSignOut, }) {
|
|
36
|
+
const { theme, setTheme } = useVertebrTheme(themeStorageKey);
|
|
33
37
|
const [environment, setEnvironment] = useState(() => readStored(envStorageKey) ?? "");
|
|
34
38
|
const [token, setToken] = useState(() => readStored(tokenStorageKey) ?? "");
|
|
35
39
|
const [tokenDraft, setTokenDraft] = useState(token);
|
|
@@ -46,6 +50,8 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
46
50
|
const [configData, setConfigData] = useState(null);
|
|
47
51
|
const [metricsData, setMetricsData] = useState(null);
|
|
48
52
|
const [metricsLoading, setMetricsLoading] = useState(false);
|
|
53
|
+
const [galleryData, setGalleryData] = useState(null);
|
|
54
|
+
const [galleryLoading, setGalleryLoading] = useState(false);
|
|
49
55
|
const [error, setError] = useState(null);
|
|
50
56
|
const [loading, setLoading] = useState(true);
|
|
51
57
|
useEffect(() => {
|
|
@@ -89,6 +95,8 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
89
95
|
setSelection((current) => {
|
|
90
96
|
if (current?.kind === "overview")
|
|
91
97
|
return current;
|
|
98
|
+
if (current?.kind === "gallery" && next.gallery)
|
|
99
|
+
return current;
|
|
92
100
|
if (current?.kind === "resource") {
|
|
93
101
|
if (next.resources.some((r) => r.id === current.id))
|
|
94
102
|
return current;
|
|
@@ -102,6 +110,8 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
102
110
|
const firstResource = next.resources[0];
|
|
103
111
|
if (firstResource)
|
|
104
112
|
return { kind: "resource", id: firstResource.id };
|
|
113
|
+
if (next.gallery)
|
|
114
|
+
return { kind: "gallery" };
|
|
105
115
|
const firstConfig = next.configs[0];
|
|
106
116
|
if (firstConfig)
|
|
107
117
|
return { kind: "config", id: firstConfig.id };
|
|
@@ -137,6 +147,17 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
137
147
|
setMetricsData(data);
|
|
138
148
|
setResourceData(null);
|
|
139
149
|
setConfigData(null);
|
|
150
|
+
setGalleryData(null);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else if (selection.kind === "gallery") {
|
|
154
|
+
setGalleryLoading(true);
|
|
155
|
+
const data = await client.getGallery();
|
|
156
|
+
if (!cancelled) {
|
|
157
|
+
setGalleryData(data);
|
|
158
|
+
setResourceData(null);
|
|
159
|
+
setConfigData(null);
|
|
160
|
+
setMetricsData(null);
|
|
140
161
|
}
|
|
141
162
|
}
|
|
142
163
|
else if (selection.kind === "resource") {
|
|
@@ -145,6 +166,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
145
166
|
setResourceData(data);
|
|
146
167
|
setConfigData(null);
|
|
147
168
|
setMetricsData(null);
|
|
169
|
+
setGalleryData(null);
|
|
148
170
|
}
|
|
149
171
|
}
|
|
150
172
|
else {
|
|
@@ -153,6 +175,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
153
175
|
setConfigData(data);
|
|
154
176
|
setResourceData(null);
|
|
155
177
|
setMetricsData(null);
|
|
178
|
+
setGalleryData(null);
|
|
156
179
|
}
|
|
157
180
|
}
|
|
158
181
|
}
|
|
@@ -162,8 +185,10 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
162
185
|
}
|
|
163
186
|
}
|
|
164
187
|
finally {
|
|
165
|
-
if (!cancelled)
|
|
188
|
+
if (!cancelled) {
|
|
166
189
|
setMetricsLoading(false);
|
|
190
|
+
setGalleryLoading(false);
|
|
191
|
+
}
|
|
167
192
|
}
|
|
168
193
|
})();
|
|
169
194
|
return () => {
|
|
@@ -186,9 +211,23 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
186
211
|
setMetricsLoading(false);
|
|
187
212
|
}
|
|
188
213
|
}, [client]);
|
|
214
|
+
const refreshGallery = useCallback(async () => {
|
|
215
|
+
setGalleryLoading(true);
|
|
216
|
+
try {
|
|
217
|
+
const data = await client.getGallery();
|
|
218
|
+
setGalleryData(data);
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
setGalleryLoading(false);
|
|
222
|
+
}
|
|
223
|
+
}, [client]);
|
|
189
224
|
const envList = catalog?.environments ?? bootstrap?.environments ?? [];
|
|
190
225
|
const activeEnv = envList.find((env) => env.id === environment);
|
|
191
226
|
const authRequired = Boolean(activeEnv?.auth?.required);
|
|
227
|
+
const activeConfig = selection?.kind === "config"
|
|
228
|
+
? catalog?.configs.find((c) => c.id === selection.id)
|
|
229
|
+
: undefined;
|
|
230
|
+
const isPageSelection = activeConfig?.section === "page";
|
|
192
231
|
const switchEnvironment = (nextEnv) => {
|
|
193
232
|
if (nextEnv === environment)
|
|
194
233
|
return;
|
|
@@ -202,6 +241,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
202
241
|
setEnvironment(nextEnv);
|
|
203
242
|
setResourceData(null);
|
|
204
243
|
setConfigData(null);
|
|
244
|
+
setGalleryData(null);
|
|
205
245
|
};
|
|
206
246
|
const saveToken = () => {
|
|
207
247
|
const next = tokenDraft.trim();
|
|
@@ -224,38 +264,82 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
224
264
|
}
|
|
225
265
|
const activeLabel = selection?.kind === "overview"
|
|
226
266
|
? (catalog?.metrics?.label ?? "Overview")
|
|
227
|
-
: selection?.kind === "
|
|
228
|
-
? catalog?.
|
|
229
|
-
: selection?.kind === "
|
|
230
|
-
? catalog?.
|
|
231
|
-
:
|
|
232
|
-
|
|
233
|
-
|
|
267
|
+
: selection?.kind === "gallery"
|
|
268
|
+
? (catalog?.gallery?.label ?? "Gallery")
|
|
269
|
+
: selection?.kind === "resource"
|
|
270
|
+
? catalog?.resources.find((r) => r.id === selection.id)?.label
|
|
271
|
+
: selection?.kind === "config"
|
|
272
|
+
? catalog?.configs.find((c) => c.id === selection.id)?.label
|
|
273
|
+
: null;
|
|
274
|
+
return (_jsxs("div", { className: "vertebr-root", "data-env": environment, children: [_jsxs("aside", { className: "vertebr-sidebar", children: [_jsxs("div", { className: "vertebr-sidebar-top", children: [_jsxs("div", { className: "vertebr-brand", children: [_jsx("strong", { children: bootstrap?.brand ?? catalog?.brand ?? "Vertebr" }), _jsx("span", { children: title })] }), envList.length > 0 ? (_jsxs("div", { className: "vertebr-env", children: [_jsx("div", { className: "vertebr-nav-label", children: "Environment" }), _jsx(EnvSelect, { environments: envList, value: environment, onChange: switchEnvironment })] })) : null, _jsxs("nav", { className: "vertebr-nav", children: [catalog?.metrics ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Analytics" }), _jsx("button", { type: "button", "data-active": selection?.kind === "overview", onClick: () => setSelection({ kind: "overview" }), children: catalog.metrics.label })] })) : null, catalog &&
|
|
275
|
+
(catalog.resources.length > 0 || catalog.gallery) ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Resources" }), catalog.resources.map((resource) => (_jsx("button", { type: "button", "data-active": selection?.kind === "resource" &&
|
|
276
|
+
selection.id === resource.id, onClick: () => setSelection({ kind: "resource", id: resource.id }), children: resource.label }, resource.id))), catalog.gallery ? (_jsx("button", { type: "button", "data-active": selection?.kind === "gallery", onClick: () => setSelection({ kind: "gallery" }), children: catalog.gallery.label })) : null] })) : null, catalog &&
|
|
277
|
+
catalog.configs.some((c) => (c.section ?? "config") !== "page") ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Config" }), catalog.configs
|
|
278
|
+
.filter((c) => (c.section ?? "config") !== "page")
|
|
279
|
+
.map((config) => (_jsx("button", { type: "button", "data-active": selection?.kind === "config" &&
|
|
280
|
+
selection.id === config.id, onClick: () => setSelection({ kind: "config", id: config.id }), children: config.label }, config.id)))] })) : null, catalog &&
|
|
281
|
+
catalog.configs.some((c) => c.section === "page") ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Pages" }), catalog.configs
|
|
282
|
+
.filter((c) => c.section === "page")
|
|
283
|
+
.map((config) => (_jsx("button", { type: "button", "data-active": selection?.kind === "config" &&
|
|
284
|
+
selection.id === config.id, onClick: () => setSelection({ kind: "config", id: config.id }), children: config.label }, config.id)))] })) : null] })] }), _jsxs("div", { className: "vertebr-sidebar-bottom", children: [getAccessToken && authUserLabel ? (_jsxs("div", { className: "vertebr-auth", children: [_jsx("div", { className: "vertebr-nav-label", children: "Signed in" }), _jsx("p", { className: "vertebr-auth-hint", children: authUserLabel }), onSignOut ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: () => void onSignOut(), children: "Sign out" })) : null] })) : null, !getAccessToken && (authRequired || token) ? (_jsxs("div", { className: "vertebr-auth", children: [_jsx("div", { className: "vertebr-nav-label", children: "Access" }), _jsx("p", { className: "vertebr-auth-hint", children: authRequired
|
|
234
285
|
? `This environment requires auth${activeEnv?.auth?.claimKey
|
|
235
286
|
? ` (${activeEnv.auth.claimKey})`
|
|
236
287
|
: ""}.`
|
|
237
|
-
: "Optional access token for protected environments." }), _jsx("input", { className: "vertebr-auth-input", type: "password", placeholder: "Bearer token", value: tokenDraft, onChange: (event) => setTokenDraft(event.target.value), autoComplete: "off" }), _jsxs("div", { className: "vertebr-auth-actions", children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-primary", onClick: saveToken, children: "Use token" }), token ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: clearToken, children: "Clear" })) : null] })] })) : null] })] }), _jsxs("main", { className: "vertebr-main", children: [_jsxs("header", { className: "vertebr-header", children: [_jsxs("div", { children: [_jsxs("div", { className: "vertebr-heading-row", children: [_jsx("h1", { children: activeLabel ?? (catalog ? "Dashboard" : "Sign in") }), activeEnv ? (_jsx("span", { className: "vertebr-env-badge", "data-tone": activeEnv.tone ?? "neutral", children: activeEnv.label })) : null] }), _jsx("p", { children: !catalog && authRequired
|
|
288
|
+
: "Optional access token for protected environments." }), _jsx("input", { className: "vertebr-auth-input", type: "password", placeholder: "Bearer token", value: tokenDraft, onChange: (event) => setTokenDraft(event.target.value), autoComplete: "off" }), _jsxs("div", { className: "vertebr-auth-actions", children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-primary", onClick: saveToken, children: "Use token" }), token ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: clearToken, children: "Clear" })) : null] })] })) : null, _jsx(ThemeToggle, { theme: theme, onChange: setTheme })] })] }), _jsxs("main", { className: "vertebr-main", children: [_jsxs("header", { className: "vertebr-header", children: [_jsxs("div", { children: [_jsxs("div", { className: "vertebr-heading-row", children: [_jsx("h1", { children: activeLabel ?? (catalog ? "Dashboard" : "Sign in") }), activeEnv ? (_jsx("span", { className: "vertebr-env-badge", "data-tone": activeEnv.tone ?? "neutral", children: activeEnv.label })) : null] }), _jsx("p", { children: !catalog && authRequired
|
|
238
289
|
? "Provide an access token to load this environment."
|
|
239
290
|
: selection?.kind === "overview"
|
|
240
291
|
? "Live metrics for the selected environment."
|
|
241
|
-
: selection?.kind === "
|
|
242
|
-
? "
|
|
243
|
-
: selection?.kind === "
|
|
244
|
-
? "
|
|
245
|
-
:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
292
|
+
: selection?.kind === "gallery"
|
|
293
|
+
? "Browse and upload shared media assets."
|
|
294
|
+
: selection?.kind === "resource"
|
|
295
|
+
? "Manage records scoped to the selected environment."
|
|
296
|
+
: selection?.kind === "config"
|
|
297
|
+
? isPageSelection
|
|
298
|
+
? "Edit content for this page."
|
|
299
|
+
: "Owner-managed config for this environment."
|
|
300
|
+
: "Select a panel." })] }), error ? (_jsx("span", { className: "vertebr-status", "data-tone": "danger", children: error })) : null] }), _jsx("div", { className: "vertebr-content", children: catalog ? (_jsxs("section", { className: "vertebr-panel", children: [selection?.kind === "overview" ? (_jsx(MetricsBoard, { label: metricsData?.label ?? catalog.metrics?.label, metrics: metricsData?.metrics ?? [], loading: metricsLoading, onRefresh: () => void refreshMetrics() })) : null, selection?.kind === "gallery" && galleryData ? (_jsx(GalleryBoard, { label: galleryData.label, items: galleryData.items, accept: galleryData.accept, maxBytes: galleryData.maxBytes, canDelete: galleryData.canDelete, loading: galleryLoading, onRefresh: () => void refreshGallery(), onUpload: async (files) => {
|
|
301
|
+
if (files.length === 1 && files[0]) {
|
|
302
|
+
await client.uploadGalleryFile(files[0]);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
await client.uploadGalleryFiles(files);
|
|
306
|
+
}
|
|
307
|
+
await refreshGallery();
|
|
308
|
+
}, onDelete: galleryData.canDelete
|
|
309
|
+
? async (id) => {
|
|
310
|
+
await client.deleteGalleryItem(id);
|
|
311
|
+
await refreshGallery();
|
|
312
|
+
}
|
|
313
|
+
: undefined })) : null, selection?.kind === "resource" && resourceData ? (_jsx(ResourcePanel, { schema: resourceData.schema, items: resourceData.items, permissions: resourceData.permissions, actions: resourceData.actions, uploads: resourceData.uploads, storageKey: resourceData.id, onUpdate: resourceData.permissions.update
|
|
314
|
+
? async (rowId, value) => {
|
|
315
|
+
await client.updateResource(resourceData.id, rowId, value);
|
|
316
|
+
await refreshResource();
|
|
317
|
+
}
|
|
318
|
+
: undefined, onCreate: resourceData.permissions.create
|
|
319
|
+
? async (value) => {
|
|
320
|
+
await client.createResource(resourceData.id, value);
|
|
321
|
+
await refreshResource();
|
|
322
|
+
}
|
|
323
|
+
: undefined, onDelete: resourceData.permissions.delete
|
|
324
|
+
? async (rowId) => {
|
|
325
|
+
await client.deleteResource(resourceData.id, rowId);
|
|
326
|
+
await refreshResource();
|
|
327
|
+
}
|
|
328
|
+
: undefined, onAction: async (rowId, actionId) => {
|
|
329
|
+
await client.runResourceAction(resourceData.id, rowId, actionId);
|
|
330
|
+
await refreshResource();
|
|
331
|
+
}, onUpload: resourceData.uploads
|
|
332
|
+
? async (field, files) => {
|
|
333
|
+
if (files.length === 1 && files[0]) {
|
|
334
|
+
const result = await client.uploadResourceFile(resourceData.id, field, files[0]);
|
|
335
|
+
return [
|
|
336
|
+
{ url: result.url, name: result.name },
|
|
337
|
+
];
|
|
338
|
+
}
|
|
339
|
+
const result = await client.uploadResourceFiles(resourceData.id, field, files);
|
|
340
|
+
return result.files;
|
|
341
|
+
}
|
|
342
|
+
: undefined })) : null, selection?.kind === "config" && configData ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: isPageSelection ? "Page content" : "Settings" }), _jsxs("span", { className: "vertebr-count", children: [Object.keys(configData.schema).length, " fields"] })] }), _jsx(ConfigForm, { schema: configData.schema, value: configData.value, onSave: async (next) => {
|
|
259
343
|
const saved = await client.setConfig(configData.id, next);
|
|
260
344
|
setConfigData({
|
|
261
345
|
...configData,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FieldSchema, ResourceUploadMeta } from "@vertebr/client";
|
|
2
|
+
export type ConfigFormVariant = "config" | "record";
|
|
3
|
+
type ConfigFormProps = {
|
|
4
|
+
schema: Record<string, FieldSchema>;
|
|
5
|
+
value: Record<string, unknown>;
|
|
6
|
+
onSave: (value: Record<string, unknown>) => Promise<void>;
|
|
7
|
+
/** `record` uses a dense product-style editor. */
|
|
8
|
+
variant?: ConfigFormVariant;
|
|
9
|
+
/** Field-keyed upload capabilities from the host resource. */
|
|
10
|
+
uploads?: Record<string, ResourceUploadMeta>;
|
|
11
|
+
/** Upload files for a field; return public URL(s). */
|
|
12
|
+
onUpload?: (field: string, files: File[]) => Promise<Array<{
|
|
13
|
+
url: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
}>>;
|
|
16
|
+
};
|
|
17
|
+
export declare function ConfigForm({ schema, value, onSave, variant, uploads, onUpload, }: ConfigFormProps): import("react").JSX.Element;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=ConfigForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigForm.d.ts","sourceRoot":"","sources":["../../../src/components/config/ConfigForm.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAGvE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEpD,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,kDAAkD;IAClD,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,sDAAsD;IACtD,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,IAAI,EAAE,KACV,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CACrD,CAAC;AAqNF,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,KAAK,EACL,MAAM,EACN,OAAkB,EAClB,OAAO,EACP,QAAQ,GACT,EAAE,eAAe,+BAyUjB"}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { useForm, Controller, useWatch } from "react-hook-form";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
6
|
+
import * as Label from "@radix-ui/react-label";
|
|
7
|
+
import * as Switch from "@radix-ui/react-switch";
|
|
8
|
+
import { ImageUploadControl } from "../resources/ImageUploadControl";
|
|
9
|
+
const RECORD_GROUP_ORDER = [
|
|
10
|
+
"Details",
|
|
11
|
+
"Content",
|
|
12
|
+
"Pricing",
|
|
13
|
+
"Inventory",
|
|
14
|
+
"Media",
|
|
15
|
+
"Status",
|
|
16
|
+
];
|
|
17
|
+
function buildZodSchema(schema) {
|
|
18
|
+
const shape = {};
|
|
19
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
20
|
+
let zodField;
|
|
21
|
+
switch (field.type) {
|
|
22
|
+
case "number":
|
|
23
|
+
zodField = z.number();
|
|
24
|
+
if (field.min != null)
|
|
25
|
+
zodField = zodField.min(field.min);
|
|
26
|
+
if (field.max != null)
|
|
27
|
+
zodField = zodField.max(field.max);
|
|
28
|
+
break;
|
|
29
|
+
case "boolean":
|
|
30
|
+
zodField = z.boolean();
|
|
31
|
+
break;
|
|
32
|
+
default:
|
|
33
|
+
zodField = z.string();
|
|
34
|
+
}
|
|
35
|
+
if (!field.required) {
|
|
36
|
+
zodField = zodField.optional();
|
|
37
|
+
}
|
|
38
|
+
shape[key] = zodField;
|
|
39
|
+
}
|
|
40
|
+
return z.object(shape);
|
|
41
|
+
}
|
|
42
|
+
function humanizeKey(key) {
|
|
43
|
+
return key
|
|
44
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
45
|
+
.replace(/[_-]+/g, " ")
|
|
46
|
+
.trim();
|
|
47
|
+
}
|
|
48
|
+
function escapeRegExp(value) {
|
|
49
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
50
|
+
}
|
|
51
|
+
function looksLikeUrl(value) {
|
|
52
|
+
return /^https?:\/\//i.test(value) || value.startsWith("//");
|
|
53
|
+
}
|
|
54
|
+
function isImageField(key, field) {
|
|
55
|
+
const hay = `${key} ${field?.label ?? ""}`;
|
|
56
|
+
return /(image|img|photo|avatar|thumbnail|thumb|cover)/i.test(hay);
|
|
57
|
+
}
|
|
58
|
+
function isStockField(key, field) {
|
|
59
|
+
const hay = `${key} ${field?.label ?? ""}`;
|
|
60
|
+
return /(stock|inventory|qty|quantity)/i.test(hay);
|
|
61
|
+
}
|
|
62
|
+
function spanFor(key, field, variant) {
|
|
63
|
+
if (field.type === "boolean")
|
|
64
|
+
return "full";
|
|
65
|
+
if (field.type === "richtext")
|
|
66
|
+
return "full";
|
|
67
|
+
if (variant === "record" && isImageField(key, field))
|
|
68
|
+
return "full";
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
function inferConfigGroup(key, field) {
|
|
72
|
+
if (field.group?.trim())
|
|
73
|
+
return field.group.trim();
|
|
74
|
+
const source = field.label?.trim() || humanizeKey(key);
|
|
75
|
+
const words = source.split(/\s+/).filter(Boolean);
|
|
76
|
+
if (words.length >= 2)
|
|
77
|
+
return words[0];
|
|
78
|
+
return "General";
|
|
79
|
+
}
|
|
80
|
+
function inferRecordGroup(key, field) {
|
|
81
|
+
if (field.group?.trim())
|
|
82
|
+
return field.group.trim();
|
|
83
|
+
const k = key.toLowerCase();
|
|
84
|
+
const label = (field.label ?? "").toLowerCase();
|
|
85
|
+
const hay = `${k} ${label}`;
|
|
86
|
+
if (/(image|img|photo|avatar|thumbnail|thumb|cover|media_url)/i.test(hay)) {
|
|
87
|
+
return "Media";
|
|
88
|
+
}
|
|
89
|
+
if (/(price|cost|cents|amount|msrp)/i.test(hay)) {
|
|
90
|
+
return "Pricing";
|
|
91
|
+
}
|
|
92
|
+
if (/(stock|inventory|qty|quantity)/i.test(hay)) {
|
|
93
|
+
return "Inventory";
|
|
94
|
+
}
|
|
95
|
+
if (field.type === "boolean" ||
|
|
96
|
+
/(^|_)(active|enabled|published|status|visible)(_|$)/i.test(k) ||
|
|
97
|
+
/^(active|enabled|published|status|visible)$/i.test(label)) {
|
|
98
|
+
return "Status";
|
|
99
|
+
}
|
|
100
|
+
if (field.type === "richtext" ||
|
|
101
|
+
/(description|body|content|summary|bio)/i.test(hay)) {
|
|
102
|
+
return "Content";
|
|
103
|
+
}
|
|
104
|
+
return "Details";
|
|
105
|
+
}
|
|
106
|
+
function displayLabel(key, field, group) {
|
|
107
|
+
const label = field.label?.trim() || humanizeKey(key);
|
|
108
|
+
const stripped = label.replace(new RegExp(`^${escapeRegExp(group)}\\s+`, "i"), "");
|
|
109
|
+
if (/^inventory$/i.test(group)) {
|
|
110
|
+
const stock = label.replace(/^stock\s+/i, "").trim();
|
|
111
|
+
if (stock && stock !== label)
|
|
112
|
+
return stock;
|
|
113
|
+
}
|
|
114
|
+
if (/^pricing$/i.test(group) && /cents/i.test(label)) {
|
|
115
|
+
return label.replace(/\s*\(cents\)/i, "").trim() || label;
|
|
116
|
+
}
|
|
117
|
+
return stripped || label;
|
|
118
|
+
}
|
|
119
|
+
function sortGroups(groups, variant) {
|
|
120
|
+
if (variant !== "record")
|
|
121
|
+
return groups;
|
|
122
|
+
const rank = (id) => {
|
|
123
|
+
const index = RECORD_GROUP_ORDER.indexOf(id);
|
|
124
|
+
return index === -1 ? RECORD_GROUP_ORDER.length + 1 : index;
|
|
125
|
+
};
|
|
126
|
+
return [...groups].sort((a, b) => rank(a.id) - rank(b.id));
|
|
127
|
+
}
|
|
128
|
+
function groupFields(schema, variant) {
|
|
129
|
+
const order = [];
|
|
130
|
+
const map = new Map();
|
|
131
|
+
const explicit = new Set();
|
|
132
|
+
for (const entry of Object.entries(schema)) {
|
|
133
|
+
const [key, field] = entry;
|
|
134
|
+
const group = variant === "record"
|
|
135
|
+
? inferRecordGroup(key, field)
|
|
136
|
+
: inferConfigGroup(key, field);
|
|
137
|
+
if (field.group?.trim())
|
|
138
|
+
explicit.add(group);
|
|
139
|
+
if (!map.has(group)) {
|
|
140
|
+
map.set(group, []);
|
|
141
|
+
order.push(group);
|
|
142
|
+
}
|
|
143
|
+
map.get(group).push(entry);
|
|
144
|
+
}
|
|
145
|
+
if (variant === "config") {
|
|
146
|
+
for (const [group, fields] of [...map.entries()]) {
|
|
147
|
+
if (fields.length > 1 || explicit.has(group) || group === "General") {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (!map.has("General")) {
|
|
151
|
+
map.set("General", []);
|
|
152
|
+
order.unshift("General");
|
|
153
|
+
}
|
|
154
|
+
map.get("General").push(...fields);
|
|
155
|
+
map.delete(group);
|
|
156
|
+
const idx = order.indexOf(group);
|
|
157
|
+
if (idx >= 0)
|
|
158
|
+
order.splice(idx, 1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const groups = order
|
|
162
|
+
.filter((id) => map.has(id))
|
|
163
|
+
.map((id) => ({
|
|
164
|
+
id,
|
|
165
|
+
label: id,
|
|
166
|
+
fields: map.get(id),
|
|
167
|
+
}));
|
|
168
|
+
return sortGroups(groups, variant);
|
|
169
|
+
}
|
|
170
|
+
function findPrimaryImageKey(schema, values) {
|
|
171
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
172
|
+
if (!isImageField(key, field))
|
|
173
|
+
continue;
|
|
174
|
+
const value = values?.[key];
|
|
175
|
+
if (typeof value === "string" && looksLikeUrl(value))
|
|
176
|
+
return key;
|
|
177
|
+
}
|
|
178
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
179
|
+
if (isImageField(key, field))
|
|
180
|
+
return key;
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
export function ConfigForm({ schema, value, onSave, variant = "config", uploads, onUpload, }) {
|
|
185
|
+
const zodSchema = useMemo(() => buildZodSchema(schema), [schema]);
|
|
186
|
+
const groups = useMemo(() => groupFields(schema, variant), [schema, variant]);
|
|
187
|
+
const [status, setStatus] = useState(null);
|
|
188
|
+
const [tone, setTone] = useState();
|
|
189
|
+
const form = useForm({
|
|
190
|
+
resolver: zodResolver(zodSchema),
|
|
191
|
+
defaultValues: value,
|
|
192
|
+
});
|
|
193
|
+
const watched = useWatch({ control: form.control });
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
form.reset(value);
|
|
196
|
+
}, [value, form]);
|
|
197
|
+
const onSubmit = form.handleSubmit(async (data) => {
|
|
198
|
+
setStatus("Saving…");
|
|
199
|
+
setTone(undefined);
|
|
200
|
+
try {
|
|
201
|
+
await onSave(data);
|
|
202
|
+
setStatus("Saved");
|
|
203
|
+
setTone("ok");
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
setStatus(error instanceof Error ? error.message : "Save failed");
|
|
207
|
+
setTone("danger");
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
const isDirty = form.formState.isDirty;
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
const onKeyDown = (event) => {
|
|
213
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s") {
|
|
214
|
+
event.preventDefault();
|
|
215
|
+
void onSubmit();
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
window.addEventListener("keydown", onKeyDown);
|
|
219
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
220
|
+
}, [onSubmit]);
|
|
221
|
+
const renderField = (key, field, groupLabel, options) => {
|
|
222
|
+
const label = displayLabel(key, field, groupLabel);
|
|
223
|
+
const showInlinePreview = !options?.hideImagePreview &&
|
|
224
|
+
isImageField(key, field) &&
|
|
225
|
+
typeof watched?.[key] === "string" &&
|
|
226
|
+
looksLikeUrl(String(watched[key]));
|
|
227
|
+
if (field.type === "boolean") {
|
|
228
|
+
return (_jsxs("div", { className: "vertebr-switch-row", children: [_jsx(Label.Root, { htmlFor: key, children: label }), _jsx(Controller, { control: form.control, name: key, render: ({ field: controllerField }) => (_jsx(Switch.Root, { className: "vertebr-switch", id: key, checked: Boolean(controllerField.value), onCheckedChange: controllerField.onChange, children: _jsx(Switch.Thumb, { className: "vertebr-switch-thumb" }) })) })] }, key));
|
|
229
|
+
}
|
|
230
|
+
return (_jsxs("div", { className: "vertebr-field", "data-span": spanFor(key, field, variant), "data-kind": isImageField(key, field) ? "image" : undefined, children: [_jsx(Label.Root, { htmlFor: key, children: label }), showInlinePreview ? (_jsxs("div", { className: "vertebr-field-media", children: [_jsx("img", { src: String(watched[key]), alt: "" }), _jsx("input", { id: key, type: "text", readOnly: field.readOnly, ...form.register(key) })] })) : field.type === "richtext" ? (_jsx("textarea", { id: key, rows: 3, readOnly: field.readOnly, ...form.register(key) })) : field.type === "number" ? (_jsx(Controller, { control: form.control, name: key, render: ({ field: controllerField }) => (_jsx("input", { id: key, type: "number", readOnly: field.readOnly, value: typeof controllerField.value === "number" &&
|
|
231
|
+
Number.isFinite(controllerField.value)
|
|
232
|
+
? controllerField.value
|
|
233
|
+
: "", onBlur: controllerField.onBlur, onChange: (event) => {
|
|
234
|
+
const next = event.target.value;
|
|
235
|
+
controllerField.onChange(next === "" ? undefined : Number(next));
|
|
236
|
+
}, name: controllerField.name, ref: controllerField.ref })) })) : (_jsx("input", { id: key, type: "text", readOnly: field.readOnly, ...form.register(key) })), !options?.hideUpload &&
|
|
237
|
+
uploads?.[key] &&
|
|
238
|
+
onUpload &&
|
|
239
|
+
!field.readOnly ? (_jsx(ImageUploadControl, { meta: uploads[key], onUpload: async (files) => {
|
|
240
|
+
const [first] = await onUpload(key, files);
|
|
241
|
+
if (first?.url) {
|
|
242
|
+
form.setValue(key, first.url, {
|
|
243
|
+
shouldDirty: true,
|
|
244
|
+
shouldTouch: true,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
} })) : null] }, key));
|
|
248
|
+
};
|
|
249
|
+
const renderActions = () => (_jsxs("div", { className: "vertebr-actions", children: [_jsx("button", { className: "vertebr-btn vertebr-btn-primary", type: "submit", disabled: form.formState.isSubmitting || !isDirty, children: form.formState.isSubmitting ? "Saving…" : "Save changes" }), isDirty ? (_jsx("button", { className: "vertebr-btn", type: "button", onClick: () => {
|
|
250
|
+
form.reset(value);
|
|
251
|
+
setStatus(null);
|
|
252
|
+
setTone(undefined);
|
|
253
|
+
}, children: "Discard" })) : null, isDirty ? (_jsx("span", { className: "vertebr-dirty", children: "Unsaved" })) : status ? (_jsx("span", { className: "vertebr-status", "data-tone": tone, children: status })) : null, _jsx("span", { className: "vertebr-actions-spacer" }), _jsx("span", { className: "vertebr-kbd", children: "\u2318S" })] }));
|
|
254
|
+
if (variant === "record") {
|
|
255
|
+
const imageKey = findPrimaryImageKey(schema, watched);
|
|
256
|
+
const imageValue = imageKey && typeof watched?.[imageKey] === "string"
|
|
257
|
+
? String(watched[imageKey])
|
|
258
|
+
: "";
|
|
259
|
+
const asideGroups = groups.filter((g) => /^(Media|Status)$/i.test(g.id));
|
|
260
|
+
const mainGroups = groups.filter((g) => !/^(Media|Status)$/i.test(g.id));
|
|
261
|
+
return (_jsxs("form", { className: "vertebr-form", "data-variant": "record", onSubmit: onSubmit, children: [_jsx("div", { className: "vertebr-form-scroll", children: _jsxs("div", { className: "vertebr-record-layout", children: [_jsxs("aside", { className: "vertebr-record-aside", children: [_jsx("div", { className: "vertebr-record-preview", children: imageValue && looksLikeUrl(imageValue) ? (_jsx("img", { src: imageValue, alt: "" })) : (_jsx("div", { className: "vertebr-record-preview-empty", children: "No image" })) }), imageKey && uploads?.[imageKey] && onUpload ? (_jsx(ImageUploadControl, { meta: uploads[imageKey], label: "Drop a new image or browse", onUpload: async (files) => {
|
|
262
|
+
const [first] = await onUpload(imageKey, files);
|
|
263
|
+
if (first?.url) {
|
|
264
|
+
form.setValue(imageKey, first.url, {
|
|
265
|
+
shouldDirty: true,
|
|
266
|
+
shouldTouch: true,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
} })) : null, asideGroups.map((group) => (_jsxs("div", { className: "vertebr-record-block", "data-layout": /^inventory$/i.test(group.id) ? "chips" : undefined, children: [_jsx("div", { className: "vertebr-record-block-label", children: group.label }), _jsx("div", { className: "vertebr-form-fields", "data-layout": isStockField(group.fields[0]?.[0] ?? "", group.fields[0]?.[1])
|
|
270
|
+
? "chips"
|
|
271
|
+
: undefined, children: group.fields.map(([key, field]) => renderField(key, field, group.label, {
|
|
272
|
+
hideImagePreview: true,
|
|
273
|
+
hideUpload: key === imageKey,
|
|
274
|
+
})) })] }, group.id)))] }), _jsx("div", { className: "vertebr-record-main", children: mainGroups.map((group) => {
|
|
275
|
+
const inventory = /^inventory$/i.test(group.id);
|
|
276
|
+
return (_jsxs("div", { className: "vertebr-record-block", "data-layout": inventory ? "chips" : undefined, children: [_jsx("div", { className: "vertebr-record-block-label", children: group.label }), _jsx("div", { className: "vertebr-form-fields", "data-layout": inventory ? "chips" : undefined, children: group.fields.map(([key, field]) => renderField(key, field, group.label)) })] }, group.id));
|
|
277
|
+
}) })] }) }), renderActions()] }));
|
|
278
|
+
}
|
|
279
|
+
const singleGroup = groups.length === 1;
|
|
280
|
+
return (_jsxs("form", { className: "vertebr-form", "data-variant": "config", onSubmit: onSubmit, children: [_jsx("div", { className: "vertebr-form-scroll", children: groups.map((group) => (_jsxs("div", { className: "vertebr-form-section", "data-plain": singleGroup ? "true" : undefined, children: [!singleGroup ? (_jsxs("div", { className: "vertebr-form-section-head", children: [_jsx("h3", { children: group.label }), _jsx("span", { className: "vertebr-form-section-count", children: group.fields.length })] })) : null, _jsx("div", { className: "vertebr-form-fields", children: group.fields.map(([key, field]) => renderField(key, field, group.label)) })] }, group.id))) }), renderActions()] }));
|
|
281
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EnvSelect.d.ts","sourceRoot":"","sources":["../../../src/components/env/EnvSelect.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,KAAK,cAAc,GAAG;IACpB,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,wBAAgB,SAAS,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,cAAc,+BAqD1E"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { GalleryItem } from "@vertebr/client";
|
|
2
|
+
export type GalleryBoardProps = {
|
|
3
|
+
label?: string;
|
|
4
|
+
items: GalleryItem[];
|
|
5
|
+
accept: string[];
|
|
6
|
+
maxBytes: number;
|
|
7
|
+
canDelete?: boolean;
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
onRefresh?: () => void;
|
|
10
|
+
onUpload: (files: File[]) => Promise<void>;
|
|
11
|
+
onDelete?: (id: string) => Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
export declare function GalleryBoard({ label, items, accept, maxBytes, canDelete, loading, onRefresh, onUpload, onDelete, }: GalleryBoardProps): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=GalleryBoard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GalleryBoard.d.ts","sourceRoot":"","sources":["../../../src/components/gallery/GalleryBoard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAsB,MAAM,iBAAiB,CAAC;AAGvE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAC3B,KAAiB,EACjB,KAAK,EACL,MAAM,EACN,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,GACT,EAAE,iBAAiB,+BAiFnB"}
|