@maykonpaulo/maestro-admin 0.3.0 → 0.4.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -12
- package/dist/index.d.ts +315 -3
- package/dist/index.js +1379 -473
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
- package/src/styles.css +1716 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/MaestroAdmin.tsx
|
|
2
|
-
import { useMemo as
|
|
2
|
+
import { useCallback as useCallback5, useEffect as useEffect8, useMemo as useMemo7, useState as useState14 } from "react";
|
|
3
3
|
|
|
4
4
|
// src/client/MaestroClient.ts
|
|
5
5
|
var MaestroApiError = class extends Error {
|
|
@@ -110,6 +110,11 @@ function useEntity(entityId) {
|
|
|
110
110
|
const entities = useEntities();
|
|
111
111
|
return entityId ? entities.find((e) => e.id === entityId) : void 0;
|
|
112
112
|
}
|
|
113
|
+
var RootContext = createContext(null);
|
|
114
|
+
var AdminRootProvider = RootContext.Provider;
|
|
115
|
+
function useAdminRoot() {
|
|
116
|
+
return useContext(RootContext);
|
|
117
|
+
}
|
|
113
118
|
|
|
114
119
|
// src/hooks.ts
|
|
115
120
|
import { useCallback, useEffect, useState } from "react";
|
|
@@ -198,9 +203,6 @@ function formFields(entity, mode) {
|
|
|
198
203
|
return entity.fields.filter((f) => mode === "create" ? f.form.creatable : f.form.editable).sort((a, b) => (a.form.order ?? 0) - (b.form.order ?? 0));
|
|
199
204
|
}
|
|
200
205
|
|
|
201
|
-
// src/components/Sidebar.tsx
|
|
202
|
-
import { useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
203
|
-
|
|
204
206
|
// src/grouping.ts
|
|
205
207
|
var MIN_PREFIX = 4;
|
|
206
208
|
function commonPrefix(a, b) {
|
|
@@ -257,6 +259,9 @@ function buildMenu(entities, labels) {
|
|
|
257
259
|
return { groups, ungrouped };
|
|
258
260
|
}
|
|
259
261
|
|
|
262
|
+
// src/components/Sidebar.tsx
|
|
263
|
+
import { useMemo, useState as useState2 } from "react";
|
|
264
|
+
|
|
260
265
|
// src/i18n.ts
|
|
261
266
|
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
262
267
|
var EN = {
|
|
@@ -264,7 +269,7 @@ var EN = {
|
|
|
264
269
|
loadingMetadata: "Loading metadata\u2026",
|
|
265
270
|
records: (total) => `${total} record${total === 1 ? "" : "s"}`,
|
|
266
271
|
searchPlaceholder: (plural) => `Search ${plural.toLowerCase()}\u2026`,
|
|
267
|
-
newRecord: "
|
|
272
|
+
newRecord: "New",
|
|
268
273
|
view: "View",
|
|
269
274
|
edit: "Edit",
|
|
270
275
|
clone: "Clone",
|
|
@@ -303,14 +308,42 @@ var EN = {
|
|
|
303
308
|
searchMenu: "Search menu\u2026",
|
|
304
309
|
recents: "Recents",
|
|
305
310
|
menus: "Menus",
|
|
306
|
-
noMenuResults: "No matches"
|
|
311
|
+
noMenuResults: "No matches",
|
|
312
|
+
hub: "Hub",
|
|
313
|
+
breadcrumb: "Breadcrumb",
|
|
314
|
+
search: "Search",
|
|
315
|
+
commandKey: "\u2318K",
|
|
316
|
+
commandPalette: "Command palette",
|
|
317
|
+
palettePlaceholder: "Go to an entity, or run an action\u2026",
|
|
318
|
+
goTo: "Go to",
|
|
319
|
+
onThisScreen: "On this screen",
|
|
320
|
+
appearance: "Appearance",
|
|
321
|
+
palette: "Palette",
|
|
322
|
+
density: "Density",
|
|
323
|
+
colorScheme: "Light / dark",
|
|
324
|
+
schemeSystem: "system",
|
|
325
|
+
schemeLight: "light",
|
|
326
|
+
schemeDark: "dark",
|
|
327
|
+
entities: "Entities",
|
|
328
|
+
hubMeta: (entities, sources) => `${entities} ${entities === 1 ? "entity" : "entities"} across ${sources} ${sources === 1 ? "datasource" : "datasources"}.`,
|
|
329
|
+
otherEntities: "Other",
|
|
330
|
+
noEntities: "No entities",
|
|
331
|
+
noEntitiesHint: "The server served an empty metadata document.",
|
|
332
|
+
fieldCount: (count) => `${count} field${count === 1 ? "" : "s"}`,
|
|
333
|
+
capRead: "read",
|
|
334
|
+
capCreate: "create",
|
|
335
|
+
capUpdate: "update",
|
|
336
|
+
capDelete: "delete",
|
|
337
|
+
columns: "Columns",
|
|
338
|
+
columnsCount: (visible, total) => `${visible}/${total}`,
|
|
339
|
+
resetColumns: "Reset columns"
|
|
307
340
|
};
|
|
308
341
|
var PT_BR = {
|
|
309
342
|
loading: "Carregando\u2026",
|
|
310
343
|
loadingMetadata: "Carregando metadados\u2026",
|
|
311
344
|
records: (total) => `${total} registro${total === 1 ? "" : "s"}`,
|
|
312
345
|
searchPlaceholder: (plural) => `Buscar ${plural.toLowerCase()}\u2026`,
|
|
313
|
-
newRecord: "
|
|
346
|
+
newRecord: "Novo",
|
|
314
347
|
view: "Visualizar",
|
|
315
348
|
edit: "Editar",
|
|
316
349
|
clone: "Clonar",
|
|
@@ -349,7 +382,35 @@ var PT_BR = {
|
|
|
349
382
|
searchMenu: "Buscar no menu\u2026",
|
|
350
383
|
recents: "Recentes",
|
|
351
384
|
menus: "Menus",
|
|
352
|
-
noMenuResults: "Nada encontrado"
|
|
385
|
+
noMenuResults: "Nada encontrado",
|
|
386
|
+
hub: "Hub",
|
|
387
|
+
breadcrumb: "Trilha",
|
|
388
|
+
search: "Buscar",
|
|
389
|
+
commandKey: "\u2318K",
|
|
390
|
+
commandPalette: "Paleta de comandos",
|
|
391
|
+
palettePlaceholder: "Ir para uma entidade, ou executar uma a\xE7\xE3o\u2026",
|
|
392
|
+
goTo: "Ir para",
|
|
393
|
+
onThisScreen: "Nesta tela",
|
|
394
|
+
appearance: "Apar\xEAncia",
|
|
395
|
+
palette: "Paleta",
|
|
396
|
+
density: "Densidade",
|
|
397
|
+
colorScheme: "Claro / escuro",
|
|
398
|
+
schemeSystem: "sistema",
|
|
399
|
+
schemeLight: "claro",
|
|
400
|
+
schemeDark: "escuro",
|
|
401
|
+
entities: "Entidades",
|
|
402
|
+
hubMeta: (entities, sources) => `${entities} ${entities === 1 ? "entidade" : "entidades"} em ${sources} ${sources === 1 ? "origem de dados" : "origens de dados"}.`,
|
|
403
|
+
otherEntities: "Outras",
|
|
404
|
+
noEntities: "Nenhuma entidade",
|
|
405
|
+
noEntitiesHint: "O servidor entregou um documento de metadados vazio.",
|
|
406
|
+
fieldCount: (count) => `${count} campo${count === 1 ? "" : "s"}`,
|
|
407
|
+
capRead: "ler",
|
|
408
|
+
capCreate: "criar",
|
|
409
|
+
capUpdate: "editar",
|
|
410
|
+
capDelete: "excluir",
|
|
411
|
+
columns: "Colunas",
|
|
412
|
+
columnsCount: (visible, total) => `${visible}/${total}`,
|
|
413
|
+
resetColumns: "Restaurar colunas"
|
|
353
414
|
};
|
|
354
415
|
var LOCALES = { en: EN, "pt-BR": PT_BR };
|
|
355
416
|
function stringsFor(locale) {
|
|
@@ -361,8 +422,251 @@ function useT() {
|
|
|
361
422
|
return useContext2(I18nContext);
|
|
362
423
|
}
|
|
363
424
|
|
|
364
|
-
// src/components/
|
|
425
|
+
// src/components/icons.tsx
|
|
365
426
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
427
|
+
var PATHS = {
|
|
428
|
+
hub: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
429
|
+
/* @__PURE__ */ jsx("rect", { x: "3.5", y: "3.5", width: "7", height: "7", rx: "1.5" }),
|
|
430
|
+
/* @__PURE__ */ jsx("rect", { x: "13.5", y: "3.5", width: "7", height: "7", rx: "1.5" }),
|
|
431
|
+
/* @__PURE__ */ jsx("rect", { x: "3.5", y: "13.5", width: "7", height: "7", rx: "1.5" }),
|
|
432
|
+
/* @__PURE__ */ jsx("rect", { x: "13.5", y: "13.5", width: "7", height: "7", rx: "1.5" })
|
|
433
|
+
] }),
|
|
434
|
+
search: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
435
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "6.5" }),
|
|
436
|
+
/* @__PURE__ */ jsx("path", { d: "m16 16 4.5 4.5" })
|
|
437
|
+
] }),
|
|
438
|
+
chevronRight: /* @__PURE__ */ jsx("path", { d: "m9 5 7 7-7 7" }),
|
|
439
|
+
chevronLeft: /* @__PURE__ */ jsx("path", { d: "M15 5 8 12l7 7" }),
|
|
440
|
+
chevronsLeft: /* @__PURE__ */ jsx("path", { d: "M17 5l-7 7 7 7M10 5l-7 7 7 7" }),
|
|
441
|
+
chevronsRight: /* @__PURE__ */ jsx("path", { d: "m7 5 7 7-7 7M14 5l7 7-7 7" }),
|
|
442
|
+
plus: /* @__PURE__ */ jsx("path", { d: "M12 5v14M5 12h14" }),
|
|
443
|
+
close: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6 6 18" }),
|
|
444
|
+
check: /* @__PURE__ */ jsx("path", { d: "m4.5 12.5 5 5 10-11" }),
|
|
445
|
+
filter: /* @__PURE__ */ jsx("path", { d: "M3 5h18l-7 8v6l-4 2v-8L3 5Z" }),
|
|
446
|
+
download: /* @__PURE__ */ jsx("path", { d: "M12 3v12M7.5 10.5 12 15l4.5-4.5M4 20h16" }),
|
|
447
|
+
columns: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
448
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
|
|
449
|
+
/* @__PURE__ */ jsx("path", { d: "M9.5 4v16M15 4v16" })
|
|
450
|
+
] }),
|
|
451
|
+
rows: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
452
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
|
|
453
|
+
/* @__PURE__ */ jsx("path", { d: "M3 9.3h18M3 14.7h18" })
|
|
454
|
+
] }),
|
|
455
|
+
sun: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
456
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "4.2" }),
|
|
457
|
+
/* @__PURE__ */ jsx("path", { d: "M12 2.5v2.2M12 19.3v2.2M4.2 4.2l1.6 1.6M18.2 18.2l1.6 1.6M2.5 12h2.2M19.3 12h2.2M4.2 19.8l1.6-1.6M18.2 5.8l1.6-1.6" })
|
|
458
|
+
] }),
|
|
459
|
+
moon: /* @__PURE__ */ jsx("path", { d: "M20 14.5A8.5 8.5 0 0 1 9.5 4a8.5 8.5 0 1 0 10.5 10.5Z" }),
|
|
460
|
+
swatch: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
461
|
+
/* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "4.5" }),
|
|
462
|
+
/* @__PURE__ */ jsx("circle", { cx: "15.5", cy: "8.5", r: "4.5" }),
|
|
463
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "15", r: "4.5" })
|
|
464
|
+
] }),
|
|
465
|
+
dots: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
466
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "5", r: "1.4" }),
|
|
467
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "1.4" }),
|
|
468
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "19", r: "1.4" })
|
|
469
|
+
] }),
|
|
470
|
+
trash: /* @__PURE__ */ jsx("path", { d: "M4 7h16M9 7V4.5h6V7M6.5 7l1 13h9l1-13" }),
|
|
471
|
+
pencil: /* @__PURE__ */ jsx("path", { d: "M4 20h4L20 8l-4-4L4 16v4Z" }),
|
|
472
|
+
eye: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
473
|
+
/* @__PURE__ */ jsx("path", { d: "M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z" }),
|
|
474
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" })
|
|
475
|
+
] }),
|
|
476
|
+
copy: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
477
|
+
/* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "11", height: "11", rx: "2" }),
|
|
478
|
+
/* @__PURE__ */ jsx("path", { d: "M15 6.5V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h.5" })
|
|
479
|
+
] }),
|
|
480
|
+
clone: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
481
|
+
/* @__PURE__ */ jsx("rect", { x: "4", y: "4", width: "11", height: "11", rx: "2" }),
|
|
482
|
+
/* @__PURE__ */ jsx("path", { d: "M9 20h9a2 2 0 0 0 2-2V9" })
|
|
483
|
+
] }),
|
|
484
|
+
arrowUpRight: /* @__PURE__ */ jsx("path", { d: "M7 17 17 7M9 7h8v8" }),
|
|
485
|
+
arrowLeft: /* @__PURE__ */ jsx("path", { d: "M19 12H6M11 6l-5 6 5 6" }),
|
|
486
|
+
sort: /* @__PURE__ */ jsx("path", { d: "M8 9 12 5l4 4M8 15l4 4 4-4" }),
|
|
487
|
+
sortAsc: /* @__PURE__ */ jsx("path", { d: "M12 19V6M7 11l5-5 5 5" }),
|
|
488
|
+
sortDesc: /* @__PURE__ */ jsx("path", { d: "M12 5v13M7 13l5 5 5-5" }),
|
|
489
|
+
database: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
490
|
+
/* @__PURE__ */ jsx("ellipse", { cx: "12", cy: "6", rx: "8", ry: "3.2" }),
|
|
491
|
+
/* @__PURE__ */ jsx("path", { d: "M4 6v12c0 1.8 3.6 3.2 8 3.2s8-1.4 8-3.2V6M4 12c0 1.8 3.6 3.2 8 3.2s8-1.4 8-3.2" })
|
|
492
|
+
] }),
|
|
493
|
+
table: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
494
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
|
|
495
|
+
/* @__PURE__ */ jsx("path", { d: "M3 9.5h18M9 9.5V20" })
|
|
496
|
+
] }),
|
|
497
|
+
users: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
498
|
+
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "8", r: "3.2" }),
|
|
499
|
+
/* @__PURE__ */ jsx("path", { d: "M3 19c0-3 2.7-4.8 6-4.8s6 1.8 6 4.8" }),
|
|
500
|
+
/* @__PURE__ */ jsx("path", { d: "M16 5.5a3 3 0 0 1 0 5.6M17.5 14.6c2 .6 3.5 2.1 3.5 4.4" })
|
|
501
|
+
] }),
|
|
502
|
+
box: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
503
|
+
/* @__PURE__ */ jsx("path", { d: "M12 3 20.5 7.5v9L12 21l-8.5-4.5v-9L12 3Z" }),
|
|
504
|
+
/* @__PURE__ */ jsx("path", { d: "M3.5 7.5 12 12l8.5-4.5M12 12v9" })
|
|
505
|
+
] }),
|
|
506
|
+
receipt: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
507
|
+
/* @__PURE__ */ jsx("path", { d: "M5 3h14v18l-2.3-1.6-2.3 1.6-2.4-1.6L9.6 21l-2.3-1.6L5 21V3Z" }),
|
|
508
|
+
/* @__PURE__ */ jsx("path", { d: "M9 8h6M9 12h6" })
|
|
509
|
+
] }),
|
|
510
|
+
key: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
511
|
+
/* @__PURE__ */ jsx("circle", { cx: "8", cy: "13", r: "4" }),
|
|
512
|
+
/* @__PURE__ */ jsx("path", { d: "m11 11 8-8M17 5l2 2M15 7l2 2" })
|
|
513
|
+
] }),
|
|
514
|
+
shield: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
515
|
+
/* @__PURE__ */ jsx("path", { d: "M12 3 20 6v6c0 4.4-3.3 7.9-8 9-4.7-1.1-8-4.6-8-9V6l8-3Z" }),
|
|
516
|
+
/* @__PURE__ */ jsx("path", { d: "m9 12 2.2 2.2L15.5 10" })
|
|
517
|
+
] }),
|
|
518
|
+
activity: /* @__PURE__ */ jsx("path", { d: "M3 12h4l2.5-6 4 12 2.5-6h5" }),
|
|
519
|
+
tag: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
520
|
+
/* @__PURE__ */ jsx("path", { d: "M3 11.5V4h7.5l10 10-7.5 7.5-10-10Z" }),
|
|
521
|
+
/* @__PURE__ */ jsx("circle", { cx: "7.5", cy: "7.5", r: "1.3" })
|
|
522
|
+
] }),
|
|
523
|
+
map: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
524
|
+
/* @__PURE__ */ jsx("path", { d: "M9 4 3 6.5v13L9 17l6 2.5 6-2.5v-13L15 6.5 9 4Z" }),
|
|
525
|
+
/* @__PURE__ */ jsx("path", { d: "M9 4v13M15 6.5v13" })
|
|
526
|
+
] }),
|
|
527
|
+
calendar: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
528
|
+
/* @__PURE__ */ jsx("rect", { x: "3.5", y: "5", width: "17", height: "15", rx: "2" }),
|
|
529
|
+
/* @__PURE__ */ jsx("path", { d: "M3.5 10h17M8 3v4M16 3v4" })
|
|
530
|
+
] }),
|
|
531
|
+
mail: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
532
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
|
|
533
|
+
/* @__PURE__ */ jsx("path", { d: "m3.5 7 8.5 6 8.5-6" })
|
|
534
|
+
] }),
|
|
535
|
+
file: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
536
|
+
/* @__PURE__ */ jsx("path", { d: "M13 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9l-6-6Z" }),
|
|
537
|
+
/* @__PURE__ */ jsx("path", { d: "M13 3v6h6" })
|
|
538
|
+
] }),
|
|
539
|
+
alert: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
540
|
+
/* @__PURE__ */ jsx("path", { d: "M12 4 2.5 20h19L12 4Z" }),
|
|
541
|
+
/* @__PURE__ */ jsx("path", { d: "M12 10v4.5M12 17.4v.2" })
|
|
542
|
+
] })
|
|
543
|
+
};
|
|
544
|
+
function Icon({
|
|
545
|
+
name,
|
|
546
|
+
size = 15,
|
|
547
|
+
className,
|
|
548
|
+
title
|
|
549
|
+
}) {
|
|
550
|
+
return /* @__PURE__ */ jsxs(
|
|
551
|
+
"svg",
|
|
552
|
+
{
|
|
553
|
+
viewBox: "0 0 24 24",
|
|
554
|
+
width: size,
|
|
555
|
+
height: size,
|
|
556
|
+
className: className ? `mst-icon ${className}` : "mst-icon",
|
|
557
|
+
"aria-hidden": title ? void 0 : true,
|
|
558
|
+
role: title ? "img" : void 0,
|
|
559
|
+
focusable: "false",
|
|
560
|
+
children: [
|
|
561
|
+
title && /* @__PURE__ */ jsx("title", { children: title }),
|
|
562
|
+
PATHS[name]
|
|
563
|
+
]
|
|
564
|
+
}
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
var ENTITY_ICON_HINTS = [
|
|
568
|
+
[["user", "usuario", "people", "pessoa", "customer", "cliente", "associado", "member", "contact"], "users"],
|
|
569
|
+
[["invoice", "fatura", "cobranca", "billing", "receipt", "nota"], "receipt"],
|
|
570
|
+
[["payment", "pagamento", "transaction", "transacao", "ledger"], "table"],
|
|
571
|
+
[["product", "produto", "item", "sku", "inventory", "estoque", "catalog"], "box"],
|
|
572
|
+
[["address", "endereco", "location", "local", "region", "city", "cidade"], "map"],
|
|
573
|
+
[["role", "papel", "permission", "permissao", "grant", "token", "credential"], "key"],
|
|
574
|
+
[["audit", "auditoria", "log", "event", "evento", "metric"], "activity"],
|
|
575
|
+
[["policy", "politica", "security", "seguranca", "consent", "consentimento"], "shield"],
|
|
576
|
+
[["tag", "label", "category", "categoria", "plan", "plano", "subscription", "assinatura"], "tag"],
|
|
577
|
+
[["schedule", "agenda", "appointment", "date", "periodo", "calendar"], "calendar"],
|
|
578
|
+
[["message", "mensagem", "email", "notification", "notificacao", "ticket"], "mail"],
|
|
579
|
+
[["document", "documento", "file", "arquivo", "contract", "contrato", "report"], "file"]
|
|
580
|
+
];
|
|
581
|
+
function fold(text) {
|
|
582
|
+
return text.toLowerCase().normalize("NFD").replace(/\p{M}/gu, "");
|
|
583
|
+
}
|
|
584
|
+
function entityIcon(entity) {
|
|
585
|
+
const haystack = fold(`${entity.id} ${entity.table} ${entity.label.singular} ${entity.label.plural}`);
|
|
586
|
+
for (const [words, icon] of ENTITY_ICON_HINTS) {
|
|
587
|
+
if (words.some((word) => haystack.includes(word))) return icon;
|
|
588
|
+
}
|
|
589
|
+
return "table";
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// src/components/brand.tsx
|
|
593
|
+
import { useId } from "react";
|
|
594
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
595
|
+
function labelProps(title) {
|
|
596
|
+
return title ? { role: "img" } : { "aria-hidden": true };
|
|
597
|
+
}
|
|
598
|
+
function MaestroMark({
|
|
599
|
+
size = 30,
|
|
600
|
+
className,
|
|
601
|
+
title
|
|
602
|
+
}) {
|
|
603
|
+
const maskId = useId();
|
|
604
|
+
return /* @__PURE__ */ jsxs2(
|
|
605
|
+
"svg",
|
|
606
|
+
{
|
|
607
|
+
viewBox: "0 0 64 64",
|
|
608
|
+
width: size,
|
|
609
|
+
height: size,
|
|
610
|
+
fill: "none",
|
|
611
|
+
className,
|
|
612
|
+
focusable: "false",
|
|
613
|
+
...labelProps(title),
|
|
614
|
+
children: [
|
|
615
|
+
title && /* @__PURE__ */ jsx2("title", { children: title }),
|
|
616
|
+
/* @__PURE__ */ jsxs2("mask", { id: maskId, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "64", height: "64", children: [
|
|
617
|
+
/* @__PURE__ */ jsx2("rect", { width: "64", height: "64", fill: "#000" }),
|
|
618
|
+
/* @__PURE__ */ jsx2("rect", { y: "14", width: "64", height: "10.667", fill: "#fff" }),
|
|
619
|
+
/* @__PURE__ */ jsx2("rect", { y: "28.667", width: "64", height: "10.667", fill: "#fff" }),
|
|
620
|
+
/* @__PURE__ */ jsx2("rect", { y: "43.333", width: "64", height: "10.667", fill: "#fff" })
|
|
621
|
+
] }),
|
|
622
|
+
/* @__PURE__ */ jsx2(
|
|
623
|
+
"path",
|
|
624
|
+
{
|
|
625
|
+
d: "M12 54V14l20 24 20-24v40",
|
|
626
|
+
stroke: "currentColor",
|
|
627
|
+
strokeWidth: "10",
|
|
628
|
+
strokeLinejoin: "round",
|
|
629
|
+
mask: `url(#${maskId})`
|
|
630
|
+
}
|
|
631
|
+
)
|
|
632
|
+
]
|
|
633
|
+
}
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
function MaestroSeal({
|
|
637
|
+
size = 30,
|
|
638
|
+
className,
|
|
639
|
+
title
|
|
640
|
+
}) {
|
|
641
|
+
const maskId = useId();
|
|
642
|
+
return /* @__PURE__ */ jsxs2(
|
|
643
|
+
"svg",
|
|
644
|
+
{
|
|
645
|
+
viewBox: "0 0 64 64",
|
|
646
|
+
width: size,
|
|
647
|
+
height: size,
|
|
648
|
+
className,
|
|
649
|
+
focusable: "false",
|
|
650
|
+
...labelProps(title),
|
|
651
|
+
children: [
|
|
652
|
+
title && /* @__PURE__ */ jsx2("title", { children: title }),
|
|
653
|
+
/* @__PURE__ */ jsxs2("mask", { id: maskId, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "64", height: "64", children: [
|
|
654
|
+
/* @__PURE__ */ jsx2("rect", { width: "64", height: "64", fill: "#fff" }),
|
|
655
|
+
/* @__PURE__ */ jsxs2("g", { transform: "scale(1.142857143) translate(-4 -4)", children: [
|
|
656
|
+
/* @__PURE__ */ jsx2("path", { d: "M17.3 47V17L32 35L46.7 17V47", fill: "none", stroke: "#000", strokeWidth: "8.1", strokeLinejoin: "round" }),
|
|
657
|
+
/* @__PURE__ */ jsx2("rect", { x: "-8", y: "-8", width: "80", height: "25", fill: "#fff" }),
|
|
658
|
+
/* @__PURE__ */ jsx2("rect", { x: "-8", y: "47", width: "80", height: "25", fill: "#fff" }),
|
|
659
|
+
/* @__PURE__ */ jsx2("rect", { x: "-8", y: "25", width: "80", height: "3", fill: "#fff" })
|
|
660
|
+
] })
|
|
661
|
+
] }),
|
|
662
|
+
/* @__PURE__ */ jsx2("rect", { width: "64", height: "64", rx: "18.3", fill: "currentColor", mask: `url(#${maskId})` })
|
|
663
|
+
]
|
|
664
|
+
}
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// src/components/Sidebar.tsx
|
|
669
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
366
670
|
function normalize(text) {
|
|
367
671
|
return text.toLowerCase().normalize("NFD").replace(/\p{M}/gu, "");
|
|
368
672
|
}
|
|
@@ -381,9 +685,9 @@ function Highlight({ label, query }) {
|
|
|
381
685
|
if (idx === -1) return label;
|
|
382
686
|
const start = originalIndex[idx];
|
|
383
687
|
const end = (originalIndex[idx + foldedQuery.length - 1] ?? label.length - 1) + 1;
|
|
384
|
-
return /* @__PURE__ */
|
|
688
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
385
689
|
label.slice(0, start),
|
|
386
|
-
/* @__PURE__ */
|
|
690
|
+
/* @__PURE__ */ jsx3("mark", { children: label.slice(start, end) }),
|
|
387
691
|
label.slice(end)
|
|
388
692
|
] });
|
|
389
693
|
}
|
|
@@ -394,33 +698,40 @@ function EntityButton({
|
|
|
394
698
|
subtitle,
|
|
395
699
|
query
|
|
396
700
|
}) {
|
|
397
|
-
return /* @__PURE__ */
|
|
701
|
+
return /* @__PURE__ */ jsxs3(
|
|
398
702
|
"button",
|
|
399
703
|
{
|
|
704
|
+
type: "button",
|
|
705
|
+
"aria-current": active,
|
|
400
706
|
onClick: () => onSelect(entity.id),
|
|
401
|
-
className:
|
|
707
|
+
className: "mst-navitem",
|
|
708
|
+
title: entity.label.plural,
|
|
402
709
|
children: [
|
|
403
|
-
/* @__PURE__ */
|
|
404
|
-
|
|
710
|
+
/* @__PURE__ */ jsx3(Icon, { name: entityIcon(entity), size: 14 }),
|
|
711
|
+
/* @__PURE__ */ jsxs3("span", { className: "mst-navitem__label", children: [
|
|
712
|
+
query ? /* @__PURE__ */ jsx3(Highlight, { label: entity.label.plural, query }) : entity.label.plural,
|
|
713
|
+
subtitle && /* @__PURE__ */ jsxs3("span", { className: "mst-navitem__sub", children: [
|
|
714
|
+
" \xB7 ",
|
|
715
|
+
subtitle
|
|
716
|
+
] })
|
|
717
|
+
] })
|
|
405
718
|
]
|
|
406
719
|
}
|
|
407
720
|
);
|
|
408
721
|
}
|
|
409
|
-
function SectionTitle({ children }) {
|
|
410
|
-
return /* @__PURE__ */ jsx("div", { className: "px-3 pb-1 pt-3 text-xs font-semibold uppercase tracking-wide text-slate-400", children });
|
|
411
|
-
}
|
|
412
722
|
function Sidebar({
|
|
413
723
|
entities,
|
|
414
724
|
activeId,
|
|
415
725
|
onSelect,
|
|
416
726
|
title,
|
|
417
727
|
labels,
|
|
418
|
-
recents = []
|
|
728
|
+
recents = [],
|
|
729
|
+
onHome,
|
|
730
|
+
homeActive = false
|
|
419
731
|
}) {
|
|
420
732
|
const t = useT();
|
|
421
733
|
const [query, setQuery] = useState2("");
|
|
422
734
|
const [expanded, setExpanded] = useState2({});
|
|
423
|
-
const searchRef = useRef(null);
|
|
424
735
|
const menu = useMemo(() => buildMenu(entities, labels), [entities, labels]);
|
|
425
736
|
const groupOf = useMemo(() => {
|
|
426
737
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -433,27 +744,19 @@ function Sidebar({
|
|
|
433
744
|
const q = normalize(query);
|
|
434
745
|
return entities.filter((e) => normalize(`${e.label.plural} ${e.label.singular} ${e.table}`).includes(q)).sort((a, b) => a.label.plural.localeCompare(b.label.plural));
|
|
435
746
|
}, [entities, query]);
|
|
436
|
-
useEffect2(() => {
|
|
437
|
-
const onKey = (e) => {
|
|
438
|
-
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
|
|
439
|
-
e.preventDefault();
|
|
440
|
-
searchRef.current?.focus();
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
document.addEventListener("keydown", onKey);
|
|
444
|
-
return () => document.removeEventListener("keydown", onKey);
|
|
445
|
-
}, []);
|
|
446
747
|
const select = (entityId) => {
|
|
447
748
|
setQuery("");
|
|
448
749
|
onSelect(entityId);
|
|
449
750
|
};
|
|
450
751
|
const isExpanded = (groupId, hasActive) => expanded[groupId] ?? hasActive;
|
|
451
|
-
return /* @__PURE__ */
|
|
452
|
-
/* @__PURE__ */
|
|
453
|
-
|
|
752
|
+
return /* @__PURE__ */ jsxs3("aside", { className: "mst-side", children: [
|
|
753
|
+
/* @__PURE__ */ jsxs3("div", { className: "mst-side__brand", children: [
|
|
754
|
+
/* @__PURE__ */ jsx3(MaestroMark, { className: "mst-mark", size: 30 }),
|
|
755
|
+
/* @__PURE__ */ jsx3("span", { className: "mst-side__title", children: title })
|
|
756
|
+
] }),
|
|
757
|
+
/* @__PURE__ */ jsx3("div", { className: "mst-side__search", children: /* @__PURE__ */ jsx3(
|
|
454
758
|
"input",
|
|
455
759
|
{
|
|
456
|
-
ref: searchRef,
|
|
457
760
|
type: "search",
|
|
458
761
|
value: query,
|
|
459
762
|
onChange: (e) => setQuery(e.target.value),
|
|
@@ -461,11 +764,11 @@ function Sidebar({
|
|
|
461
764
|
if (e.key === "Escape") setQuery("");
|
|
462
765
|
if (e.key === "Enter" && results[0]) select(results[0].id);
|
|
463
766
|
},
|
|
464
|
-
placeholder:
|
|
465
|
-
|
|
767
|
+
placeholder: t.searchMenu,
|
|
768
|
+
"aria-label": t.searchMenu
|
|
466
769
|
}
|
|
467
770
|
) }),
|
|
468
|
-
/* @__PURE__ */
|
|
771
|
+
/* @__PURE__ */ jsx3("nav", { className: "mst-side__nav", children: query ? results.length === 0 ? /* @__PURE__ */ jsx3("p", { className: "mst-side__empty", children: t.noMenuResults }) : results.map((e) => /* @__PURE__ */ jsx3(
|
|
469
772
|
EntityButton,
|
|
470
773
|
{
|
|
471
774
|
entity: e,
|
|
@@ -475,90 +778,615 @@ function Sidebar({
|
|
|
475
778
|
query
|
|
476
779
|
},
|
|
477
780
|
e.id
|
|
478
|
-
)) : /* @__PURE__ */
|
|
479
|
-
|
|
480
|
-
/* @__PURE__ */
|
|
481
|
-
|
|
781
|
+
)) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
782
|
+
onHome && /* @__PURE__ */ jsxs3("button", { type: "button", className: "mst-navitem", "aria-current": homeActive, onClick: onHome, title: t.hub, children: [
|
|
783
|
+
/* @__PURE__ */ jsx3(Icon, { name: "hub", size: 14 }),
|
|
784
|
+
/* @__PURE__ */ jsx3("span", { className: "mst-navitem__label", children: t.hub })
|
|
482
785
|
] }),
|
|
483
|
-
/* @__PURE__ */
|
|
786
|
+
recentEntities.length > 0 && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
787
|
+
/* @__PURE__ */ jsx3("p", { className: "mst-side__section", children: t.recents }),
|
|
788
|
+
recentEntities.map((e) => /* @__PURE__ */ jsx3(EntityButton, { entity: e, active: e.id === activeId, onSelect: select }, e.id))
|
|
789
|
+
] }),
|
|
790
|
+
/* @__PURE__ */ jsx3("p", { className: "mst-side__section", children: t.menus }),
|
|
484
791
|
menu.groups.map((group) => {
|
|
485
792
|
const hasActive = group.entities.some((e) => e.id === activeId);
|
|
486
793
|
const open = isExpanded(group.id, hasActive);
|
|
487
|
-
return /* @__PURE__ */
|
|
488
|
-
/* @__PURE__ */
|
|
794
|
+
return /* @__PURE__ */ jsxs3("div", { children: [
|
|
795
|
+
/* @__PURE__ */ jsxs3(
|
|
489
796
|
"button",
|
|
490
797
|
{
|
|
798
|
+
type: "button",
|
|
799
|
+
"aria-expanded": open,
|
|
491
800
|
onClick: () => setExpanded((prev) => ({ ...prev, [group.id]: !open })),
|
|
492
|
-
className: "
|
|
801
|
+
className: "mst-navitem mst-navitem--folder",
|
|
493
802
|
children: [
|
|
494
|
-
/* @__PURE__ */
|
|
495
|
-
|
|
496
|
-
|
|
803
|
+
/* @__PURE__ */ jsx3(
|
|
804
|
+
Icon,
|
|
805
|
+
{
|
|
806
|
+
name: "chevronRight",
|
|
807
|
+
size: 12,
|
|
808
|
+
className: open ? "mst-navitem__chev mst-navitem__chev--open" : "mst-navitem__chev"
|
|
809
|
+
}
|
|
810
|
+
),
|
|
811
|
+
/* @__PURE__ */ jsx3("span", { className: "mst-navitem__label", children: group.label }),
|
|
812
|
+
/* @__PURE__ */ jsx3("span", { className: "mst-navitem__count", children: group.entities.length })
|
|
497
813
|
]
|
|
498
814
|
}
|
|
499
815
|
),
|
|
500
|
-
open && /* @__PURE__ */
|
|
816
|
+
open && /* @__PURE__ */ jsx3("div", { className: "mst-side__children", children: group.entities.map((e) => /* @__PURE__ */ jsx3(EntityButton, { entity: e, active: e.id === activeId, onSelect: select }, e.id)) })
|
|
501
817
|
] }, group.id);
|
|
502
818
|
}),
|
|
503
|
-
menu.ungrouped.map((e) => /* @__PURE__ */
|
|
819
|
+
menu.ungrouped.map((e) => /* @__PURE__ */ jsx3(EntityButton, { entity: e, active: e.id === activeId, onSelect: select }, e.id))
|
|
504
820
|
] }) }),
|
|
505
|
-
/* @__PURE__ */
|
|
821
|
+
/* @__PURE__ */ jsxs3("div", { className: "mst-side__foot", children: [
|
|
822
|
+
/* @__PURE__ */ jsx3("span", { className: "mst-pulse", "aria-hidden": true }),
|
|
823
|
+
/* @__PURE__ */ jsx3("span", { children: t.entitiesFooter(entities.length) })
|
|
824
|
+
] })
|
|
506
825
|
] });
|
|
507
826
|
}
|
|
508
827
|
|
|
509
|
-
// src/components/
|
|
510
|
-
import {
|
|
828
|
+
// src/components/Header.tsx
|
|
829
|
+
import { useState as useState5 } from "react";
|
|
511
830
|
|
|
512
|
-
// src/
|
|
513
|
-
import {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
831
|
+
// src/theme.tsx
|
|
832
|
+
import {
|
|
833
|
+
createContext as createContext3,
|
|
834
|
+
useCallback as useCallback2,
|
|
835
|
+
useContext as useContext3,
|
|
836
|
+
useEffect as useEffect2,
|
|
837
|
+
useMemo as useMemo2,
|
|
838
|
+
useRef,
|
|
839
|
+
useState as useState3
|
|
840
|
+
} from "react";
|
|
841
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
842
|
+
var MAESTRO_THEMES = [
|
|
843
|
+
{ id: "cobalto", label: "Grafite & Cobalto", swatch: "#3557e8" },
|
|
844
|
+
{ id: "latao", label: "Ard\xF3sia & Lat\xE3o", swatch: "#9a6a22" },
|
|
845
|
+
{ id: "teal", label: "Tinta & Teal", swatch: "#0e7c74" }
|
|
846
|
+
];
|
|
847
|
+
var STORAGE_KEY = "maestro-admin:appearance";
|
|
848
|
+
var DEFAULTS = {
|
|
849
|
+
preset: "cobalto",
|
|
850
|
+
scheme: "system",
|
|
851
|
+
density: "comfortable"
|
|
852
|
+
};
|
|
853
|
+
function normalizeConfig(input) {
|
|
854
|
+
if (!input) return {};
|
|
855
|
+
return typeof input === "string" ? { preset: input } : input;
|
|
856
|
+
}
|
|
857
|
+
function readStored() {
|
|
858
|
+
try {
|
|
859
|
+
const raw = globalThis.localStorage?.getItem(STORAGE_KEY);
|
|
860
|
+
const parsed = raw ? JSON.parse(raw) : null;
|
|
861
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
862
|
+
} catch {
|
|
863
|
+
return {};
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
function writeStored(value) {
|
|
867
|
+
try {
|
|
868
|
+
globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(value));
|
|
869
|
+
} catch {
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
var ThemeContext = createContext3({
|
|
873
|
+
...DEFAULTS,
|
|
874
|
+
picker: true,
|
|
875
|
+
setPreset: () => void 0,
|
|
876
|
+
setScheme: () => void 0,
|
|
877
|
+
setDensity: () => void 0
|
|
878
|
+
});
|
|
879
|
+
function useTheme() {
|
|
880
|
+
return useContext3(ThemeContext);
|
|
881
|
+
}
|
|
882
|
+
function MaestroThemeProvider({
|
|
883
|
+
theme,
|
|
884
|
+
className,
|
|
885
|
+
children,
|
|
886
|
+
rootRef
|
|
887
|
+
}) {
|
|
888
|
+
const config = useMemo2(() => normalizeConfig(theme), [theme]);
|
|
889
|
+
const remember = config.remember !== false;
|
|
890
|
+
const [state, setState] = useState3(() => {
|
|
891
|
+
const stored = remember ? readStored() : {};
|
|
892
|
+
return {
|
|
893
|
+
preset: stored.preset ?? config.preset ?? DEFAULTS.preset,
|
|
894
|
+
scheme: stored.scheme ?? config.scheme ?? DEFAULTS.scheme,
|
|
895
|
+
density: stored.density ?? config.density ?? DEFAULTS.density
|
|
896
|
+
};
|
|
897
|
+
});
|
|
898
|
+
const update = useCallback2(
|
|
899
|
+
(patch) => {
|
|
900
|
+
setState((prev) => {
|
|
901
|
+
const next = { ...prev, ...patch };
|
|
902
|
+
if (remember) writeStored(next);
|
|
903
|
+
return next;
|
|
904
|
+
});
|
|
905
|
+
},
|
|
906
|
+
[remember]
|
|
907
|
+
);
|
|
908
|
+
const value = useMemo2(
|
|
909
|
+
() => ({
|
|
910
|
+
preset: state.preset ?? DEFAULTS.preset,
|
|
911
|
+
scheme: state.scheme ?? DEFAULTS.scheme,
|
|
912
|
+
density: state.density ?? DEFAULTS.density,
|
|
913
|
+
picker: config.picker !== false,
|
|
914
|
+
setPreset: (preset) => update({ preset }),
|
|
915
|
+
setScheme: (scheme) => update({ scheme }),
|
|
916
|
+
setDensity: (density) => update({ density })
|
|
917
|
+
}),
|
|
918
|
+
[state, config.picker, update]
|
|
919
|
+
);
|
|
920
|
+
const faviconRef = useRef(config.favicon);
|
|
921
|
+
faviconRef.current = config.favicon;
|
|
922
|
+
useEffect2(() => {
|
|
923
|
+
const href = faviconRef.current?.(value.preset);
|
|
924
|
+
if (!href) return;
|
|
925
|
+
const link = globalThis.document?.querySelector('link[rel="icon"]');
|
|
926
|
+
if (link) link.href = href;
|
|
927
|
+
}, [value.preset]);
|
|
928
|
+
return /* @__PURE__ */ jsx4(ThemeContext.Provider, { value, children: /* @__PURE__ */ jsx4(
|
|
929
|
+
"div",
|
|
930
|
+
{
|
|
931
|
+
ref: rootRef,
|
|
932
|
+
className,
|
|
933
|
+
"data-maestro-theme": value.preset,
|
|
934
|
+
"data-maestro-scheme": value.scheme,
|
|
935
|
+
"data-maestro-density": value.density,
|
|
936
|
+
style: config.tokens,
|
|
937
|
+
children
|
|
938
|
+
}
|
|
939
|
+
) });
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// src/components/menu.tsx
|
|
943
|
+
import { useEffect as useEffect3, useLayoutEffect, useRef as useRef2, useState as useState4 } from "react";
|
|
944
|
+
import { createPortal } from "react-dom";
|
|
945
|
+
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
946
|
+
function PopMenu({ anchor, items, onClose }) {
|
|
947
|
+
const ref = useRef2(null);
|
|
948
|
+
const [pos, setPos] = useState4(anchor);
|
|
949
|
+
const root = useAdminRoot();
|
|
950
|
+
useLayoutEffect(() => {
|
|
951
|
+
const el = ref.current;
|
|
952
|
+
if (!el) return;
|
|
953
|
+
const rect = el.getBoundingClientRect();
|
|
954
|
+
setPos({
|
|
955
|
+
x: Math.max(4, Math.min(anchor.x, window.innerWidth - rect.width - 8)),
|
|
956
|
+
y: Math.max(4, Math.min(anchor.y, window.innerHeight - rect.height - 8))
|
|
957
|
+
});
|
|
958
|
+
}, [anchor]);
|
|
959
|
+
useEffect3(() => {
|
|
960
|
+
const onDown = (e) => {
|
|
961
|
+
if (ref.current && !ref.current.contains(e.target)) onClose();
|
|
962
|
+
};
|
|
963
|
+
const onKey = (e) => {
|
|
964
|
+
if (e.key === "Escape") onClose();
|
|
965
|
+
};
|
|
966
|
+
document.addEventListener("mousedown", onDown);
|
|
967
|
+
document.addEventListener("keydown", onKey);
|
|
968
|
+
window.addEventListener("scroll", onClose, true);
|
|
969
|
+
window.addEventListener("resize", onClose);
|
|
970
|
+
return () => {
|
|
971
|
+
document.removeEventListener("mousedown", onDown);
|
|
972
|
+
document.removeEventListener("keydown", onKey);
|
|
973
|
+
window.removeEventListener("scroll", onClose, true);
|
|
974
|
+
window.removeEventListener("resize", onClose);
|
|
975
|
+
};
|
|
976
|
+
}, [onClose]);
|
|
977
|
+
return createPortal(
|
|
978
|
+
/* @__PURE__ */ jsx5("div", { ref, role: "menu", style: { left: pos.x, top: pos.y }, className: "mst-menu", children: items.map((item) => /* @__PURE__ */ jsxs4("div", { children: [
|
|
979
|
+
item.separated && /* @__PURE__ */ jsx5("div", { className: "mst-menu__sep" }),
|
|
980
|
+
/* @__PURE__ */ jsxs4(
|
|
981
|
+
"button",
|
|
982
|
+
{
|
|
983
|
+
type: "button",
|
|
984
|
+
role: "menuitem",
|
|
985
|
+
onClick: () => {
|
|
986
|
+
onClose();
|
|
987
|
+
item.onSelect();
|
|
988
|
+
},
|
|
989
|
+
className: `mst-menu__item${item.danger ? " mst-menu__item--danger" : ""}`,
|
|
990
|
+
children: [
|
|
991
|
+
item.icon && /* @__PURE__ */ jsx5(Icon, { name: item.icon, size: 14 }),
|
|
992
|
+
item.label
|
|
993
|
+
]
|
|
994
|
+
}
|
|
995
|
+
)
|
|
996
|
+
] }, item.id)) }),
|
|
997
|
+
root ?? document.body
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
function MenuButton({ items, label }) {
|
|
1001
|
+
const [anchor, setAnchor] = useState4(void 0);
|
|
1002
|
+
return /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1003
|
+
/* @__PURE__ */ jsx5(
|
|
1004
|
+
"button",
|
|
1005
|
+
{
|
|
1006
|
+
type: "button",
|
|
1007
|
+
"aria-label": label,
|
|
1008
|
+
title: label,
|
|
1009
|
+
onClick: (e) => {
|
|
1010
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
1011
|
+
setAnchor({ x: rect.right - 184, y: rect.bottom + 4 });
|
|
1012
|
+
},
|
|
1013
|
+
className: "mst-iconbtn",
|
|
1014
|
+
children: /* @__PURE__ */ jsx5(Icon, { name: "dots", size: 14 })
|
|
1015
|
+
}
|
|
1016
|
+
),
|
|
1017
|
+
anchor && /* @__PURE__ */ jsx5(PopMenu, { anchor, items, onClose: () => setAnchor(void 0) })
|
|
1018
|
+
] });
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// src/components/Header.tsx
|
|
1022
|
+
import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1023
|
+
var NEXT_SCHEME = { system: "light", light: "dark", dark: "system" };
|
|
1024
|
+
var SCHEME_ICON = {
|
|
1025
|
+
system: "swatch",
|
|
1026
|
+
light: "sun",
|
|
1027
|
+
dark: "moon"
|
|
519
1028
|
};
|
|
1029
|
+
function Header({ crumbs, onOpenPalette }) {
|
|
1030
|
+
const t = useT();
|
|
1031
|
+
const theme = useTheme();
|
|
1032
|
+
const [anchor, setAnchor] = useState5(void 0);
|
|
1033
|
+
const themeItems = MAESTRO_THEMES.map((option) => ({
|
|
1034
|
+
id: option.id,
|
|
1035
|
+
label: /* @__PURE__ */ jsxs5(Fragment4, { children: [
|
|
1036
|
+
/* @__PURE__ */ jsx6("span", { className: "mst-swatch", style: { background: option.swatch } }),
|
|
1037
|
+
option.label,
|
|
1038
|
+
theme.preset === option.id && /* @__PURE__ */ jsx6(Icon, { name: "check", size: 13 })
|
|
1039
|
+
] }),
|
|
1040
|
+
onSelect: () => theme.setPreset(option.id)
|
|
1041
|
+
}));
|
|
1042
|
+
const schemeLabel = theme.scheme === "system" ? t.schemeSystem : theme.scheme === "light" ? t.schemeLight : t.schemeDark;
|
|
1043
|
+
return /* @__PURE__ */ jsxs5("header", { className: "mst-header", children: [
|
|
1044
|
+
/* @__PURE__ */ jsx6("nav", { className: "mst-crumbs", "aria-label": t.breadcrumb, children: crumbs.map((crumb, index) => /* @__PURE__ */ jsxs5("span", { className: "mst-crumbs__item", children: [
|
|
1045
|
+
index > 0 && /* @__PURE__ */ jsx6(Icon, { name: "chevronRight", size: 12, className: "mst-crumbs__sep" }),
|
|
1046
|
+
crumb.onClick ? /* @__PURE__ */ jsxs5("button", { type: "button", className: "mst-crumbs__link", onClick: crumb.onClick, children: [
|
|
1047
|
+
crumb.icon && /* @__PURE__ */ jsx6(Icon, { name: crumb.icon, size: 13 }),
|
|
1048
|
+
crumb.label
|
|
1049
|
+
] }) : /* @__PURE__ */ jsxs5("span", { className: "mst-crumbs__now", children: [
|
|
1050
|
+
crumb.icon && /* @__PURE__ */ jsx6(Icon, { name: crumb.icon, size: 13 }),
|
|
1051
|
+
crumb.label
|
|
1052
|
+
] })
|
|
1053
|
+
] }, `${crumb.label}-${index}`)) }),
|
|
1054
|
+
/* @__PURE__ */ jsxs5("button", { type: "button", className: "mst-searchchip", onClick: onOpenPalette, children: [
|
|
1055
|
+
/* @__PURE__ */ jsx6(Icon, { name: "search", size: 13 }),
|
|
1056
|
+
/* @__PURE__ */ jsx6("span", { children: t.search }),
|
|
1057
|
+
/* @__PURE__ */ jsx6("kbd", { className: "mst-kbd", children: t.commandKey })
|
|
1058
|
+
] }),
|
|
1059
|
+
theme.picker && /* @__PURE__ */ jsxs5(Fragment4, { children: [
|
|
1060
|
+
/* @__PURE__ */ jsx6(
|
|
1061
|
+
"button",
|
|
1062
|
+
{
|
|
1063
|
+
type: "button",
|
|
1064
|
+
"aria-label": t.density,
|
|
1065
|
+
title: t.density,
|
|
1066
|
+
"aria-pressed": theme.density === "compact",
|
|
1067
|
+
className: "mst-iconbtn",
|
|
1068
|
+
onClick: () => theme.setDensity(theme.density === "compact" ? "comfortable" : "compact"),
|
|
1069
|
+
children: /* @__PURE__ */ jsx6(Icon, { name: "rows", size: 14 })
|
|
1070
|
+
}
|
|
1071
|
+
),
|
|
1072
|
+
/* @__PURE__ */ jsx6(
|
|
1073
|
+
"button",
|
|
1074
|
+
{
|
|
1075
|
+
type: "button",
|
|
1076
|
+
"aria-label": t.palette,
|
|
1077
|
+
title: t.palette,
|
|
1078
|
+
className: "mst-iconbtn",
|
|
1079
|
+
onClick: (e) => {
|
|
1080
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
1081
|
+
setAnchor({ x: rect.right - 200, y: rect.bottom + 6 });
|
|
1082
|
+
},
|
|
1083
|
+
children: /* @__PURE__ */ jsx6(Icon, { name: "swatch", size: 14 })
|
|
1084
|
+
}
|
|
1085
|
+
),
|
|
1086
|
+
/* @__PURE__ */ jsx6(
|
|
1087
|
+
"button",
|
|
1088
|
+
{
|
|
1089
|
+
type: "button",
|
|
1090
|
+
"aria-label": `${t.colorScheme}: ${schemeLabel}`,
|
|
1091
|
+
title: `${t.colorScheme}: ${schemeLabel}`,
|
|
1092
|
+
className: "mst-iconbtn",
|
|
1093
|
+
onClick: () => theme.setScheme(NEXT_SCHEME[theme.scheme]),
|
|
1094
|
+
children: /* @__PURE__ */ jsx6(Icon, { name: SCHEME_ICON[theme.scheme], size: 14 })
|
|
1095
|
+
}
|
|
1096
|
+
)
|
|
1097
|
+
] }),
|
|
1098
|
+
anchor && /* @__PURE__ */ jsx6(PopMenu, { anchor, items: themeItems, onClose: () => setAnchor(void 0) })
|
|
1099
|
+
] });
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// src/components/Hub.tsx
|
|
1103
|
+
import { useMemo as useMemo3 } from "react";
|
|
1104
|
+
|
|
1105
|
+
// src/components/ui.tsx
|
|
1106
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
520
1107
|
function Button({
|
|
521
1108
|
variant = "secondary",
|
|
1109
|
+
icon,
|
|
522
1110
|
className = "",
|
|
1111
|
+
children,
|
|
523
1112
|
...props
|
|
524
1113
|
}) {
|
|
525
|
-
return /* @__PURE__ */
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
...props
|
|
530
|
-
}
|
|
531
|
-
);
|
|
1114
|
+
return /* @__PURE__ */ jsxs6("button", { type: "button", className: `mst-btn mst-btn--${variant} ${className}`.trim(), ...props, children: [
|
|
1115
|
+
icon && /* @__PURE__ */ jsx7(Icon, { name: icon, size: 14 }),
|
|
1116
|
+
children
|
|
1117
|
+
] });
|
|
532
1118
|
}
|
|
533
|
-
function
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
1119
|
+
function IconButton({
|
|
1120
|
+
icon,
|
|
1121
|
+
label,
|
|
1122
|
+
className = "",
|
|
1123
|
+
...props
|
|
1124
|
+
}) {
|
|
1125
|
+
return /* @__PURE__ */ jsx7("button", { type: "button", "aria-label": label, title: label, className: `mst-iconbtn ${className}`.trim(), ...props, children: /* @__PURE__ */ jsx7(Icon, { name: icon, size: 14 }) });
|
|
1126
|
+
}
|
|
1127
|
+
function Badge({ children, tone = "neutral", dot = false }) {
|
|
1128
|
+
const toneClass = tone === "neutral" ? "" : ` mst-badge--${tone}`;
|
|
1129
|
+
return /* @__PURE__ */ jsx7("span", { className: `mst-badge${toneClass}${dot ? " mst-badge--dot" : ""}`, children });
|
|
542
1130
|
}
|
|
543
1131
|
function Spinner({ label }) {
|
|
544
|
-
return /* @__PURE__ */
|
|
545
|
-
/* @__PURE__ */ jsx2("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-slate-300 border-t-indigo-600" }),
|
|
546
|
-
label ?? "Loading\u2026"
|
|
547
|
-
] });
|
|
1132
|
+
return /* @__PURE__ */ jsx7("div", { className: "mst-spinner", role: "status", children: label ?? "Loading\u2026" });
|
|
548
1133
|
}
|
|
549
1134
|
function EmptyState({ title, hint }) {
|
|
550
|
-
return /* @__PURE__ */
|
|
551
|
-
/* @__PURE__ */
|
|
552
|
-
hint && /* @__PURE__ */
|
|
1135
|
+
return /* @__PURE__ */ jsxs6("div", { className: "mst-empty", children: [
|
|
1136
|
+
/* @__PURE__ */ jsx7("p", { className: "mst-empty__title", children: title }),
|
|
1137
|
+
hint && /* @__PURE__ */ jsx7("p", { className: "mst-empty__hint", children: hint })
|
|
553
1138
|
] });
|
|
554
1139
|
}
|
|
555
1140
|
function ErrorBanner({ error }) {
|
|
556
|
-
return /* @__PURE__ */
|
|
1141
|
+
return /* @__PURE__ */ jsxs6("div", { className: "mst-error", role: "alert", children: [
|
|
1142
|
+
/* @__PURE__ */ jsx7(Icon, { name: "alert", size: 15 }),
|
|
1143
|
+
/* @__PURE__ */ jsx7("span", { children: error.message })
|
|
1144
|
+
] });
|
|
1145
|
+
}
|
|
1146
|
+
function Toast({ children }) {
|
|
1147
|
+
return /* @__PURE__ */ jsx7("div", { className: "mst-toast", role: "status", children });
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// src/components/Hub.tsx
|
|
1151
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1152
|
+
var CAPS = ["list", "create", "update", "delete"];
|
|
1153
|
+
function EntityCard({ entity, onOpen }) {
|
|
1154
|
+
const t = useT();
|
|
1155
|
+
const capLabel = { list: t.capRead, create: t.capCreate, update: t.capUpdate, delete: t.capDelete };
|
|
1156
|
+
return /* @__PURE__ */ jsxs7("button", { type: "button", className: "mst-ecard", onClick: () => onOpen(entity.id), children: [
|
|
1157
|
+
/* @__PURE__ */ jsx8("span", { className: "mst-ecard__tile", children: /* @__PURE__ */ jsx8(Icon, { name: entityIcon(entity), size: 16 }) }),
|
|
1158
|
+
/* @__PURE__ */ jsxs7("span", { className: "mst-ecard__body", children: [
|
|
1159
|
+
/* @__PURE__ */ jsx8("span", { className: "mst-ecard__name", children: entity.label.plural }),
|
|
1160
|
+
/* @__PURE__ */ jsx8("span", { className: "mst-ecard__desc", children: entity.description ?? t.fieldCount(entity.fields.length) }),
|
|
1161
|
+
/* @__PURE__ */ jsx8("span", { className: "mst-caps", children: CAPS.map((cap) => /* @__PURE__ */ jsx8("span", { className: "mst-cap", "data-on": String(entity.capabilities[cap]), children: capLabel[cap] }, cap)) }),
|
|
1162
|
+
/* @__PURE__ */ jsxs7("span", { className: "mst-source", children: [
|
|
1163
|
+
/* @__PURE__ */ jsx8(Icon, { name: "database", size: 11 }),
|
|
1164
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
1165
|
+
entity.datasource,
|
|
1166
|
+
" \xB7 ",
|
|
1167
|
+
entity.table
|
|
1168
|
+
] })
|
|
1169
|
+
] })
|
|
1170
|
+
] })
|
|
1171
|
+
] });
|
|
1172
|
+
}
|
|
1173
|
+
function Hub({
|
|
1174
|
+
entities,
|
|
1175
|
+
labels,
|
|
1176
|
+
onOpen
|
|
1177
|
+
}) {
|
|
1178
|
+
const t = useT();
|
|
1179
|
+
const menu = useMemo3(() => buildMenu(entities, labels), [entities, labels]);
|
|
1180
|
+
const sources = useMemo3(() => new Set(entities.map((e) => e.datasource)).size, [entities]);
|
|
1181
|
+
if (entities.length === 0) {
|
|
1182
|
+
return /* @__PURE__ */ jsx8(EmptyState, { title: t.noEntities, hint: t.noEntitiesHint });
|
|
1183
|
+
}
|
|
1184
|
+
return /* @__PURE__ */ jsxs7("section", { className: "mst-page", children: [
|
|
1185
|
+
/* @__PURE__ */ jsx8("div", { className: "mst-pagehead", children: /* @__PURE__ */ jsxs7("div", { children: [
|
|
1186
|
+
/* @__PURE__ */ jsx8("h1", { className: "mst-pagehead__title", children: t.entities }),
|
|
1187
|
+
/* @__PURE__ */ jsx8("p", { className: "mst-pagehead__meta", children: t.hubMeta(entities.length, sources) })
|
|
1188
|
+
] }) }),
|
|
1189
|
+
/* @__PURE__ */ jsxs7("div", { className: "mst-hub", children: [
|
|
1190
|
+
menu.groups.map((group) => /* @__PURE__ */ jsxs7("div", { children: [
|
|
1191
|
+
/* @__PURE__ */ jsx8("p", { className: "mst-grouplabel", children: group.label }),
|
|
1192
|
+
/* @__PURE__ */ jsx8("div", { className: "mst-cards", children: group.entities.map((entity) => /* @__PURE__ */ jsx8(EntityCard, { entity, onOpen }, entity.id)) })
|
|
1193
|
+
] }, group.id)),
|
|
1194
|
+
menu.ungrouped.length > 0 && /* @__PURE__ */ jsxs7("div", { children: [
|
|
1195
|
+
menu.groups.length > 0 && /* @__PURE__ */ jsx8("p", { className: "mst-grouplabel", children: t.otherEntities }),
|
|
1196
|
+
/* @__PURE__ */ jsx8("div", { className: "mst-cards", children: menu.ungrouped.map((entity) => /* @__PURE__ */ jsx8(EntityCard, { entity, onOpen }, entity.id)) })
|
|
1197
|
+
] })
|
|
1198
|
+
] })
|
|
1199
|
+
] });
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// src/components/CommandPalette.tsx
|
|
1203
|
+
import { useEffect as useEffect4, useMemo as useMemo4, useRef as useRef3, useState as useState6 } from "react";
|
|
1204
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
1205
|
+
import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1206
|
+
function normalize2(text) {
|
|
1207
|
+
return text.toLowerCase().normalize("NFD").replace(/\p{M}/gu, "");
|
|
1208
|
+
}
|
|
1209
|
+
function Highlight2({ label, query }) {
|
|
1210
|
+
if (!query) return label;
|
|
1211
|
+
const folded = normalize2(label);
|
|
1212
|
+
const idx = folded.indexOf(normalize2(query));
|
|
1213
|
+
if (idx === -1) return label;
|
|
1214
|
+
return /* @__PURE__ */ jsxs8(Fragment5, { children: [
|
|
1215
|
+
label.slice(0, idx),
|
|
1216
|
+
/* @__PURE__ */ jsx9("mark", { children: label.slice(idx, idx + query.length) }),
|
|
1217
|
+
label.slice(idx + query.length)
|
|
1218
|
+
] });
|
|
1219
|
+
}
|
|
1220
|
+
function entityCommands(entities, groupOf, onSelect) {
|
|
1221
|
+
return entities.map((entity) => ({
|
|
1222
|
+
id: `entity:${entity.id}`,
|
|
1223
|
+
label: entity.label.plural,
|
|
1224
|
+
icon: entityIcon(entity),
|
|
1225
|
+
hint: groupOf.get(entity.id),
|
|
1226
|
+
keywords: `${entity.label.singular} ${entity.table} ${entity.id}`,
|
|
1227
|
+
run: () => onSelect(entity.id)
|
|
1228
|
+
}));
|
|
1229
|
+
}
|
|
1230
|
+
function CommandPalette({ sections, onClose }) {
|
|
1231
|
+
const t = useT();
|
|
1232
|
+
const root = useAdminRoot();
|
|
1233
|
+
const inputRef = useRef3(null);
|
|
1234
|
+
const listRef = useRef3(null);
|
|
1235
|
+
const [query, setQuery] = useState6("");
|
|
1236
|
+
const [cursor, setCursor] = useState6(0);
|
|
1237
|
+
const visible = useMemo4(() => {
|
|
1238
|
+
if (!query) return sections;
|
|
1239
|
+
const q = normalize2(query);
|
|
1240
|
+
return sections.map((section) => ({
|
|
1241
|
+
...section,
|
|
1242
|
+
commands: section.commands.filter((c) => normalize2(`${c.label} ${c.keywords ?? ""}`).includes(q))
|
|
1243
|
+
})).filter((section) => section.commands.length > 0);
|
|
1244
|
+
}, [sections, query]);
|
|
1245
|
+
const flat = useMemo4(() => visible.flatMap((section) => section.commands), [visible]);
|
|
1246
|
+
const active = flat[Math.min(cursor, flat.length - 1)];
|
|
1247
|
+
useEffect4(() => setCursor(0), [query]);
|
|
1248
|
+
useEffect4(() => inputRef.current?.focus(), []);
|
|
1249
|
+
useEffect4(() => {
|
|
1250
|
+
listRef.current?.querySelector('[data-active="true"]')?.scrollIntoView({ block: "nearest" });
|
|
1251
|
+
}, [cursor, query]);
|
|
1252
|
+
const onKeyDown = (e) => {
|
|
1253
|
+
if (e.key === "Escape") {
|
|
1254
|
+
e.preventDefault();
|
|
1255
|
+
onClose();
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
if (e.key === "ArrowDown" || e.key === "n" && e.ctrlKey) {
|
|
1259
|
+
e.preventDefault();
|
|
1260
|
+
setCursor((c) => flat.length === 0 ? 0 : (Math.min(c, flat.length - 1) + 1) % flat.length);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
if (e.key === "ArrowUp" || e.key === "p" && e.ctrlKey) {
|
|
1264
|
+
e.preventDefault();
|
|
1265
|
+
setCursor((c) => flat.length === 0 ? 0 : (Math.min(c, flat.length - 1) + flat.length - 1) % flat.length);
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
if (e.key === "Enter" && active) {
|
|
1269
|
+
e.preventDefault();
|
|
1270
|
+
onClose();
|
|
1271
|
+
active.run();
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
return createPortal2(
|
|
1275
|
+
/* @__PURE__ */ jsx9(
|
|
1276
|
+
"div",
|
|
1277
|
+
{
|
|
1278
|
+
className: "mst-overlay",
|
|
1279
|
+
role: "presentation",
|
|
1280
|
+
onMouseDown: (e) => {
|
|
1281
|
+
if (e.target === e.currentTarget) onClose();
|
|
1282
|
+
},
|
|
1283
|
+
children: /* @__PURE__ */ jsxs8("div", { className: "mst-palette", role: "dialog", "aria-modal": "true", "aria-label": t.commandPalette, children: [
|
|
1284
|
+
/* @__PURE__ */ jsxs8("div", { className: "mst-palette__input", children: [
|
|
1285
|
+
/* @__PURE__ */ jsx9(Icon, { name: "search", size: 15 }),
|
|
1286
|
+
/* @__PURE__ */ jsx9(
|
|
1287
|
+
"input",
|
|
1288
|
+
{
|
|
1289
|
+
ref: inputRef,
|
|
1290
|
+
type: "text",
|
|
1291
|
+
value: query,
|
|
1292
|
+
placeholder: t.palettePlaceholder,
|
|
1293
|
+
"aria-label": t.commandPalette,
|
|
1294
|
+
onChange: (e) => setQuery(e.target.value),
|
|
1295
|
+
onKeyDown
|
|
1296
|
+
}
|
|
1297
|
+
),
|
|
1298
|
+
/* @__PURE__ */ jsx9("kbd", { className: "mst-kbd", children: "esc" })
|
|
1299
|
+
] }),
|
|
1300
|
+
/* @__PURE__ */ jsx9("div", { className: "mst-palette__list", ref: listRef, children: flat.length === 0 ? /* @__PURE__ */ jsx9("p", { className: "mst-palette__empty", children: t.noMenuResults }) : visible.map((section) => /* @__PURE__ */ jsxs8("div", { children: [
|
|
1301
|
+
/* @__PURE__ */ jsx9("p", { className: "mst-palette__section", children: section.label }),
|
|
1302
|
+
section.commands.map((command) => /* @__PURE__ */ jsxs8(
|
|
1303
|
+
"button",
|
|
1304
|
+
{
|
|
1305
|
+
type: "button",
|
|
1306
|
+
className: "mst-palette__row",
|
|
1307
|
+
"data-active": String(command.id === active?.id),
|
|
1308
|
+
onMouseEnter: () => setCursor(flat.findIndex((c) => c.id === command.id)),
|
|
1309
|
+
onClick: () => {
|
|
1310
|
+
onClose();
|
|
1311
|
+
command.run();
|
|
1312
|
+
},
|
|
1313
|
+
children: [
|
|
1314
|
+
command.icon && /* @__PURE__ */ jsx9(Icon, { name: command.icon, size: 14 }),
|
|
1315
|
+
/* @__PURE__ */ jsx9("span", { className: "mst-palette__label", children: /* @__PURE__ */ jsx9(Highlight2, { label: command.label, query }) }),
|
|
1316
|
+
command.hint && /* @__PURE__ */ jsx9("span", { className: "mst-palette__hint", children: command.hint })
|
|
1317
|
+
]
|
|
1318
|
+
},
|
|
1319
|
+
command.id
|
|
1320
|
+
))
|
|
1321
|
+
] }, section.id)) })
|
|
1322
|
+
] })
|
|
1323
|
+
}
|
|
1324
|
+
),
|
|
1325
|
+
root ?? document.body
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// src/components/EntityList.tsx
|
|
1330
|
+
import { useMemo as useMemo6, useState as useState9 } from "react";
|
|
1331
|
+
|
|
1332
|
+
// src/columns.ts
|
|
1333
|
+
import { useCallback as useCallback3, useMemo as useMemo5, useState as useState7 } from "react";
|
|
1334
|
+
var STORAGE_PREFIX = "maestro-admin:columns";
|
|
1335
|
+
function read(key) {
|
|
1336
|
+
try {
|
|
1337
|
+
const raw = globalThis.localStorage?.getItem(key);
|
|
1338
|
+
const parsed = raw ? JSON.parse(raw) : void 0;
|
|
1339
|
+
return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : void 0;
|
|
1340
|
+
} catch {
|
|
1341
|
+
return void 0;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
function write(key, names) {
|
|
1345
|
+
try {
|
|
1346
|
+
if (names) globalThis.localStorage?.setItem(key, JSON.stringify(names));
|
|
1347
|
+
else globalThis.localStorage?.removeItem(key);
|
|
1348
|
+
} catch {
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
function useColumnPrefs(entity, max = 6) {
|
|
1352
|
+
const key = `${STORAGE_PREFIX}:${entity.id}`;
|
|
1353
|
+
const [selection, setSelection] = useState7(() => read(key));
|
|
1354
|
+
const available = useMemo5(() => listFields(entity), [entity]);
|
|
1355
|
+
const fallback = useMemo5(() => primaryListFields(entity, max), [entity, max]);
|
|
1356
|
+
const columns = useMemo5(() => {
|
|
1357
|
+
if (!selection) return fallback;
|
|
1358
|
+
const chosen = available.filter((f) => selection.includes(f.name));
|
|
1359
|
+
return chosen.length > 0 ? chosen : fallback;
|
|
1360
|
+
}, [selection, available, fallback]);
|
|
1361
|
+
const toggle = useCallback3(
|
|
1362
|
+
(name) => {
|
|
1363
|
+
setSelection((prev) => {
|
|
1364
|
+
const current = prev ?? columns.map((f) => f.name);
|
|
1365
|
+
const next = current.includes(name) ? current.filter((n) => n !== name) : [...current, name];
|
|
1366
|
+
write(key, next);
|
|
1367
|
+
return next;
|
|
1368
|
+
});
|
|
1369
|
+
},
|
|
1370
|
+
[key, columns]
|
|
1371
|
+
);
|
|
1372
|
+
const reset = useCallback3(() => {
|
|
1373
|
+
write(key, void 0);
|
|
1374
|
+
setSelection(void 0);
|
|
1375
|
+
}, [key]);
|
|
1376
|
+
const visibleNames = useMemo5(() => new Set(columns.map((f) => f.name)), [columns]);
|
|
1377
|
+
return {
|
|
1378
|
+
columns,
|
|
1379
|
+
available,
|
|
1380
|
+
visible: (name) => visibleNames.has(name),
|
|
1381
|
+
toggle,
|
|
1382
|
+
reset,
|
|
1383
|
+
customized: selection !== void 0
|
|
1384
|
+
};
|
|
557
1385
|
}
|
|
558
1386
|
|
|
559
1387
|
// src/components/RelationValue.tsx
|
|
560
|
-
import { useEffect as
|
|
561
|
-
import { jsx as
|
|
1388
|
+
import { useEffect as useEffect5, useState as useState8 } from "react";
|
|
1389
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
562
1390
|
var displayCache = /* @__PURE__ */ new Map();
|
|
563
1391
|
function resolveDisplay(client, entityId, displayField, id) {
|
|
564
1392
|
const key = `${entityId}/${id}`;
|
|
@@ -579,8 +1407,8 @@ function RelationValue({ field, value }) {
|
|
|
579
1407
|
const client = useClient();
|
|
580
1408
|
const target = useEntity(field.relationEntity);
|
|
581
1409
|
const id = String(value);
|
|
582
|
-
const [display, setDisplay] =
|
|
583
|
-
|
|
1410
|
+
const [display, setDisplay] = useState8(void 0);
|
|
1411
|
+
useEffect5(() => {
|
|
584
1412
|
let cancelled = false;
|
|
585
1413
|
setDisplay(void 0);
|
|
586
1414
|
if (!target || target.displayField === target.primaryKey) return;
|
|
@@ -591,24 +1419,17 @@ function RelationValue({ field, value }) {
|
|
|
591
1419
|
cancelled = true;
|
|
592
1420
|
};
|
|
593
1421
|
}, [client, target, id]);
|
|
594
|
-
return /* @__PURE__ */
|
|
595
|
-
"span",
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
target.label.singular,
|
|
602
|
-
":"
|
|
603
|
-
] }),
|
|
604
|
-
/* @__PURE__ */ jsx3("span", { className: "truncate", children: display ?? shortId(id) })
|
|
605
|
-
]
|
|
606
|
-
}
|
|
607
|
-
);
|
|
1422
|
+
return /* @__PURE__ */ jsxs9("span", { title: id, className: "mst-relation", children: [
|
|
1423
|
+
target && /* @__PURE__ */ jsxs9("span", { className: "mst-subtle", children: [
|
|
1424
|
+
target.label.singular,
|
|
1425
|
+
":"
|
|
1426
|
+
] }),
|
|
1427
|
+
/* @__PURE__ */ jsx10("span", { className: "mst-relation__label", children: display ?? shortId(id) })
|
|
1428
|
+
] });
|
|
608
1429
|
}
|
|
609
1430
|
|
|
610
1431
|
// src/components/ObjectValue.tsx
|
|
611
|
-
import { jsx as
|
|
1432
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
612
1433
|
var ISO_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
|
|
613
1434
|
var MAX_DEPTH = 4;
|
|
614
1435
|
function isPlainObject(value) {
|
|
@@ -616,49 +1437,40 @@ function isPlainObject(value) {
|
|
|
616
1437
|
}
|
|
617
1438
|
function Primitive({ value }) {
|
|
618
1439
|
if (value === null || value === void 0) {
|
|
619
|
-
return /* @__PURE__ */
|
|
1440
|
+
return /* @__PURE__ */ jsx11("span", { className: "mst-subtle", children: "\u2014" });
|
|
620
1441
|
}
|
|
621
1442
|
if (typeof value === "boolean" || typeof value === "number") {
|
|
622
|
-
return /* @__PURE__ */
|
|
1443
|
+
return /* @__PURE__ */ jsx11("code", { className: "mst-mono", style: { color: "var(--maestro-brand)" }, children: String(value) });
|
|
623
1444
|
}
|
|
624
1445
|
const text = String(value);
|
|
625
1446
|
if (ISO_DATETIME.test(text)) {
|
|
626
1447
|
const d = new Date(text);
|
|
627
1448
|
if (!Number.isNaN(d.getTime())) {
|
|
628
|
-
return /* @__PURE__ */
|
|
1449
|
+
return /* @__PURE__ */ jsx11("span", { className: "mst-nums", children: d.toLocaleString() });
|
|
629
1450
|
}
|
|
630
1451
|
}
|
|
631
|
-
return /* @__PURE__ */
|
|
1452
|
+
return /* @__PURE__ */ jsx11("span", { style: { overflowWrap: "anywhere" }, children: text });
|
|
632
1453
|
}
|
|
633
1454
|
function ObjectValue({ value, depth = 0 }) {
|
|
634
1455
|
const t = useT();
|
|
635
1456
|
if (!isPlainObject(value) && !Array.isArray(value)) {
|
|
636
|
-
return /* @__PURE__ */
|
|
1457
|
+
return /* @__PURE__ */ jsx11(Primitive, { value });
|
|
637
1458
|
}
|
|
638
1459
|
if (depth >= MAX_DEPTH) {
|
|
639
|
-
return /* @__PURE__ */
|
|
1460
|
+
return /* @__PURE__ */ jsx11("code", { className: "mst-code mst-code--block", children: JSON.stringify(value) });
|
|
640
1461
|
}
|
|
641
1462
|
const entries = Array.isArray(value) ? value.map((v, i) => [String(i), v]) : Object.entries(value);
|
|
642
1463
|
if (entries.length === 0) {
|
|
643
|
-
return /* @__PURE__ */
|
|
1464
|
+
return /* @__PURE__ */ jsx11("span", { className: "mst-subtle", children: t.emptyObject });
|
|
644
1465
|
}
|
|
645
|
-
return /* @__PURE__ */
|
|
646
|
-
"
|
|
647
|
-
{
|
|
648
|
-
|
|
649
|
-
children: entries.map(([key, entry]) => (
|
|
650
|
-
// flex-wrap + basis: deeply nested values drop to their own full-width line instead of squeezing.
|
|
651
|
-
/* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-start gap-x-3 gap-y-1 px-3 py-1.5", children: [
|
|
652
|
-
/* @__PURE__ */ jsx4("dt", { className: "w-36 shrink-0 truncate pt-px text-xs font-medium text-slate-500", title: key, children: key }),
|
|
653
|
-
/* @__PURE__ */ jsx4("dd", { className: "min-w-0 flex-1 basis-56 text-sm", children: isPlainObject(entry) || Array.isArray(entry) ? /* @__PURE__ */ jsx4(ObjectValue, { value: entry, depth: depth + 1 }) : /* @__PURE__ */ jsx4(Primitive, { value: entry }) })
|
|
654
|
-
] }, key)
|
|
655
|
-
))
|
|
656
|
-
}
|
|
657
|
-
);
|
|
1466
|
+
return /* @__PURE__ */ jsx11("dl", { className: depth === 0 ? "mst-objectvalue mst-objectvalue--root" : "mst-objectvalue mst-objectvalue--nested", children: entries.map(([key, entry]) => /* @__PURE__ */ jsxs10("div", { className: "mst-objectvalue__row", children: [
|
|
1467
|
+
/* @__PURE__ */ jsx11("dt", { className: "mst-objectvalue__key", title: key, children: key }),
|
|
1468
|
+
/* @__PURE__ */ jsx11("dd", { className: "mst-objectvalue__val", children: isPlainObject(entry) || Array.isArray(entry) ? /* @__PURE__ */ jsx11(ObjectValue, { value: entry, depth: depth + 1 }) : /* @__PURE__ */ jsx11(Primitive, { value: entry }) })
|
|
1469
|
+
] }, key)) });
|
|
658
1470
|
}
|
|
659
1471
|
|
|
660
1472
|
// src/components/FieldValue.tsx
|
|
661
|
-
import { jsx as
|
|
1473
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
662
1474
|
function enumLabel(field, value) {
|
|
663
1475
|
const opt = field.enumOptions?.find((o) => o.value === value);
|
|
664
1476
|
return opt ? opt.label : String(value);
|
|
@@ -666,117 +1478,42 @@ function enumLabel(field, value) {
|
|
|
666
1478
|
function FieldValue({ field, value, compact = false }) {
|
|
667
1479
|
const t = useT();
|
|
668
1480
|
if (value === null || value === void 0 || value === "") {
|
|
669
|
-
return /* @__PURE__ */
|
|
1481
|
+
return /* @__PURE__ */ jsx12("span", { className: "mst-subtle", children: "\u2014" });
|
|
670
1482
|
}
|
|
671
1483
|
if (field.sensitive) {
|
|
672
|
-
return /* @__PURE__ */
|
|
1484
|
+
return /* @__PURE__ */ jsx12("span", { className: "mst-subtle", children: "\u2022\u2022\u2022\u2022\u2022\u2022" });
|
|
673
1485
|
}
|
|
674
1486
|
switch (field.type) {
|
|
675
1487
|
case "boolean":
|
|
676
|
-
return value ? /* @__PURE__ */
|
|
1488
|
+
return value ? /* @__PURE__ */ jsx12(Badge, { tone: "ok", dot: true, children: t.yes }) : /* @__PURE__ */ jsx12(Badge, { dot: true, children: t.no });
|
|
677
1489
|
case "enum":
|
|
678
|
-
return /* @__PURE__ */
|
|
1490
|
+
return /* @__PURE__ */ jsx12(Badge, { tone: "brand", children: enumLabel(field, value) });
|
|
679
1491
|
case "relation":
|
|
680
|
-
return field.relationEntity ? /* @__PURE__ */
|
|
1492
|
+
return field.relationEntity ? /* @__PURE__ */ jsx12(RelationValue, { field, value }) : /* @__PURE__ */ jsx12("span", { children: String(value) });
|
|
681
1493
|
case "date":
|
|
682
1494
|
case "datetime": {
|
|
683
1495
|
const d = new Date(String(value));
|
|
684
|
-
return /* @__PURE__ */
|
|
1496
|
+
return /* @__PURE__ */ jsx12("span", { className: "mst-nums", children: Number.isNaN(d.getTime()) ? String(value) : d.toLocaleString() });
|
|
685
1497
|
}
|
|
1498
|
+
case "integer":
|
|
1499
|
+
case "float":
|
|
1500
|
+
case "number":
|
|
1501
|
+
case "decimal":
|
|
1502
|
+
case "currency":
|
|
1503
|
+
return /* @__PURE__ */ jsx12("span", { className: "mst-nums", children: String(value) });
|
|
686
1504
|
case "json":
|
|
687
1505
|
if (compact) {
|
|
688
|
-
return /* @__PURE__ */
|
|
1506
|
+
return /* @__PURE__ */ jsx12("code", { className: "mst-code", children: JSON.stringify(value) });
|
|
689
1507
|
}
|
|
690
1508
|
if (typeof value === "object") {
|
|
691
|
-
return /* @__PURE__ */
|
|
1509
|
+
return /* @__PURE__ */ jsx12(ObjectValue, { value });
|
|
692
1510
|
}
|
|
693
|
-
return /* @__PURE__ */
|
|
1511
|
+
return /* @__PURE__ */ jsx12("code", { className: "mst-code mst-code--block", children: JSON.stringify(value) });
|
|
694
1512
|
default:
|
|
695
|
-
return /* @__PURE__ */
|
|
1513
|
+
return /* @__PURE__ */ jsx12("span", { children: String(value) });
|
|
696
1514
|
}
|
|
697
1515
|
}
|
|
698
1516
|
|
|
699
|
-
// src/components/menu.tsx
|
|
700
|
-
import { useEffect as useEffect4, useLayoutEffect, useRef as useRef2, useState as useState4 } from "react";
|
|
701
|
-
import { createPortal } from "react-dom";
|
|
702
|
-
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
703
|
-
function PopMenu({ anchor, items, onClose }) {
|
|
704
|
-
const ref = useRef2(null);
|
|
705
|
-
const [pos, setPos] = useState4(anchor);
|
|
706
|
-
useLayoutEffect(() => {
|
|
707
|
-
const el = ref.current;
|
|
708
|
-
if (!el) return;
|
|
709
|
-
const rect = el.getBoundingClientRect();
|
|
710
|
-
setPos({
|
|
711
|
-
x: Math.max(4, Math.min(anchor.x, window.innerWidth - rect.width - 8)),
|
|
712
|
-
y: Math.max(4, Math.min(anchor.y, window.innerHeight - rect.height - 8))
|
|
713
|
-
});
|
|
714
|
-
}, [anchor]);
|
|
715
|
-
useEffect4(() => {
|
|
716
|
-
const onDown = (e) => {
|
|
717
|
-
if (ref.current && !ref.current.contains(e.target)) onClose();
|
|
718
|
-
};
|
|
719
|
-
const onKey = (e) => {
|
|
720
|
-
if (e.key === "Escape") onClose();
|
|
721
|
-
};
|
|
722
|
-
document.addEventListener("mousedown", onDown);
|
|
723
|
-
document.addEventListener("keydown", onKey);
|
|
724
|
-
window.addEventListener("scroll", onClose, true);
|
|
725
|
-
window.addEventListener("resize", onClose);
|
|
726
|
-
return () => {
|
|
727
|
-
document.removeEventListener("mousedown", onDown);
|
|
728
|
-
document.removeEventListener("keydown", onKey);
|
|
729
|
-
window.removeEventListener("scroll", onClose, true);
|
|
730
|
-
window.removeEventListener("resize", onClose);
|
|
731
|
-
};
|
|
732
|
-
}, [onClose]);
|
|
733
|
-
return createPortal(
|
|
734
|
-
/* @__PURE__ */ jsx6(
|
|
735
|
-
"div",
|
|
736
|
-
{
|
|
737
|
-
ref,
|
|
738
|
-
role: "menu",
|
|
739
|
-
style: { position: "fixed", left: pos.x, top: pos.y, zIndex: 50 },
|
|
740
|
-
className: "min-w-44 rounded-md border border-slate-200 bg-white py-1 shadow-lg",
|
|
741
|
-
children: items.map((item) => /* @__PURE__ */ jsx6(
|
|
742
|
-
"button",
|
|
743
|
-
{
|
|
744
|
-
role: "menuitem",
|
|
745
|
-
onClick: () => {
|
|
746
|
-
onClose();
|
|
747
|
-
item.onSelect();
|
|
748
|
-
},
|
|
749
|
-
className: `block w-full px-3 py-1.5 text-left text-sm ${item.danger ? "text-red-600 hover:bg-red-50" : "text-slate-700 hover:bg-slate-100"}`,
|
|
750
|
-
children: item.label
|
|
751
|
-
},
|
|
752
|
-
item.id
|
|
753
|
-
))
|
|
754
|
-
}
|
|
755
|
-
),
|
|
756
|
-
document.body
|
|
757
|
-
);
|
|
758
|
-
}
|
|
759
|
-
function MenuButton({ items, label }) {
|
|
760
|
-
const [anchor, setAnchor] = useState4(void 0);
|
|
761
|
-
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
762
|
-
/* @__PURE__ */ jsx6(
|
|
763
|
-
"button",
|
|
764
|
-
{
|
|
765
|
-
type: "button",
|
|
766
|
-
"aria-label": label,
|
|
767
|
-
title: label,
|
|
768
|
-
onClick: (e) => {
|
|
769
|
-
const rect = e.currentTarget.getBoundingClientRect();
|
|
770
|
-
setAnchor({ x: rect.right - 176, y: rect.bottom + 4 });
|
|
771
|
-
},
|
|
772
|
-
className: "rounded-md px-2 py-1 text-slate-500 hover:bg-slate-100 hover:text-slate-900",
|
|
773
|
-
children: "\u22EE"
|
|
774
|
-
}
|
|
775
|
-
),
|
|
776
|
-
anchor && /* @__PURE__ */ jsx6(PopMenu, { anchor, items, onClose: () => setAnchor(void 0) })
|
|
777
|
-
] });
|
|
778
|
-
}
|
|
779
|
-
|
|
780
1517
|
// src/clipboard.ts
|
|
781
1518
|
async function copyText(text) {
|
|
782
1519
|
if (navigator.clipboard?.writeText) {
|
|
@@ -797,8 +1534,9 @@ async function copyText(text) {
|
|
|
797
1534
|
}
|
|
798
1535
|
|
|
799
1536
|
// src/components/EntityList.tsx
|
|
800
|
-
import { jsx as
|
|
1537
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
801
1538
|
var PAGE_SIZE = 20;
|
|
1539
|
+
var ACTIONS_COLUMN_WIDTH = 44;
|
|
802
1540
|
function filterFor(field, value) {
|
|
803
1541
|
if (value === null || value === void 0) return { field: field.name, operator: "isNull" };
|
|
804
1542
|
if (field.type === "boolean") return { field: field.name, operator: value ? "isTrue" : "isFalse" };
|
|
@@ -813,6 +1551,11 @@ function filterChipText(entity, filter) {
|
|
|
813
1551
|
const value = filter.value ?? "";
|
|
814
1552
|
return `${label} = ${value.length > 24 ? `${value.slice(0, 24)}\u2026` : value}`;
|
|
815
1553
|
}
|
|
1554
|
+
function pageWindow(page, totalPages) {
|
|
1555
|
+
const size = Math.min(5, totalPages);
|
|
1556
|
+
const start = Math.max(1, Math.min(page - 2, totalPages - size + 1));
|
|
1557
|
+
return Array.from({ length: size }, (_, i) => start + i);
|
|
1558
|
+
}
|
|
816
1559
|
function EntityList({
|
|
817
1560
|
entity,
|
|
818
1561
|
onOpen,
|
|
@@ -826,14 +1569,16 @@ function EntityList({
|
|
|
826
1569
|
const t = useT();
|
|
827
1570
|
const entities = useEntities();
|
|
828
1571
|
const relatedLabel = (entityId) => entities.find((e) => e.id === entityId)?.label.singular ?? entityId;
|
|
829
|
-
const
|
|
830
|
-
const
|
|
831
|
-
const
|
|
832
|
-
const [
|
|
833
|
-
const [
|
|
834
|
-
const [
|
|
835
|
-
const [
|
|
836
|
-
const [
|
|
1572
|
+
const columnPrefs = useColumnPrefs(entity);
|
|
1573
|
+
const columns = columnPrefs.columns;
|
|
1574
|
+
const searchFields = useMemo6(() => entity.fields.filter((f) => f.searchable).map((f) => f.name), [entity]);
|
|
1575
|
+
const [page, setPage] = useState9(1);
|
|
1576
|
+
const [search, setSearch] = useState9("");
|
|
1577
|
+
const [sort, setSort] = useState9(void 0);
|
|
1578
|
+
const [filters, setFilters] = useState9(initialFilters ?? []);
|
|
1579
|
+
const [cellMenu, setCellMenu] = useState9(void 0);
|
|
1580
|
+
const [columnMenu, setColumnMenu] = useState9(void 0);
|
|
1581
|
+
const [copied, setCopied] = useState9(false);
|
|
837
1582
|
const query = {
|
|
838
1583
|
page,
|
|
839
1584
|
pageSize: PAGE_SIZE,
|
|
@@ -870,17 +1615,31 @@ function EntityList({
|
|
|
870
1615
|
};
|
|
871
1616
|
const rowMenuItems = (record) => {
|
|
872
1617
|
const id = String(record[entity.primaryKey]);
|
|
873
|
-
const items = [{ id: "view", label:
|
|
874
|
-
if (entity.capabilities.update)
|
|
1618
|
+
const items = [{ id: "view", label: t.view, icon: "eye", onSelect: () => onOpen(id) }];
|
|
1619
|
+
if (entity.capabilities.update) {
|
|
1620
|
+
items.push({ id: "edit", label: t.edit, icon: "pencil", onSelect: () => onEdit(id) });
|
|
1621
|
+
}
|
|
875
1622
|
if (entity.capabilities.clone && entity.capabilities.create) {
|
|
876
|
-
items.push({ id: "clone", label:
|
|
1623
|
+
items.push({ id: "clone", label: t.clone, icon: "clone", onSelect: () => onClone(id) });
|
|
877
1624
|
}
|
|
878
1625
|
items.push(
|
|
879
|
-
{ id: "copy-id", label:
|
|
880
|
-
{
|
|
1626
|
+
{ id: "copy-id", label: t.copyId, icon: "copy", separated: true, onSelect: () => void copy(id) },
|
|
1627
|
+
{
|
|
1628
|
+
id: "copy-object",
|
|
1629
|
+
label: t.copyObject,
|
|
1630
|
+
icon: "copy",
|
|
1631
|
+
onSelect: () => void copy(JSON.stringify(record, null, 2))
|
|
1632
|
+
}
|
|
881
1633
|
);
|
|
882
1634
|
if (entity.capabilities.delete) {
|
|
883
|
-
items.push({
|
|
1635
|
+
items.push({
|
|
1636
|
+
id: "remove",
|
|
1637
|
+
label: t.remove,
|
|
1638
|
+
icon: "trash",
|
|
1639
|
+
danger: true,
|
|
1640
|
+
separated: true,
|
|
1641
|
+
onSelect: () => void onDelete(id)
|
|
1642
|
+
});
|
|
884
1643
|
}
|
|
885
1644
|
return items;
|
|
886
1645
|
};
|
|
@@ -890,130 +1649,195 @@ function EntityList({
|
|
|
890
1649
|
const value = record[field.name];
|
|
891
1650
|
const items = [];
|
|
892
1651
|
const filter = filterFor(field, value);
|
|
893
|
-
if (filter) items.push({ id: "filter", label:
|
|
894
|
-
items.push({ id: "view", label:
|
|
895
|
-
if (entity.capabilities.update) items.push({ id: "edit", label:
|
|
1652
|
+
if (filter) items.push({ id: "filter", label: t.addToFilter, icon: "filter", onSelect: () => addFilter(filter) });
|
|
1653
|
+
items.push({ id: "view", label: t.view, icon: "eye", onSelect: () => onOpen(id) });
|
|
1654
|
+
if (entity.capabilities.update) items.push({ id: "edit", label: t.edit, icon: "pencil", onSelect: () => onEdit(id) });
|
|
896
1655
|
if (field.type === "relation" && field.relationEntity && value !== null && value !== void 0) {
|
|
897
1656
|
const targetId = field.relationEntity;
|
|
898
1657
|
items.push({
|
|
899
1658
|
id: "open-related",
|
|
900
|
-
label:
|
|
1659
|
+
label: t.openRelated(relatedLabel(targetId)),
|
|
1660
|
+
icon: "arrowUpRight",
|
|
901
1661
|
onSelect: () => onNavigate(targetId, String(value))
|
|
902
1662
|
});
|
|
903
1663
|
}
|
|
904
1664
|
if (value !== null && value !== void 0) {
|
|
905
1665
|
items.push({
|
|
906
1666
|
id: "copy-value",
|
|
907
|
-
label:
|
|
1667
|
+
label: t.copyValue,
|
|
1668
|
+
icon: "copy",
|
|
1669
|
+
separated: true,
|
|
908
1670
|
onSelect: () => void copy(typeof value === "object" ? JSON.stringify(value) : String(value))
|
|
909
1671
|
});
|
|
910
1672
|
}
|
|
911
1673
|
return items;
|
|
912
1674
|
};
|
|
1675
|
+
const columnMenuItems = [
|
|
1676
|
+
...columnPrefs.available.map((field) => ({
|
|
1677
|
+
id: `col:${field.name}`,
|
|
1678
|
+
label: field.label,
|
|
1679
|
+
icon: columnPrefs.visible(field.name) ? "check" : void 0,
|
|
1680
|
+
onSelect: () => columnPrefs.toggle(field.name)
|
|
1681
|
+
})),
|
|
1682
|
+
...columnPrefs.customized ? [{ id: "reset", label: t.resetColumns, icon: "rows", separated: true, onSelect: columnPrefs.reset }] : []
|
|
1683
|
+
];
|
|
913
1684
|
const totalPages = data?.totalPages ?? (data ? Math.max(1, Math.ceil(data.total / PAGE_SIZE)) : 1);
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1685
|
+
const firstRow = data && data.total > 0 ? (page - 1) * PAGE_SIZE + 1 : 0;
|
|
1686
|
+
const lastRow = data ? Math.min(page * PAGE_SIZE, data.total) : 0;
|
|
1687
|
+
return /* @__PURE__ */ jsxs11("section", { className: "mst-page mst-page--fill", children: [
|
|
1688
|
+
/* @__PURE__ */ jsxs11("div", { className: "mst-pagehead", children: [
|
|
1689
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
1690
|
+
/* @__PURE__ */ jsx13("h1", { className: "mst-pagehead__title", children: entity.label.plural }),
|
|
1691
|
+
/* @__PURE__ */ jsxs11("p", { className: "mst-pagehead__meta", children: [
|
|
1692
|
+
data && /* @__PURE__ */ jsx13("span", { className: "mst-nums", children: t.records(data.total) }),
|
|
1693
|
+
/* @__PURE__ */ jsxs11("span", { className: "mst-source", children: [
|
|
1694
|
+
/* @__PURE__ */ jsx13(Icon, { name: "database", size: 11 }),
|
|
1695
|
+
/* @__PURE__ */ jsxs11("span", { children: [
|
|
1696
|
+
entity.datasource,
|
|
1697
|
+
" \xB7 ",
|
|
1698
|
+
entity.table
|
|
1699
|
+
] })
|
|
1700
|
+
] })
|
|
1701
|
+
] })
|
|
919
1702
|
] }),
|
|
920
|
-
/* @__PURE__ */
|
|
921
|
-
searchFields.length > 0 && /* @__PURE__ */
|
|
1703
|
+
/* @__PURE__ */ jsxs11("div", { className: "mst-pagehead__actions", children: [
|
|
1704
|
+
searchFields.length > 0 && /* @__PURE__ */ jsx13(
|
|
922
1705
|
"input",
|
|
923
1706
|
{
|
|
924
1707
|
type: "search",
|
|
1708
|
+
className: "mst-input mst-input--inline",
|
|
925
1709
|
placeholder: t.searchPlaceholder(entity.label.plural),
|
|
926
1710
|
value: search,
|
|
927
1711
|
onChange: (e) => {
|
|
928
1712
|
setSearch(e.target.value);
|
|
929
1713
|
setPage(1);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
),
|
|
1717
|
+
/* @__PURE__ */ jsxs11(
|
|
1718
|
+
Button,
|
|
1719
|
+
{
|
|
1720
|
+
icon: "columns",
|
|
1721
|
+
onClick: (e) => {
|
|
1722
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
1723
|
+
setColumnMenu({ x: rect.right - 200, y: rect.bottom + 6 });
|
|
930
1724
|
},
|
|
931
|
-
|
|
1725
|
+
children: [
|
|
1726
|
+
t.columns,
|
|
1727
|
+
" ",
|
|
1728
|
+
/* @__PURE__ */ jsx13("span", { className: "mst-subtle mst-nums", children: t.columnsCount(columns.length, columnPrefs.available.length) })
|
|
1729
|
+
]
|
|
932
1730
|
}
|
|
933
1731
|
),
|
|
934
|
-
entity.capabilities.create && /* @__PURE__ */
|
|
1732
|
+
entity.capabilities.create && /* @__PURE__ */ jsx13(Button, { variant: "primary", icon: "plus", onClick: onCreate, children: t.newRecord })
|
|
935
1733
|
] })
|
|
936
1734
|
] }),
|
|
937
|
-
filters.length > 0 && /* @__PURE__ */
|
|
938
|
-
/* @__PURE__ */
|
|
939
|
-
t.filters,
|
|
940
|
-
":"
|
|
941
|
-
] }),
|
|
942
|
-
filters.map((f) => /* @__PURE__ */ jsxs6(
|
|
1735
|
+
filters.length > 0 && /* @__PURE__ */ jsxs11("div", { className: "mst-chips", children: [
|
|
1736
|
+
filters.map((f) => /* @__PURE__ */ jsxs11(
|
|
943
1737
|
"button",
|
|
944
1738
|
{
|
|
1739
|
+
type: "button",
|
|
1740
|
+
className: "mst-chip",
|
|
1741
|
+
title: f.value,
|
|
945
1742
|
onClick: () => {
|
|
946
1743
|
setFilters((prev) => prev.filter((p) => p.field !== f.field));
|
|
947
1744
|
setPage(1);
|
|
948
1745
|
},
|
|
949
|
-
title: f.value,
|
|
950
|
-
className: "inline-flex items-center gap-1 rounded-full bg-indigo-50 px-2.5 py-0.5 text-xs font-medium text-indigo-700 hover:bg-indigo-100",
|
|
951
1746
|
children: [
|
|
952
1747
|
filterChipText(entity, f),
|
|
953
|
-
/* @__PURE__ */
|
|
1748
|
+
/* @__PURE__ */ jsx13(Icon, { name: "close", size: 11 })
|
|
954
1749
|
]
|
|
955
1750
|
},
|
|
956
1751
|
f.field
|
|
957
1752
|
)),
|
|
958
|
-
/* @__PURE__ */
|
|
1753
|
+
/* @__PURE__ */ jsx13(
|
|
959
1754
|
"button",
|
|
960
1755
|
{
|
|
1756
|
+
type: "button",
|
|
1757
|
+
className: "mst-btn mst-btn--ghost",
|
|
961
1758
|
onClick: () => {
|
|
962
1759
|
setFilters([]);
|
|
963
1760
|
setPage(1);
|
|
964
1761
|
},
|
|
965
|
-
className: "text-xs text-slate-500 underline hover:text-slate-700",
|
|
966
1762
|
children: t.clearFilters
|
|
967
1763
|
}
|
|
968
1764
|
)
|
|
969
1765
|
] }),
|
|
970
|
-
error && /* @__PURE__ */
|
|
971
|
-
loading && !data ? /* @__PURE__ */
|
|
972
|
-
/* @__PURE__ */
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
onClick: () => f.sortable && toggleSort(f.name),
|
|
977
|
-
className: `px-3 py-2 text-left font-medium text-slate-600 ${f.sortable ? "cursor-pointer select-none hover:text-slate-900" : ""}`,
|
|
978
|
-
children: [
|
|
979
|
-
f.label,
|
|
980
|
-
sort?.field === f.name && (sort.direction === "asc" ? " \u25B2" : " \u25BC")
|
|
981
|
-
]
|
|
982
|
-
},
|
|
983
|
-
f.name
|
|
984
|
-
)),
|
|
985
|
-
/* @__PURE__ */ jsx7("th", { className: "px-3 py-2 text-right font-medium text-slate-600", children: t.actions })
|
|
986
|
-
] }) }),
|
|
987
|
-
/* @__PURE__ */ jsx7("tbody", { className: "divide-y divide-slate-100 bg-white", children: data?.records.map((record, i) => {
|
|
988
|
-
const id = String(record[entity.primaryKey]);
|
|
989
|
-
return /* @__PURE__ */ jsxs6("tr", { className: "hover:bg-slate-50", children: [
|
|
990
|
-
columns.map((f) => /* @__PURE__ */ jsx7(
|
|
991
|
-
"td",
|
|
1766
|
+
error && /* @__PURE__ */ jsx13(ErrorBanner, { error }),
|
|
1767
|
+
loading && !data ? /* @__PURE__ */ jsx13(Spinner, { label: t.loading }) : data && data.records.length === 0 ? /* @__PURE__ */ jsx13(EmptyState, { title: t.noRecords, hint: search || filters.length ? t.tryDifferentSearch : void 0 }) : /* @__PURE__ */ jsxs11("div", { className: "mst-card mst-tablewrap", children: [
|
|
1768
|
+
/* @__PURE__ */ jsx13("div", { className: "mst-tablescroll", children: /* @__PURE__ */ jsxs11("table", { className: "mst-table", children: [
|
|
1769
|
+
/* @__PURE__ */ jsx13("thead", { children: /* @__PURE__ */ jsxs11("tr", { children: [
|
|
1770
|
+
columns.map((f) => /* @__PURE__ */ jsx13(
|
|
1771
|
+
"th",
|
|
992
1772
|
{
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1773
|
+
style: { width: f.list.width, textAlign: f.list.align },
|
|
1774
|
+
"aria-sort": sort?.field === f.name ? sort.direction === "asc" ? "ascending" : "descending" : "none",
|
|
1775
|
+
children: f.sortable ? /* @__PURE__ */ jsxs11("button", { type: "button", className: "mst-th__button", onClick: () => toggleSort(f.name), children: [
|
|
1776
|
+
/* @__PURE__ */ jsx13("span", { className: "mst-th__label", children: f.label }),
|
|
1777
|
+
/* @__PURE__ */ jsx13(
|
|
1778
|
+
Icon,
|
|
1779
|
+
{
|
|
1780
|
+
name: sort?.field === f.name ? sort.direction === "asc" ? "sortAsc" : "sortDesc" : "sort",
|
|
1781
|
+
size: 11
|
|
1782
|
+
}
|
|
1783
|
+
)
|
|
1784
|
+
] }) : /* @__PURE__ */ jsx13("span", { className: "mst-th__label", children: f.label })
|
|
997
1785
|
},
|
|
998
1786
|
f.name
|
|
999
1787
|
)),
|
|
1000
|
-
/* @__PURE__ */
|
|
1001
|
-
] }
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1788
|
+
/* @__PURE__ */ jsx13("th", { style: { width: ACTIONS_COLUMN_WIDTH }, children: /* @__PURE__ */ jsx13("span", { className: "mst-sr-only", children: t.actions }) })
|
|
1789
|
+
] }) }),
|
|
1790
|
+
/* @__PURE__ */ jsx13("tbody", { children: data?.records.map((record, i) => {
|
|
1791
|
+
const id = String(record[entity.primaryKey]);
|
|
1792
|
+
return /* @__PURE__ */ jsxs11("tr", { children: [
|
|
1793
|
+
columns.map((f) => /* @__PURE__ */ jsx13(
|
|
1794
|
+
"td",
|
|
1795
|
+
{
|
|
1796
|
+
className: "mst-table__cell",
|
|
1797
|
+
style: { textAlign: f.list.align },
|
|
1798
|
+
title: f.label,
|
|
1799
|
+
onClick: (e) => setCellMenu({ anchor: { x: e.clientX, y: e.clientY }, field: f, record }),
|
|
1800
|
+
onDoubleClick: () => onOpen(id),
|
|
1801
|
+
children: /* @__PURE__ */ jsx13(FieldValue, { field: f, value: record[f.name], compact: true })
|
|
1802
|
+
},
|
|
1803
|
+
f.name
|
|
1804
|
+
)),
|
|
1805
|
+
/* @__PURE__ */ jsx13("td", { className: "mst-td--actions", children: /* @__PURE__ */ jsx13(MenuButton, { label: t.actions, items: rowMenuItems(record) }) })
|
|
1806
|
+
] }, id || i);
|
|
1807
|
+
}) })
|
|
1808
|
+
] }) }),
|
|
1809
|
+
data && /* @__PURE__ */ jsxs11("div", { className: "mst-pagination", children: [
|
|
1810
|
+
/* @__PURE__ */ jsxs11("span", { className: "mst-nums", children: [
|
|
1811
|
+
firstRow,
|
|
1812
|
+
"\u2013",
|
|
1813
|
+
lastRow
|
|
1814
|
+
] }),
|
|
1815
|
+
/* @__PURE__ */ jsx13("span", { children: t.records(data.total) }),
|
|
1816
|
+
totalPages > 1 && /* @__PURE__ */ jsxs11("span", { className: "mst-pagination__nums", children: [
|
|
1817
|
+
/* @__PURE__ */ jsx13("button", { type: "button", "aria-label": t.previous, disabled: page <= 1, onClick: () => setPage((p) => p - 1), children: /* @__PURE__ */ jsx13(Icon, { name: "chevronLeft", size: 13 }) }),
|
|
1818
|
+
pageWindow(page, totalPages).map((p) => /* @__PURE__ */ jsx13("button", { type: "button", "aria-current": p === page, onClick: () => setPage(p), children: p }, p)),
|
|
1819
|
+
/* @__PURE__ */ jsx13(
|
|
1820
|
+
"button",
|
|
1821
|
+
{
|
|
1822
|
+
type: "button",
|
|
1823
|
+
"aria-label": t.next,
|
|
1824
|
+
disabled: page >= totalPages,
|
|
1825
|
+
onClick: () => setPage((p) => p + 1),
|
|
1826
|
+
children: /* @__PURE__ */ jsx13(Icon, { name: "chevronRight", size: 13 })
|
|
1827
|
+
}
|
|
1828
|
+
)
|
|
1829
|
+
] })
|
|
1830
|
+
] })
|
|
1831
|
+
] }),
|
|
1832
|
+
cellMenu && /* @__PURE__ */ jsx13(PopMenu, { anchor: cellMenu.anchor, items: cellMenuItems(cellMenu), onClose: () => setCellMenu(void 0) }),
|
|
1833
|
+
columnMenu && /* @__PURE__ */ jsx13(PopMenu, { anchor: columnMenu, items: columnMenuItems, onClose: () => setColumnMenu(void 0) }),
|
|
1834
|
+
copied && /* @__PURE__ */ jsx13(Toast, { children: t.copied })
|
|
1011
1835
|
] });
|
|
1012
1836
|
}
|
|
1013
1837
|
|
|
1014
1838
|
// src/components/EntityDetail.tsx
|
|
1015
|
-
import { useState as
|
|
1016
|
-
import { jsx as
|
|
1839
|
+
import { useState as useState10 } from "react";
|
|
1840
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1017
1841
|
function EntityDetail({
|
|
1018
1842
|
entity,
|
|
1019
1843
|
id,
|
|
@@ -1028,7 +1852,7 @@ function EntityDetail({
|
|
|
1028
1852
|
const entities = useEntities();
|
|
1029
1853
|
const { data, error, loading } = useEntityRecord(entity.id, id);
|
|
1030
1854
|
const fields = detailFields(entity);
|
|
1031
|
-
const [copied, setCopied] =
|
|
1855
|
+
const [copied, setCopied] = useState10(false);
|
|
1032
1856
|
const copy = async (text) => {
|
|
1033
1857
|
await copyText(text);
|
|
1034
1858
|
setCopied(true);
|
|
@@ -1041,71 +1865,72 @@ function EntityDetail({
|
|
|
1041
1865
|
};
|
|
1042
1866
|
const menuItems = [];
|
|
1043
1867
|
if (entity.capabilities.clone && entity.capabilities.create) {
|
|
1044
|
-
menuItems.push({ id: "clone", label:
|
|
1868
|
+
menuItems.push({ id: "clone", label: t.clone, icon: "clone", onSelect: onClone });
|
|
1045
1869
|
}
|
|
1046
|
-
menuItems.push({ id: "copy-id", label:
|
|
1870
|
+
menuItems.push({ id: "copy-id", label: t.copyId, icon: "copy", onSelect: () => void copy(id) });
|
|
1047
1871
|
if (data) {
|
|
1048
1872
|
menuItems.push({
|
|
1049
1873
|
id: "copy-object",
|
|
1050
|
-
label:
|
|
1874
|
+
label: t.copyObject,
|
|
1875
|
+
icon: "copy",
|
|
1051
1876
|
onSelect: () => void copy(JSON.stringify(data, null, 2))
|
|
1052
1877
|
});
|
|
1053
1878
|
}
|
|
1054
1879
|
if (entity.capabilities.delete) {
|
|
1055
|
-
menuItems.push({
|
|
1880
|
+
menuItems.push({
|
|
1881
|
+
id: "remove",
|
|
1882
|
+
label: t.remove,
|
|
1883
|
+
icon: "trash",
|
|
1884
|
+
danger: true,
|
|
1885
|
+
separated: true,
|
|
1886
|
+
onSelect: () => void onDelete()
|
|
1887
|
+
});
|
|
1056
1888
|
}
|
|
1057
|
-
return /* @__PURE__ */
|
|
1058
|
-
/* @__PURE__ */
|
|
1059
|
-
/* @__PURE__ */
|
|
1060
|
-
/* @__PURE__ */
|
|
1061
|
-
|
|
1889
|
+
return /* @__PURE__ */ jsxs12("section", { className: "mst-page", children: [
|
|
1890
|
+
/* @__PURE__ */ jsxs12("div", { className: "mst-pagehead", children: [
|
|
1891
|
+
/* @__PURE__ */ jsxs12("div", { children: [
|
|
1892
|
+
/* @__PURE__ */ jsxs12("button", { type: "button", className: "mst-backlink", onClick: onBack, children: [
|
|
1893
|
+
/* @__PURE__ */ jsx14(Icon, { name: "arrowLeft", size: 12 }),
|
|
1062
1894
|
entity.label.plural
|
|
1063
1895
|
] }),
|
|
1064
|
-
/* @__PURE__ */
|
|
1896
|
+
/* @__PURE__ */ jsxs12("h1", { className: "mst-pagehead__title", children: [
|
|
1065
1897
|
entity.label.singular,
|
|
1066
1898
|
" ",
|
|
1067
1899
|
data ? String(data[entity.displayField] ?? id) : ""
|
|
1068
1900
|
] })
|
|
1069
1901
|
] }),
|
|
1070
|
-
/* @__PURE__ */
|
|
1071
|
-
entity.capabilities.update && /* @__PURE__ */
|
|
1072
|
-
/* @__PURE__ */
|
|
1902
|
+
/* @__PURE__ */ jsxs12("div", { className: "mst-pagehead__actions", children: [
|
|
1903
|
+
entity.capabilities.update && /* @__PURE__ */ jsx14(Button, { variant: "primary", icon: "pencil", onClick: onEdit, children: t.edit }),
|
|
1904
|
+
/* @__PURE__ */ jsx14(MenuButton, { label: t.actions, items: menuItems })
|
|
1073
1905
|
] })
|
|
1074
1906
|
] }),
|
|
1075
|
-
error && /* @__PURE__ */
|
|
1076
|
-
loading && !data ? /* @__PURE__ */
|
|
1907
|
+
error && /* @__PURE__ */ jsx14(ErrorBanner, { error }),
|
|
1908
|
+
loading && !data ? /* @__PURE__ */ jsx14(Spinner, {}) : data && /* @__PURE__ */ jsx14("dl", { className: "mst-fields", children: fields.map((f) => {
|
|
1077
1909
|
const value = data[f.name];
|
|
1078
1910
|
const isRelation = f.type === "relation" && f.relationEntity && value !== null && value !== void 0;
|
|
1079
1911
|
const target = isRelation ? entities.find((e) => e.id === f.relationEntity) : void 0;
|
|
1080
|
-
return /* @__PURE__ */
|
|
1081
|
-
/* @__PURE__ */
|
|
1082
|
-
/* @__PURE__ */
|
|
1083
|
-
/* @__PURE__ */
|
|
1084
|
-
isRelation && /* @__PURE__ */
|
|
1085
|
-
|
|
1086
|
-
{
|
|
1087
|
-
|
|
1088
|
-
className: "text-xs text-indigo-600 hover:underline",
|
|
1089
|
-
children: [
|
|
1090
|
-
t.openRelated(target?.label.singular ?? f.relationEntity),
|
|
1091
|
-
" \u2197"
|
|
1092
|
-
]
|
|
1093
|
-
}
|
|
1094
|
-
)
|
|
1912
|
+
return /* @__PURE__ */ jsxs12("div", { className: "mst-fields__row", children: [
|
|
1913
|
+
/* @__PURE__ */ jsx14("dt", { className: "mst-fields__key", children: f.label }),
|
|
1914
|
+
/* @__PURE__ */ jsxs12("dd", { className: "mst-fields__value", children: [
|
|
1915
|
+
/* @__PURE__ */ jsx14(FieldValue, { field: f, value }),
|
|
1916
|
+
isRelation && /* @__PURE__ */ jsxs12("button", { type: "button", className: "mst-backlink", onClick: () => onNavigate(f.relationEntity, String(value)), children: [
|
|
1917
|
+
t.openRelated(target?.label.singular ?? f.relationEntity),
|
|
1918
|
+
/* @__PURE__ */ jsx14(Icon, { name: "arrowUpRight", size: 11 })
|
|
1919
|
+
] })
|
|
1095
1920
|
] })
|
|
1096
1921
|
] }, f.name);
|
|
1097
1922
|
}) }),
|
|
1098
|
-
copied && /* @__PURE__ */
|
|
1923
|
+
copied && /* @__PURE__ */ jsx14(Toast, { children: t.copied })
|
|
1099
1924
|
] });
|
|
1100
1925
|
}
|
|
1101
1926
|
|
|
1102
1927
|
// src/components/EntityForm.tsx
|
|
1103
|
-
import { useEffect as
|
|
1928
|
+
import { useEffect as useEffect7, useState as useState12 } from "react";
|
|
1104
1929
|
|
|
1105
1930
|
// src/components/JsonInput.tsx
|
|
1106
|
-
import { useEffect as
|
|
1107
|
-
import { jsx as
|
|
1108
|
-
var leafInputClass = "
|
|
1931
|
+
import { useEffect as useEffect6, useRef as useRef4, useState as useState11 } from "react";
|
|
1932
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1933
|
+
var leafInputClass = "mst-input";
|
|
1109
1934
|
var MAX_DEPTH2 = 3;
|
|
1110
1935
|
function RawJsonArea({
|
|
1111
1936
|
id,
|
|
@@ -1116,10 +1941,10 @@ function RawJsonArea({
|
|
|
1116
1941
|
placeholder
|
|
1117
1942
|
}) {
|
|
1118
1943
|
const t = useT();
|
|
1119
|
-
const [text, setText] =
|
|
1120
|
-
const [invalid, setInvalid] =
|
|
1121
|
-
const lastEmitted =
|
|
1122
|
-
|
|
1944
|
+
const [text, setText] = useState11(() => value === null || value === void 0 ? "" : JSON.stringify(value, null, 2));
|
|
1945
|
+
const [invalid, setInvalid] = useState11(false);
|
|
1946
|
+
const lastEmitted = useRef4(value);
|
|
1947
|
+
useEffect6(() => {
|
|
1123
1948
|
if (value !== lastEmitted.current) {
|
|
1124
1949
|
lastEmitted.current = value;
|
|
1125
1950
|
setText(value === null || value === void 0 ? "" : JSON.stringify(value, null, 2));
|
|
@@ -1143,8 +1968,8 @@ function RawJsonArea({
|
|
|
1143
1968
|
setInvalid(true);
|
|
1144
1969
|
}
|
|
1145
1970
|
};
|
|
1146
|
-
return /* @__PURE__ */
|
|
1147
|
-
/* @__PURE__ */
|
|
1971
|
+
return /* @__PURE__ */ jsxs13("div", { className: "mst-field", children: [
|
|
1972
|
+
/* @__PURE__ */ jsx15(
|
|
1148
1973
|
"textarea",
|
|
1149
1974
|
{
|
|
1150
1975
|
id,
|
|
@@ -1154,10 +1979,10 @@ function RawJsonArea({
|
|
|
1154
1979
|
disabled,
|
|
1155
1980
|
onChange: (e) => onText(e.target.value),
|
|
1156
1981
|
"aria-invalid": invalid || void 0,
|
|
1157
|
-
className:
|
|
1982
|
+
className: `mst-textarea mst-mono${invalid ? " mst-input--invalid" : ""}`
|
|
1158
1983
|
}
|
|
1159
1984
|
),
|
|
1160
|
-
invalid && /* @__PURE__ */
|
|
1985
|
+
invalid && /* @__PURE__ */ jsx15("p", { role: "alert", className: "mst-help mst-help--warn", children: t.jsonInvalid })
|
|
1161
1986
|
] });
|
|
1162
1987
|
}
|
|
1163
1988
|
function LeafEditor({
|
|
@@ -1168,7 +1993,7 @@ function LeafEditor({
|
|
|
1168
1993
|
depth
|
|
1169
1994
|
}) {
|
|
1170
1995
|
if (typeof value === "boolean") {
|
|
1171
|
-
return /* @__PURE__ */
|
|
1996
|
+
return /* @__PURE__ */ jsx15(
|
|
1172
1997
|
"input",
|
|
1173
1998
|
{
|
|
1174
1999
|
id,
|
|
@@ -1176,12 +2001,12 @@ function LeafEditor({
|
|
|
1176
2001
|
checked: value,
|
|
1177
2002
|
disabled,
|
|
1178
2003
|
onChange: (e) => onChange(e.target.checked),
|
|
1179
|
-
className: "
|
|
2004
|
+
className: "mst-checkbox"
|
|
1180
2005
|
}
|
|
1181
2006
|
);
|
|
1182
2007
|
}
|
|
1183
2008
|
if (typeof value === "number") {
|
|
1184
|
-
return /* @__PURE__ */
|
|
2009
|
+
return /* @__PURE__ */ jsx15(
|
|
1185
2010
|
"input",
|
|
1186
2011
|
{
|
|
1187
2012
|
id,
|
|
@@ -1195,7 +2020,7 @@ function LeafEditor({
|
|
|
1195
2020
|
);
|
|
1196
2021
|
}
|
|
1197
2022
|
if (typeof value === "string") {
|
|
1198
|
-
return /* @__PURE__ */
|
|
2023
|
+
return /* @__PURE__ */ jsx15(
|
|
1199
2024
|
"input",
|
|
1200
2025
|
{
|
|
1201
2026
|
id,
|
|
@@ -1208,7 +2033,7 @@ function LeafEditor({
|
|
|
1208
2033
|
);
|
|
1209
2034
|
}
|
|
1210
2035
|
if (value === null || value === void 0) {
|
|
1211
|
-
return /* @__PURE__ */
|
|
2036
|
+
return /* @__PURE__ */ jsx15(
|
|
1212
2037
|
"input",
|
|
1213
2038
|
{
|
|
1214
2039
|
id,
|
|
@@ -1222,12 +2047,12 @@ function LeafEditor({
|
|
|
1222
2047
|
);
|
|
1223
2048
|
}
|
|
1224
2049
|
if (isPlainObject(value) && depth < MAX_DEPTH2) {
|
|
1225
|
-
return /* @__PURE__ */
|
|
2050
|
+
return /* @__PURE__ */ jsx15("div", { className: "mst-json__nest", children: /* @__PURE__ */ jsx15(ObjectEditor, { idPrefix: id, value, onChange, disabled, depth: depth + 1 }) });
|
|
1226
2051
|
}
|
|
1227
2052
|
if (Array.isArray(value) && depth < MAX_DEPTH2) {
|
|
1228
|
-
return /* @__PURE__ */
|
|
2053
|
+
return /* @__PURE__ */ jsx15("div", { className: "mst-json__nest", children: /* @__PURE__ */ jsx15(ArrayEditor, { idPrefix: id, value, onChange, disabled, depth: depth + 1 }) });
|
|
1229
2054
|
}
|
|
1230
|
-
return /* @__PURE__ */
|
|
2055
|
+
return /* @__PURE__ */ jsx15(RawJsonArea, { id, value, onChange, disabled, rows: 3 });
|
|
1231
2056
|
}
|
|
1232
2057
|
function ArrayEditor({
|
|
1233
2058
|
idPrefix,
|
|
@@ -1244,27 +2069,27 @@ function ArrayEditor({
|
|
|
1244
2069
|
const template = last === void 0 || last === null ? null : JSON.parse(JSON.stringify(last));
|
|
1245
2070
|
onChange([...value, template]);
|
|
1246
2071
|
};
|
|
1247
|
-
return /* @__PURE__ */
|
|
2072
|
+
return /* @__PURE__ */ jsxs13("div", { className: "mst-json__rows", children: [
|
|
1248
2073
|
value.map((item, i) => {
|
|
1249
2074
|
const id = `${idPrefix}-${i}`;
|
|
1250
|
-
return /* @__PURE__ */
|
|
1251
|
-
/* @__PURE__ */
|
|
1252
|
-
!disabled && /* @__PURE__ */
|
|
2075
|
+
return /* @__PURE__ */ jsxs13("div", { className: "mst-json__row", children: [
|
|
2076
|
+
/* @__PURE__ */ jsx15("label", { htmlFor: id, className: "mst-json__index", children: i }),
|
|
2077
|
+
!disabled && /* @__PURE__ */ jsx15(
|
|
1253
2078
|
"button",
|
|
1254
2079
|
{
|
|
1255
2080
|
type: "button",
|
|
1256
2081
|
title: t.removeItem,
|
|
1257
2082
|
"aria-label": `${t.removeItem} ${i}`,
|
|
1258
2083
|
onClick: () => removeAt(i),
|
|
1259
|
-
className: "
|
|
2084
|
+
className: "mst-json__remove",
|
|
1260
2085
|
children: "\u2715"
|
|
1261
2086
|
}
|
|
1262
2087
|
),
|
|
1263
|
-
/* @__PURE__ */
|
|
2088
|
+
/* @__PURE__ */ jsx15("div", { className: "mst-json__field", children: /* @__PURE__ */ jsx15(LeafEditor, { id, value: item, onChange: (next) => setAt(i, next), disabled, depth }) })
|
|
1264
2089
|
] }, i);
|
|
1265
2090
|
}),
|
|
1266
|
-
value.length === 0 && /* @__PURE__ */
|
|
1267
|
-
!disabled && /* @__PURE__ */
|
|
2091
|
+
value.length === 0 && /* @__PURE__ */ jsx15("p", { className: "mst-json__empty", children: t.emptyObject }),
|
|
2092
|
+
!disabled && /* @__PURE__ */ jsx15("div", { className: "mst-json__addrow", children: /* @__PURE__ */ jsxs13("button", { type: "button", onClick: append, className: "mst-backlink", children: [
|
|
1268
2093
|
"+ ",
|
|
1269
2094
|
t.addItem
|
|
1270
2095
|
] }) })
|
|
@@ -1280,16 +2105,16 @@ function ObjectEditor({
|
|
|
1280
2105
|
const t = useT();
|
|
1281
2106
|
const entries = Object.entries(value);
|
|
1282
2107
|
if (entries.length === 0) {
|
|
1283
|
-
return /* @__PURE__ */
|
|
2108
|
+
return /* @__PURE__ */ jsx15("p", { className: "mst-json__empty", children: t.emptyObject });
|
|
1284
2109
|
}
|
|
1285
|
-
return /* @__PURE__ */
|
|
2110
|
+
return /* @__PURE__ */ jsx15("div", { className: "mst-json__rows", children: entries.map(([key, entry]) => {
|
|
1286
2111
|
const id = `${idPrefix}-${key}`;
|
|
1287
2112
|
return (
|
|
1288
2113
|
// flex-wrap + basis: when the label column leaves less than ~16rem for the control (deeply
|
|
1289
2114
|
// nested groups), the control wraps to its own full-width line instead of squeezing.
|
|
1290
|
-
/* @__PURE__ */
|
|
1291
|
-
/* @__PURE__ */
|
|
1292
|
-
/* @__PURE__ */
|
|
2115
|
+
/* @__PURE__ */ jsxs13("div", { className: "mst-json__row", children: [
|
|
2116
|
+
/* @__PURE__ */ jsx15("label", { htmlFor: id, className: "mst-json__key", title: key, children: key }),
|
|
2117
|
+
/* @__PURE__ */ jsx15("div", { className: "mst-json__field", children: /* @__PURE__ */ jsx15(
|
|
1293
2118
|
LeafEditor,
|
|
1294
2119
|
{
|
|
1295
2120
|
id,
|
|
@@ -1310,10 +2135,10 @@ function JsonInput({
|
|
|
1310
2135
|
disabled
|
|
1311
2136
|
}) {
|
|
1312
2137
|
const t = useT();
|
|
1313
|
-
const [mode, setMode] =
|
|
1314
|
-
const [modeTouched, setModeTouched] =
|
|
2138
|
+
const [mode, setMode] = useState11(() => isPlainObject(value) ? "form" : "raw");
|
|
2139
|
+
const [modeTouched, setModeTouched] = useState11(false);
|
|
1315
2140
|
const structured = isPlainObject(value) || Array.isArray(value);
|
|
1316
|
-
|
|
2141
|
+
useEffect6(() => {
|
|
1317
2142
|
if (!modeTouched && structured) setMode("form");
|
|
1318
2143
|
}, [structured, modeTouched]);
|
|
1319
2144
|
const pick = (next) => {
|
|
@@ -1321,10 +2146,10 @@ function JsonInput({
|
|
|
1321
2146
|
setMode(next);
|
|
1322
2147
|
};
|
|
1323
2148
|
const showForm = mode === "form" && structured;
|
|
1324
|
-
const toggleClass = (active) => `
|
|
1325
|
-
return /* @__PURE__ */
|
|
1326
|
-
/* @__PURE__ */
|
|
1327
|
-
/* @__PURE__ */
|
|
2149
|
+
const toggleClass = (active) => `mst-json__tab${active ? " mst-json__tab--active" : ""}`;
|
|
2150
|
+
return /* @__PURE__ */ jsxs13("div", { className: "mst-json", children: [
|
|
2151
|
+
/* @__PURE__ */ jsxs13("div", { className: "mst-json__head", children: [
|
|
2152
|
+
/* @__PURE__ */ jsx15(
|
|
1328
2153
|
"button",
|
|
1329
2154
|
{
|
|
1330
2155
|
type: "button",
|
|
@@ -1334,9 +2159,9 @@ function JsonInput({
|
|
|
1334
2159
|
children: t.jsonFormMode
|
|
1335
2160
|
}
|
|
1336
2161
|
),
|
|
1337
|
-
/* @__PURE__ */
|
|
2162
|
+
/* @__PURE__ */ jsx15("button", { type: "button", className: toggleClass(!showForm), onClick: () => pick("raw"), children: t.jsonRawMode })
|
|
1338
2163
|
] }),
|
|
1339
|
-
/* @__PURE__ */
|
|
2164
|
+
/* @__PURE__ */ jsx15("div", { className: "mst-json__body", children: showForm ? Array.isArray(value) ? /* @__PURE__ */ jsx15(ArrayEditor, { idPrefix: `field-${field.name}`, value, onChange, disabled }) : /* @__PURE__ */ jsx15(
|
|
1340
2165
|
ObjectEditor,
|
|
1341
2166
|
{
|
|
1342
2167
|
idPrefix: `field-${field.name}`,
|
|
@@ -1344,7 +2169,7 @@ function JsonInput({
|
|
|
1344
2169
|
onChange,
|
|
1345
2170
|
disabled
|
|
1346
2171
|
}
|
|
1347
|
-
) : /* @__PURE__ */
|
|
2172
|
+
) : /* @__PURE__ */ jsx15(
|
|
1348
2173
|
RawJsonArea,
|
|
1349
2174
|
{
|
|
1350
2175
|
id: `field-${field.name}`,
|
|
@@ -1358,8 +2183,8 @@ function JsonInput({
|
|
|
1358
2183
|
}
|
|
1359
2184
|
|
|
1360
2185
|
// src/components/FieldInput.tsx
|
|
1361
|
-
import { jsx as
|
|
1362
|
-
var inputClass = "
|
|
2186
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2187
|
+
var inputClass = "mst-input";
|
|
1363
2188
|
function FieldInput({
|
|
1364
2189
|
field,
|
|
1365
2190
|
value,
|
|
@@ -1369,7 +2194,7 @@ function FieldInput({
|
|
|
1369
2194
|
const id = `field-${field.name}`;
|
|
1370
2195
|
switch (field.type) {
|
|
1371
2196
|
case "boolean":
|
|
1372
|
-
return /* @__PURE__ */
|
|
2197
|
+
return /* @__PURE__ */ jsx16(
|
|
1373
2198
|
"input",
|
|
1374
2199
|
{
|
|
1375
2200
|
id,
|
|
@@ -1377,7 +2202,7 @@ function FieldInput({
|
|
|
1377
2202
|
checked: Boolean(value),
|
|
1378
2203
|
disabled,
|
|
1379
2204
|
onChange: (e) => onChange(e.target.checked),
|
|
1380
|
-
className: "
|
|
2205
|
+
className: "mst-checkbox"
|
|
1381
2206
|
}
|
|
1382
2207
|
);
|
|
1383
2208
|
case "integer":
|
|
@@ -1385,7 +2210,7 @@ function FieldInput({
|
|
|
1385
2210
|
case "float":
|
|
1386
2211
|
case "decimal":
|
|
1387
2212
|
case "currency":
|
|
1388
|
-
return /* @__PURE__ */
|
|
2213
|
+
return /* @__PURE__ */ jsx16(
|
|
1389
2214
|
"input",
|
|
1390
2215
|
{
|
|
1391
2216
|
id,
|
|
@@ -1399,22 +2224,22 @@ function FieldInput({
|
|
|
1399
2224
|
}
|
|
1400
2225
|
);
|
|
1401
2226
|
case "enum":
|
|
1402
|
-
return /* @__PURE__ */
|
|
2227
|
+
return /* @__PURE__ */ jsxs14(
|
|
1403
2228
|
"select",
|
|
1404
2229
|
{
|
|
1405
2230
|
id,
|
|
1406
2231
|
value: value === null || value === void 0 ? "" : String(value),
|
|
1407
2232
|
disabled,
|
|
1408
2233
|
onChange: (e) => onChange(e.target.value),
|
|
1409
|
-
className:
|
|
2234
|
+
className: "mst-select",
|
|
1410
2235
|
children: [
|
|
1411
|
-
/* @__PURE__ */
|
|
1412
|
-
field.enumOptions?.map((o) => /* @__PURE__ */
|
|
2236
|
+
/* @__PURE__ */ jsx16("option", { value: "", children: "\u2014" }),
|
|
2237
|
+
field.enumOptions?.map((o) => /* @__PURE__ */ jsx16("option", { value: String(o.value), children: o.label }, String(o.value)))
|
|
1413
2238
|
]
|
|
1414
2239
|
}
|
|
1415
2240
|
);
|
|
1416
2241
|
case "date":
|
|
1417
|
-
return /* @__PURE__ */
|
|
2242
|
+
return /* @__PURE__ */ jsx16(
|
|
1418
2243
|
"input",
|
|
1419
2244
|
{
|
|
1420
2245
|
id,
|
|
@@ -1426,9 +2251,9 @@ function FieldInput({
|
|
|
1426
2251
|
}
|
|
1427
2252
|
);
|
|
1428
2253
|
case "json":
|
|
1429
|
-
return /* @__PURE__ */
|
|
2254
|
+
return /* @__PURE__ */ jsx16(JsonInput, { field, value, onChange, disabled });
|
|
1430
2255
|
case "text":
|
|
1431
|
-
return /* @__PURE__ */
|
|
2256
|
+
return /* @__PURE__ */ jsx16(
|
|
1432
2257
|
"textarea",
|
|
1433
2258
|
{
|
|
1434
2259
|
id,
|
|
@@ -1437,11 +2262,11 @@ function FieldInput({
|
|
|
1437
2262
|
placeholder: field.form.placeholder,
|
|
1438
2263
|
disabled,
|
|
1439
2264
|
onChange: (e) => onChange(e.target.value),
|
|
1440
|
-
className:
|
|
2265
|
+
className: "mst-textarea"
|
|
1441
2266
|
}
|
|
1442
2267
|
);
|
|
1443
2268
|
default:
|
|
1444
|
-
return /* @__PURE__ */
|
|
2269
|
+
return /* @__PURE__ */ jsx16(
|
|
1445
2270
|
"input",
|
|
1446
2271
|
{
|
|
1447
2272
|
id,
|
|
@@ -1457,7 +2282,7 @@ function FieldInput({
|
|
|
1457
2282
|
}
|
|
1458
2283
|
|
|
1459
2284
|
// src/components/EntityForm.tsx
|
|
1460
|
-
import { jsx as
|
|
2285
|
+
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1461
2286
|
function EntityForm({
|
|
1462
2287
|
entity,
|
|
1463
2288
|
mode,
|
|
@@ -1471,10 +2296,10 @@ function EntityForm({
|
|
|
1471
2296
|
const fields = formFields(entity, mode);
|
|
1472
2297
|
const sourceId = mode === "edit" ? id : cloneFromId;
|
|
1473
2298
|
const existing = useEntityRecord(entity.id, sourceId);
|
|
1474
|
-
const [values, setValues] =
|
|
1475
|
-
const [saving, setSaving] =
|
|
1476
|
-
const [error, setError] =
|
|
1477
|
-
|
|
2299
|
+
const [values, setValues] = useState12({});
|
|
2300
|
+
const [saving, setSaving] = useState12(false);
|
|
2301
|
+
const [error, setError] = useState12(void 0);
|
|
2302
|
+
useEffect7(() => {
|
|
1478
2303
|
if (!existing.data || !sourceId) return;
|
|
1479
2304
|
if (mode === "edit") {
|
|
1480
2305
|
setValues(existing.data);
|
|
@@ -1506,25 +2331,25 @@ function EntityForm({
|
|
|
1506
2331
|
}
|
|
1507
2332
|
};
|
|
1508
2333
|
if (sourceId && existing.loading && !existing.data) {
|
|
1509
|
-
return /* @__PURE__ */
|
|
2334
|
+
return /* @__PURE__ */ jsx17(Spinner, {});
|
|
1510
2335
|
}
|
|
1511
2336
|
const title = mode === "edit" ? t.editTitle(entity.label.singular) : cloneFromId ? t.cloneTitle(entity.label.singular) : t.newTitle(entity.label.singular);
|
|
1512
|
-
return /* @__PURE__ */
|
|
1513
|
-
/* @__PURE__ */
|
|
1514
|
-
/* @__PURE__ */
|
|
1515
|
-
|
|
2337
|
+
return /* @__PURE__ */ jsxs15("section", { className: "mst-page", children: [
|
|
2338
|
+
/* @__PURE__ */ jsx17("div", { className: "mst-pagehead", children: /* @__PURE__ */ jsxs15("div", { children: [
|
|
2339
|
+
/* @__PURE__ */ jsxs15("button", { type: "button", className: "mst-backlink", onClick: onCancel, children: [
|
|
2340
|
+
/* @__PURE__ */ jsx17(Icon, { name: "arrowLeft", size: 12 }),
|
|
1516
2341
|
entity.label.plural
|
|
1517
2342
|
] }),
|
|
1518
|
-
/* @__PURE__ */
|
|
1519
|
-
] }),
|
|
1520
|
-
error && /* @__PURE__ */
|
|
1521
|
-
/* @__PURE__ */
|
|
1522
|
-
fields.map((f) => /* @__PURE__ */
|
|
1523
|
-
/* @__PURE__ */
|
|
2343
|
+
/* @__PURE__ */ jsx17("h1", { className: "mst-pagehead__title", children: title })
|
|
2344
|
+
] }) }),
|
|
2345
|
+
error && /* @__PURE__ */ jsx17(ErrorBanner, { error }),
|
|
2346
|
+
/* @__PURE__ */ jsxs15("form", { onSubmit: submit, className: "mst-card mst-form", children: [
|
|
2347
|
+
fields.map((f) => /* @__PURE__ */ jsxs15("div", { className: "mst-field", children: [
|
|
2348
|
+
/* @__PURE__ */ jsxs15("label", { htmlFor: `field-${f.name}`, className: "mst-label", children: [
|
|
1524
2349
|
f.label,
|
|
1525
|
-
f.required && /* @__PURE__ */
|
|
2350
|
+
f.required && /* @__PURE__ */ jsx17("span", { className: "mst-label__req", children: "*" })
|
|
1526
2351
|
] }),
|
|
1527
|
-
/* @__PURE__ */
|
|
2352
|
+
/* @__PURE__ */ jsx17(
|
|
1528
2353
|
FieldInput,
|
|
1529
2354
|
{
|
|
1530
2355
|
field: f,
|
|
@@ -1533,11 +2358,11 @@ function EntityForm({
|
|
|
1533
2358
|
onChange: (next) => setValues((v) => ({ ...v, [f.name]: next }))
|
|
1534
2359
|
}
|
|
1535
2360
|
),
|
|
1536
|
-
f.form.helpText && /* @__PURE__ */
|
|
2361
|
+
f.form.helpText && /* @__PURE__ */ jsx17("p", { className: "mst-help", children: f.form.helpText })
|
|
1537
2362
|
] }, f.name)),
|
|
1538
|
-
/* @__PURE__ */
|
|
1539
|
-
/* @__PURE__ */
|
|
1540
|
-
/* @__PURE__ */
|
|
2363
|
+
/* @__PURE__ */ jsxs15("div", { className: "mst-form__foot", children: [
|
|
2364
|
+
/* @__PURE__ */ jsx17(Button, { type: "button", variant: "secondary", onClick: onCancel, disabled: saving, children: t.cancel }),
|
|
2365
|
+
/* @__PURE__ */ jsx17(Button, { type: "submit", variant: "primary", disabled: saving, children: saving ? t.saving : mode === "create" ? t.create : t.save })
|
|
1541
2366
|
] })
|
|
1542
2367
|
] })
|
|
1543
2368
|
] });
|
|
@@ -1566,10 +2391,10 @@ function applyLabels(metadata, labels) {
|
|
|
1566
2391
|
}
|
|
1567
2392
|
|
|
1568
2393
|
// src/recents.ts
|
|
1569
|
-
import { useCallback as
|
|
1570
|
-
var
|
|
2394
|
+
import { useCallback as useCallback4, useState as useState13 } from "react";
|
|
2395
|
+
var STORAGE_KEY2 = "maestro-admin:recents";
|
|
1571
2396
|
var MAX_RECENTS = 8;
|
|
1572
|
-
function
|
|
2397
|
+
function read2(key) {
|
|
1573
2398
|
try {
|
|
1574
2399
|
const raw = globalThis.localStorage?.getItem(key);
|
|
1575
2400
|
const parsed = raw ? JSON.parse(raw) : [];
|
|
@@ -1578,20 +2403,20 @@ function read(key) {
|
|
|
1578
2403
|
return [];
|
|
1579
2404
|
}
|
|
1580
2405
|
}
|
|
1581
|
-
function
|
|
2406
|
+
function write2(key, ids) {
|
|
1582
2407
|
try {
|
|
1583
2408
|
globalThis.localStorage?.setItem(key, JSON.stringify(ids));
|
|
1584
2409
|
} catch {
|
|
1585
2410
|
}
|
|
1586
2411
|
}
|
|
1587
2412
|
function useRecents(scope) {
|
|
1588
|
-
const key = scope ? `${
|
|
1589
|
-
const [recents, setRecents] =
|
|
1590
|
-
const push =
|
|
2413
|
+
const key = scope ? `${STORAGE_KEY2}:${scope}` : STORAGE_KEY2;
|
|
2414
|
+
const [recents, setRecents] = useState13(() => read2(key));
|
|
2415
|
+
const push = useCallback4(
|
|
1591
2416
|
(entityId) => {
|
|
1592
2417
|
setRecents((prev) => {
|
|
1593
2418
|
const next = [entityId, ...prev.filter((id) => id !== entityId)].slice(0, MAX_RECENTS);
|
|
1594
|
-
|
|
2419
|
+
write2(key, next);
|
|
1595
2420
|
return next;
|
|
1596
2421
|
});
|
|
1597
2422
|
},
|
|
@@ -1601,30 +2426,81 @@ function useRecents(scope) {
|
|
|
1601
2426
|
}
|
|
1602
2427
|
|
|
1603
2428
|
// src/components/MaestroAdmin.tsx
|
|
1604
|
-
import { jsx as
|
|
2429
|
+
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1605
2430
|
function AdminShell({ title, labels }) {
|
|
1606
2431
|
const { data, error, loading } = useMetadata();
|
|
1607
2432
|
const t = useT();
|
|
1608
|
-
const [view, setView] =
|
|
2433
|
+
const [view, setView] = useState14({ kind: "hub" });
|
|
2434
|
+
const [paletteOpen, setPaletteOpen] = useState14(false);
|
|
1609
2435
|
const { recents, push: pushRecent } = useRecents();
|
|
1610
|
-
const metadata =
|
|
1611
|
-
const entities = metadata?.entities ?? [];
|
|
1612
|
-
const activeId = view
|
|
2436
|
+
const metadata = useMemo7(() => data ? applyLabels(data, labels) : void 0, [data, labels]);
|
|
2437
|
+
const entities = useMemo7(() => metadata?.entities ?? [], [metadata]);
|
|
2438
|
+
const activeId = view.kind === "hub" ? void 0 : view.entityId;
|
|
1613
2439
|
const activeEntity = entities.find((e) => e.id === activeId);
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
2440
|
+
const groupOf = useMemo7(() => {
|
|
2441
|
+
const map = /* @__PURE__ */ new Map();
|
|
2442
|
+
for (const group of buildMenu(entities, labels).groups) {
|
|
2443
|
+
for (const entity of group.entities) map.set(entity.id, group.label);
|
|
2444
|
+
}
|
|
2445
|
+
return map;
|
|
2446
|
+
}, [entities, labels]);
|
|
2447
|
+
const openEntity = useCallback5(
|
|
2448
|
+
(entityId) => {
|
|
2449
|
+
pushRecent(entityId);
|
|
2450
|
+
setView({ kind: "list", entityId });
|
|
2451
|
+
},
|
|
2452
|
+
[pushRecent]
|
|
2453
|
+
);
|
|
2454
|
+
const navigateToRecord = useCallback5(
|
|
2455
|
+
(entityId, id) => {
|
|
2456
|
+
pushRecent(entityId);
|
|
2457
|
+
setView({ kind: "detail", entityId, id });
|
|
2458
|
+
},
|
|
2459
|
+
[pushRecent]
|
|
2460
|
+
);
|
|
2461
|
+
useEffect8(() => {
|
|
2462
|
+
const onKey = (e) => {
|
|
2463
|
+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
|
|
2464
|
+
e.preventDefault();
|
|
2465
|
+
setPaletteOpen((open) => !open);
|
|
2466
|
+
}
|
|
2467
|
+
};
|
|
2468
|
+
document.addEventListener("keydown", onKey);
|
|
2469
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
2470
|
+
}, []);
|
|
2471
|
+
const crumbs = useMemo7(() => {
|
|
2472
|
+
const home = { label: t.hub, icon: "hub", onClick: view.kind === "hub" ? void 0 : () => setView({ kind: "hub" }) };
|
|
2473
|
+
if (view.kind === "hub" || !activeEntity) return [home];
|
|
2474
|
+
const entityCrumb = {
|
|
2475
|
+
label: activeEntity.label.plural,
|
|
2476
|
+
icon: entityIcon(activeEntity),
|
|
2477
|
+
onClick: view.kind === "list" ? void 0 : () => setView({ kind: "list", entityId: activeEntity.id })
|
|
2478
|
+
};
|
|
2479
|
+
if (view.kind === "list") return [home, entityCrumb];
|
|
2480
|
+
const leaf = view.kind === "detail" ? { label: view.id } : view.kind === "edit" ? { label: t.editTitle(activeEntity.label.singular) } : { label: view.cloneFromId ? t.cloneTitle(activeEntity.label.singular) : t.newTitle(activeEntity.label.singular) };
|
|
2481
|
+
return [home, entityCrumb, leaf];
|
|
2482
|
+
}, [view, activeEntity, t]);
|
|
2483
|
+
const paletteSections = useMemo7(() => {
|
|
2484
|
+
const goTo = entityCommands(entities, groupOf, openEntity);
|
|
2485
|
+
goTo.unshift({ id: "hub", label: t.hub, icon: "hub", run: () => setView({ kind: "hub" }) });
|
|
2486
|
+
const screen = [];
|
|
2487
|
+
if (activeEntity?.capabilities.create) {
|
|
2488
|
+
screen.push({
|
|
2489
|
+
id: "create",
|
|
2490
|
+
label: t.newTitle(activeEntity.label.singular),
|
|
2491
|
+
icon: "plus",
|
|
2492
|
+
run: () => setView({ kind: "create", entityId: activeEntity.id })
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
const sections = [{ id: "goto", label: t.goTo, commands: goTo }];
|
|
2496
|
+
if (screen.length > 0) sections.unshift({ id: "screen", label: t.onThisScreen, commands: screen });
|
|
2497
|
+
return sections;
|
|
2498
|
+
}, [entities, groupOf, openEntity, activeEntity, t]);
|
|
1623
2499
|
if (loading && !data) {
|
|
1624
|
-
return /* @__PURE__ */
|
|
2500
|
+
return /* @__PURE__ */ jsx18("div", { className: "mst-boot", children: /* @__PURE__ */ jsx18(Spinner, { label: t.loadingMetadata }) });
|
|
1625
2501
|
}
|
|
1626
|
-
return /* @__PURE__ */
|
|
1627
|
-
/* @__PURE__ */
|
|
2502
|
+
return /* @__PURE__ */ jsxs16(AdminEntitiesProvider, { value: entities, children: [
|
|
2503
|
+
/* @__PURE__ */ jsx18(
|
|
1628
2504
|
Sidebar,
|
|
1629
2505
|
{
|
|
1630
2506
|
entities,
|
|
@@ -1632,83 +2508,113 @@ function AdminShell({ title, labels }) {
|
|
|
1632
2508
|
title,
|
|
1633
2509
|
labels,
|
|
1634
2510
|
recents,
|
|
1635
|
-
onSelect: openEntity
|
|
2511
|
+
onSelect: openEntity,
|
|
2512
|
+
onHome: () => setView({ kind: "hub" }),
|
|
2513
|
+
homeActive: view.kind === "hub"
|
|
1636
2514
|
}
|
|
1637
2515
|
),
|
|
1638
|
-
/* @__PURE__ */
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
{
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
2516
|
+
/* @__PURE__ */ jsxs16("div", { className: "mst-main", children: [
|
|
2517
|
+
/* @__PURE__ */ jsx18(Header, { crumbs, onOpenPalette: () => setPaletteOpen(true) }),
|
|
2518
|
+
/* @__PURE__ */ jsxs16("div", { className: "mst-content", children: [
|
|
2519
|
+
error && /* @__PURE__ */ jsx18(ErrorBanner, { error }),
|
|
2520
|
+
view.kind === "hub" && /* @__PURE__ */ jsx18(Hub, { entities, labels, onOpen: openEntity }),
|
|
2521
|
+
activeEntity && view.kind === "list" && /* @__PURE__ */ jsx18(
|
|
2522
|
+
EntityList,
|
|
2523
|
+
{
|
|
2524
|
+
entity: activeEntity,
|
|
2525
|
+
onOpen: (id) => setView({ kind: "detail", entityId: activeEntity.id, id }),
|
|
2526
|
+
onCreate: () => setView({ kind: "create", entityId: activeEntity.id }),
|
|
2527
|
+
onEdit: (id) => setView({ kind: "edit", entityId: activeEntity.id, id }),
|
|
2528
|
+
onClone: (id) => setView({ kind: "create", entityId: activeEntity.id, cloneFromId: id }),
|
|
2529
|
+
onNavigate: navigateToRecord
|
|
2530
|
+
}
|
|
2531
|
+
),
|
|
2532
|
+
activeEntity && view.kind === "detail" && /* @__PURE__ */ jsx18(
|
|
2533
|
+
EntityDetail,
|
|
2534
|
+
{
|
|
2535
|
+
entity: activeEntity,
|
|
2536
|
+
id: view.id,
|
|
2537
|
+
onBack: () => setView({ kind: "list", entityId: activeEntity.id }),
|
|
2538
|
+
onEdit: () => setView({ kind: "edit", entityId: activeEntity.id, id: view.id }),
|
|
2539
|
+
onClone: () => setView({ kind: "create", entityId: activeEntity.id, cloneFromId: view.id }),
|
|
2540
|
+
onDeleted: () => setView({ kind: "list", entityId: activeEntity.id }),
|
|
2541
|
+
onNavigate: navigateToRecord
|
|
2542
|
+
}
|
|
2543
|
+
),
|
|
2544
|
+
activeEntity && (view.kind === "create" || view.kind === "edit") && /* @__PURE__ */ jsx18(
|
|
2545
|
+
EntityForm,
|
|
2546
|
+
{
|
|
2547
|
+
entity: activeEntity,
|
|
2548
|
+
mode: view.kind,
|
|
2549
|
+
id: view.kind === "edit" ? view.id : void 0,
|
|
2550
|
+
cloneFromId: view.kind === "create" ? view.cloneFromId : void 0,
|
|
2551
|
+
onCancel: () => setView({ kind: "list", entityId: activeEntity.id }),
|
|
2552
|
+
onSaved: () => setView({ kind: "list", entityId: activeEntity.id })
|
|
2553
|
+
}
|
|
2554
|
+
)
|
|
2555
|
+
] })
|
|
2556
|
+
] }),
|
|
2557
|
+
paletteOpen && /* @__PURE__ */ jsx18(CommandPalette, { sections: paletteSections, onClose: () => setPaletteOpen(false) })
|
|
2558
|
+
] });
|
|
1676
2559
|
}
|
|
1677
2560
|
function MaestroAdmin(props) {
|
|
1678
|
-
const client =
|
|
2561
|
+
const client = useMemo7(
|
|
1679
2562
|
() => props.client ?? new MaestroClient({ apiUrl: props.apiUrl ?? "", headers: props.headers }),
|
|
1680
2563
|
[props.client, props.apiUrl, props.headers]
|
|
1681
2564
|
);
|
|
1682
|
-
const strings =
|
|
1683
|
-
|
|
2565
|
+
const strings = useMemo7(() => stringsFor(props.locale ?? "en"), [props.locale]);
|
|
2566
|
+
const [root, setRoot] = useState14(null);
|
|
2567
|
+
return /* @__PURE__ */ jsx18(AdminClientProvider, { value: client, children: /* @__PURE__ */ jsx18(I18nProvider, { value: strings, children: /* @__PURE__ */ jsx18(MaestroThemeProvider, { theme: props.theme, className: "mst-root", rootRef: setRoot, children: /* @__PURE__ */ jsx18(AdminRootProvider, { value: root, children: /* @__PURE__ */ jsx18(AdminShell, { title: props.title ?? "Maestro Admin", labels: props.labels }) }) }) }) });
|
|
1684
2568
|
}
|
|
1685
2569
|
export {
|
|
1686
2570
|
AdminClientProvider,
|
|
2571
|
+
Badge,
|
|
2572
|
+
Button,
|
|
2573
|
+
CommandPalette,
|
|
2574
|
+
EmptyState,
|
|
1687
2575
|
EntityDetail,
|
|
1688
2576
|
EntityForm,
|
|
1689
2577
|
EntityList,
|
|
2578
|
+
ErrorBanner,
|
|
1690
2579
|
FieldInput,
|
|
1691
2580
|
FieldValue,
|
|
2581
|
+
Header,
|
|
2582
|
+
Hub,
|
|
1692
2583
|
I18nProvider,
|
|
2584
|
+
Icon,
|
|
2585
|
+
IconButton,
|
|
1693
2586
|
JsonInput,
|
|
2587
|
+
MAESTRO_THEMES,
|
|
1694
2588
|
MaestroAdmin,
|
|
1695
2589
|
MaestroApiError,
|
|
1696
2590
|
MaestroClient,
|
|
2591
|
+
MaestroMark,
|
|
2592
|
+
MaestroSeal,
|
|
2593
|
+
MaestroThemeProvider,
|
|
1697
2594
|
ObjectValue,
|
|
1698
2595
|
Sidebar,
|
|
2596
|
+
Spinner,
|
|
2597
|
+
Toast,
|
|
1699
2598
|
applyLabels,
|
|
1700
2599
|
buildMenu,
|
|
1701
2600
|
detailFields,
|
|
2601
|
+
entityCommands,
|
|
2602
|
+
entityIcon,
|
|
1702
2603
|
formFields,
|
|
1703
2604
|
listFields,
|
|
1704
2605
|
primaryListFields,
|
|
1705
2606
|
stringsFor,
|
|
2607
|
+
useAdminRoot,
|
|
1706
2608
|
useAsync,
|
|
1707
2609
|
useClient,
|
|
2610
|
+
useColumnPrefs,
|
|
2611
|
+
useEntities,
|
|
2612
|
+
useEntity,
|
|
1708
2613
|
useEntityList,
|
|
1709
2614
|
useEntityRecord,
|
|
1710
2615
|
useMetadata,
|
|
1711
2616
|
useRecents,
|
|
1712
|
-
useT
|
|
2617
|
+
useT,
|
|
2618
|
+
useTheme
|
|
1713
2619
|
};
|
|
1714
2620
|
//# sourceMappingURL=index.js.map
|