@rebasepro/admin 0.13.0 → 0.13.1-canary.gcd6689e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{CollectionEditorDialog-CjXkWZ64.js → CollectionEditorDialog-CZZZpYOz.js} +3 -3
- package/dist/{CollectionEditorDialog-CjXkWZ64.js.map → CollectionEditorDialog-CZZZpYOz.js.map} +1 -1
- package/dist/{PropertyEditView-W8I3YV89.js → PropertyEditView-CeoHSmUg.js} +2 -2
- package/dist/{PropertyEditView-W8I3YV89.js.map → PropertyEditView-CeoHSmUg.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-BsLTNoqX.js → RouterCollectionsStudioView-CMKFWM2J.js} +4 -4
- package/dist/{RouterCollectionsStudioView-BsLTNoqX.js.map → RouterCollectionsStudioView-CMKFWM2J.js.map} +1 -1
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/AdditionalFieldValue.d.ts +25 -0
- package/dist/components/CollectionViewBinding/SlotValue.d.ts +29 -0
- package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +5 -3
- package/dist/components/CollectionViewBinding/SplitListShowButton.d.ts +12 -0
- package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +29 -4
- package/dist/components/DetailViewBinding.d.ts +6 -0
- package/dist/components/EditViewBinding.d.ts +7 -1
- package/dist/components/EntityIdentityBar.d.ts +10 -1
- package/dist/components/EntityViewBinding.d.ts +41 -6
- package/dist/components/app/Scaffold.d.ts +4 -3
- package/dist/{export-J79rMoNj.js → export-l19WevKo.js} +2 -2
- package/dist/{export-J79rMoNj.js.map → export-l19WevKo.js.map} +1 -1
- package/dist/form/components/FieldBlock.d.ts +16 -0
- package/dist/{history-D7L_JlLO.js → history-JL1ZmSKD.js} +4 -4
- package/dist/{history-D7L_JlLO.js.map → history-JL1ZmSKD.js.map} +1 -1
- package/dist/hooks/useEntityDisplay.d.ts +102 -0
- package/dist/hooks/useEntityDisplayTitle.d.ts +18 -9
- package/dist/{import-BvYl--U4.js → import-DNrKrFQV.js} +3 -3
- package/dist/{import-BvYl--U4.js.map → import-DNrKrFQV.js.map} +1 -1
- package/dist/index.js +79 -15
- package/dist/index.js.map +1 -1
- package/dist/preview/components/BooleanPreview.d.ts +3 -1
- package/dist/routes/RebaseRoute.d.ts +1 -1
- package/dist/types/components/PropertyPreviewProps.d.ts +7 -0
- package/dist/{util-GiwPgxnL.js → util-pQ3YwBve.js} +1022 -641
- package/dist/util-pQ3YwBve.js.map +1 -0
- package/package.json +9 -9
- package/src/components/AdditionalFieldValue.tsx +68 -0
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +5 -6
- package/src/components/CollectionViewBinding/BoardCardBinding.tsx +9 -27
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +11 -24
- package/src/components/CollectionViewBinding/EntityCardBinding.tsx +10 -33
- package/src/components/CollectionViewBinding/SlotValue.tsx +52 -0
- package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +7 -5
- package/src/components/CollectionViewBinding/SplitListShowButton.tsx +28 -0
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +115 -48
- package/src/components/DetailViewBinding.tsx +71 -54
- package/src/components/EditViewBinding.tsx +51 -33
- package/src/components/EntityIdentityBar.tsx +26 -4
- package/src/components/EntityPreviewBinding.tsx +24 -12
- package/src/components/EntityViewBinding.tsx +237 -39
- package/src/components/InlineEntityPreview.tsx +10 -10
- package/src/components/app/Scaffold.tsx +48 -7
- package/src/form/EntityForm.tsx +6 -24
- package/src/form/components/FieldBlock.tsx +24 -0
- package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +29 -5
- package/src/hooks/useEntityDisplay.tsx +258 -0
- package/src/hooks/useEntityDisplayTitle.tsx +27 -38
- package/src/preview/PropertyPreview.tsx +1 -1
- package/src/preview/components/BooleanPreview.tsx +5 -2
- package/src/routes/RebaseRoute.tsx +62 -4
- package/src/types/components/PropertyPreviewProps.tsx +8 -0
- package/dist/util-GiwPgxnL.js.map +0 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as React$1 from "react";
|
|
2
2
|
import React, { Suspense, createContext, createElement, forwardRef, lazy, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
3
3
|
import { deepEqual } from "fast-equals";
|
|
4
|
-
import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowDownIcon, ArrowLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, Badge, BooleanSwitchWithLabel, Button, CHIP_COLORS, CHIP_SEED_KEYS, CalendarIcon, Card, CardView, CenteredView, CheckCircleIcon, CheckIcon, Checkbox, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, ColumnsIcon, CopyIcon, DateTimeField, Dialog, DialogActions, DialogContent, DialogTitle, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderUpIcon, FunctionSquareIcon, GlobeIcon, HandleIcon, HashIcon, HistoryIcon, IconButton, ImageOffIcon, KanbanIcon, KanbanView, KeyRoundIcon, Label, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListView, LoadingButton, MailIcon, Markdown, Maximize2Icon, Menu, MenuItem, MinusIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, PencilIcon, PlusIcon, Popover, PopoverPrimitive, Portal, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SearchBar, SearchIcon, Select, SelectItem, Separator, SettingsIcon, Sheet, Skeleton, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, Tabs, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, Unlink2Icon, UploadIcon, UserIcon, VirtualTable, VirtualTableDateField, VirtualTableInput, VirtualTableInput as VirtualTableInput$1, VirtualTableNumberInput, VirtualTableSwitch, VoteIcon, XCircleIcon, XIcon, cls, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedDisabled, getColorSchemeForSeed, iconSize, lucideIcons, paperMixin, useDebouncedCallback, useInjectStyles, useOutsideAlerter } from "@rebasepro/ui";
|
|
4
|
+
import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowDownIcon, ArrowLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, Badge, BooleanSwitchWithLabel, Button, CHIP_COLORS, CHIP_SEED_KEYS, CalendarIcon, Card, CardView, CenteredView, CheckCircleIcon, CheckIcon, Checkbox, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, ColumnsIcon, CopyIcon, DateTimeField, Dialog, DialogActions, DialogContent, DialogTitle, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderUpIcon, FunctionSquareIcon, GlobeIcon, HandleIcon, HashIcon, HistoryIcon, IconButton, ImageOffIcon, KanbanIcon, KanbanView, KeyRoundIcon, Label, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListView, LoadingButton, MailIcon, Markdown, Maximize2Icon, Menu, MenuItem, MinusIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, PencilIcon, PlusIcon, Popover, PopoverPrimitive, Portal, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SearchBar, SearchIcon, Select, SelectItem, Separator, SettingsIcon, Sheet, Skeleton, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, Tabs, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, Unlink2Icon, UploadIcon, UserIcon, VirtualTable, VirtualTableDateField, VirtualTableInput, VirtualTableInput as VirtualTableInput$1, VirtualTableNumberInput, VirtualTableSwitch, VoteIcon, XCircleIcon, XIcon, cls, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedDisabled, getColorSchemeForSeed, iconSize, lucideIcons, paperMixin, useDebouncedCallback, useInjectStyles, useOutsideAlerter } from "@rebasepro/ui";
|
|
5
5
|
import { CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, canReadCollection, enumToObjectEntries, getArrayResolvedProperties, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEntityChildViews, getLabelOrConfigFrom, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, isPropertyBuilder, normalizeToEntityRelation, resolveArrayProperties, resolveEnumValues, resolveProperty, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource } from "@rebasepro/common";
|
|
6
|
-
import { CONTAINER_FULL_WIDTH, CollectionScopeProvider, DEFAULT_PAGE_SIZE, ErrorTooltip, ErrorView, IconForView, PluginProviderStack, SIDE_PANEL_DEFAULT_WIDTH, StorageSourceContext, UnsavedChangesDialog, UserDisplay, addInitialSlash, apiBaseOf, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, flattenKeys, getAdminEntityChildViews, getCollectionBySlugWithin, getCollectionPathsCombinations, getColorScheme, getColumnKeysForProperty, getEntityFromCache, getEntityFromMemoryCache, getEntityImagePreviewPropertyKey, getFormFieldKeys, getIcon, getLastSegment as getLastSegment$1, getLeadingRelationTitleKey, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getRelationIncludeParams, getRowHeight, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, iconsSearch, isFilterableRelation, isHidden, isReadOnly, looksLikeIdentifierValue, removeEntityFromCache, removeEntityFromMemoryCache, removeInitialAndTrailingSlashes, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1, removeInitialSlash, removeTrailingSlash, removeTrailingSlash as removeTrailingSlash$1, resolveCollectionPathIds, resolveCollectionPathIds as resolveCollectionPathIds$1, resolveComponentRef, resolveDefaultSelectedView, resolveFilterOperators, resolveFormLayout, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAnalyticsController, useApiBase, useApiConfig, useAuthController, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataTableController, useFetch, useLargeLayout, useModeController, useNavigationBlocker, usePermissions, useRebaseClient, useRebaseContext, useRelationSelector, useResolvedComponent, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence } from "@rebasepro/app";
|
|
6
|
+
import { CONTAINER_FULL_WIDTH, CollectionScopeProvider, DEFAULT_PAGE_SIZE, EntityDisplayCache, ErrorTooltip, ErrorView, IconForView, PluginProviderStack, SIDE_PANEL_DEFAULT_WIDTH, StorageSourceContext, UnsavedChangesDialog, UserDisplay, addInitialSlash, apiBaseOf, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, entityDisplayKey, flattenKeys, getAdminEntityChildViews, getCollectionBySlugWithin, getCollectionPathsCombinations, getColorScheme, getColumnKeysForProperty, getDisplayPropertyKey, getDisplayResolver, getEntityFromCache, getEntityFromMemoryCache, getEntityImagePreviewPropertyKey, getFormFieldKeys, getIcon, getLastSegment as getLastSegment$1, getLeadingRelationTitleKey, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getPropertyInPath, getRelationIncludeParams, getRowHeight, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, iconsSearch, isFilterableRelation, isHidden, isReadOnly, looksLikeIdentifierValue, removeEntityFromCache, removeEntityFromMemoryCache, removeInitialAndTrailingSlashes, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1, removeInitialSlash, removeTrailingSlash, removeTrailingSlash as removeTrailingSlash$1, resolveCollectionPathIds, resolveCollectionPathIds as resolveCollectionPathIds$1, resolveComponentRef, resolveDefaultSelectedView, resolveFilterOperators, resolveFormLayout, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAnalyticsController, useApiBase, useApiConfig, useAuthController, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataTableController, useFetch, useLargeLayout, useModeController, useNavigationBlocker, usePermissions, useRebaseClient, useRebaseContext, useRelationSelector, useResolvedComponent, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence } from "@rebasepro/app";
|
|
7
7
|
import { Field, Formex, getIn, setIn, useCreateFormex, useFormex } from "@rebasepro/forms";
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { resolveAdminCollection } from "@rebasepro/admin-types";
|
|
9
10
|
import { defaultDateFormat, getHashValue, getValueInPath, hydrateRegExp, isObject, mergeDeep, randomString, toArray } from "@rebasepro/utils";
|
|
10
11
|
import { format } from "date-fns";
|
|
11
12
|
import * as locales from "date-fns/locale";
|
|
@@ -14,7 +15,7 @@ import { useDropzone } from "react-dropzone";
|
|
|
14
15
|
import { EntityReference, EntityRelation, GeoPoint, Vector, getCollectionDataPath, getDataSourceCapabilities, isRelationalCollectionConfig } from "@rebasepro/types";
|
|
15
16
|
import { z } from "zod";
|
|
16
17
|
import useMeasure from "react-use-measure";
|
|
17
|
-
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
|
18
|
+
import { Link, useLocation, useNavigate, useSearchParams } from "react-router";
|
|
18
19
|
import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
|
|
19
20
|
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
|
|
20
21
|
import { SortableContext, horizontalListSortingStrategy, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
@@ -23,7 +24,6 @@ import ExcelJS from "exceljs";
|
|
|
23
24
|
import { inferTypeFromValue } from "@rebasepro/inference";
|
|
24
25
|
import { Highlight, themes } from "prism-react-renderer";
|
|
25
26
|
import { generateKeyBetween, generateNKeysBetween } from "fractional-indexing";
|
|
26
|
-
import { resolveAdminCollection } from "@rebasepro/admin-types";
|
|
27
27
|
//#region src/util/property_utils.tsx
|
|
28
28
|
/**
|
|
29
29
|
* Resolve a size value (string token or number) to a numeric pixel value
|
|
@@ -95,13 +95,13 @@ function getIconForProperty(property, size = "small", fields = {}) {
|
|
|
95
95
|
* @param properties
|
|
96
96
|
* @param path
|
|
97
97
|
*/
|
|
98
|
-
function getPropertyInPath(properties, path) {
|
|
98
|
+
function getPropertyInPath$1(properties, path) {
|
|
99
99
|
if (typeof properties === "object") {
|
|
100
100
|
if (path in properties) return properties[path];
|
|
101
101
|
if (path.includes(".")) {
|
|
102
102
|
const pathSegments = path.split(".");
|
|
103
103
|
const childProperty = properties[pathSegments[0]];
|
|
104
|
-
if (typeof childProperty === "object" && childProperty.type === "map" && childProperty.properties) return getPropertyInPath(childProperty.properties, pathSegments.slice(1).join("."));
|
|
104
|
+
if (typeof childProperty === "object" && childProperty.type === "map" && childProperty.properties) return getPropertyInPath$1(childProperty.properties, pathSegments.slice(1).join("."));
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
}
|
|
@@ -199,6 +199,28 @@ function PropertyIdCopyTooltipContent({ propertyKey }) {
|
|
|
199
199
|
//#endregion
|
|
200
200
|
//#region src/form/components/FieldBlock.tsx
|
|
201
201
|
/**
|
|
202
|
+
* Grid placement for a span.
|
|
203
|
+
*
|
|
204
|
+
* Written out as literal class strings rather than composed, because Tailwind
|
|
205
|
+
* scans source text: a template literal would produce classes that exist at
|
|
206
|
+
* runtime and were never generated into the stylesheet.
|
|
207
|
+
*
|
|
208
|
+
* Below the column breakpoint every field takes the single available column, so
|
|
209
|
+
* the side panel, the dialog and mobile all get a plain stack.
|
|
210
|
+
*
|
|
211
|
+
* Lives here rather than in the form, because the read-only rendering of a
|
|
212
|
+
* record places its fields on the same grid — a record that changes shape when
|
|
213
|
+
* you press Edit is the bug this whole layout exists to avoid.
|
|
214
|
+
*/
|
|
215
|
+
function spanClass(span) {
|
|
216
|
+
switch (span) {
|
|
217
|
+
case 1: return "@2xl/col:col-span-1";
|
|
218
|
+
case 2: return "@2xl/col:col-span-2";
|
|
219
|
+
case 3: return "@2xl/col:col-span-3";
|
|
220
|
+
case 4: return "@2xl/col:col-span-4";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
202
224
|
* Does this property's editor supply its own heading?
|
|
203
225
|
*
|
|
204
226
|
* Arrays, maps, `oneOf` blocks and key-value editors render as a collapsible
|
|
@@ -273,6 +295,61 @@ function FieldBlock({ propertyKey, property, showLabel, children }) {
|
|
|
273
295
|
});
|
|
274
296
|
}
|
|
275
297
|
//#endregion
|
|
298
|
+
//#region src/components/AdditionalFieldValue.tsx
|
|
299
|
+
/**
|
|
300
|
+
* Renders an `additionalFields` entry that supplies a `value` rather than a
|
|
301
|
+
* `Builder`.
|
|
302
|
+
*
|
|
303
|
+
* `value` is typed `string | number | Promise<string | number>`, and the promise
|
|
304
|
+
* arm has been part of that type from the start — the CSV export awaits it
|
|
305
|
+
* correctly. Every *renderer* called `.toString()` on the result instead, so an
|
|
306
|
+
* async value drew the literal text `[object Promise]` in the cell: a shape the
|
|
307
|
+
* type promised, that worked in one of its two consumers, and failed silently in
|
|
308
|
+
* the other. That is what made the obvious port of a FireCMS column — one that
|
|
309
|
+
* fetches a title from a subcollection — look supported and not be.
|
|
310
|
+
*
|
|
311
|
+
* Three call sites had the same three lines copied between them (the table cell,
|
|
312
|
+
* the form and the read-only record), which is why the bug reached all three.
|
|
313
|
+
* Now there is one.
|
|
314
|
+
*/
|
|
315
|
+
function AdditionalFieldValue({ field, entity, context }) {
|
|
316
|
+
const [resolved, setResolved] = useState(() => {
|
|
317
|
+
const value = field.value?.({
|
|
318
|
+
entity,
|
|
319
|
+
context
|
|
320
|
+
});
|
|
321
|
+
return isPromise(value) ? void 0 : value;
|
|
322
|
+
});
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
const value = field.value?.({
|
|
325
|
+
entity,
|
|
326
|
+
context
|
|
327
|
+
});
|
|
328
|
+
if (!isPromise(value)) {
|
|
329
|
+
setResolved(value);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
let live = true;
|
|
333
|
+
value.then((result) => {
|
|
334
|
+
if (live) setResolved(result);
|
|
335
|
+
}, (error) => {
|
|
336
|
+
if (live) setResolved(void 0);
|
|
337
|
+
console.warn(`[rebase] Additional field "${field.key}" failed to resolve:`, error);
|
|
338
|
+
});
|
|
339
|
+
return () => {
|
|
340
|
+
live = false;
|
|
341
|
+
};
|
|
342
|
+
}, [
|
|
343
|
+
field,
|
|
344
|
+
entity,
|
|
345
|
+
context
|
|
346
|
+
]);
|
|
347
|
+
return /* @__PURE__ */ jsx(Fragment, { children: resolved === void 0 || resolved === null ? null : String(resolved) });
|
|
348
|
+
}
|
|
349
|
+
function isPromise(value) {
|
|
350
|
+
return typeof value?.then === "function";
|
|
351
|
+
}
|
|
352
|
+
//#endregion
|
|
276
353
|
//#region src/form/components/FormRail.tsx
|
|
277
354
|
/**
|
|
278
355
|
* The metadata column beside the form.
|
|
@@ -1096,6 +1173,219 @@ function resolveTitleToString(title) {
|
|
|
1096
1173
|
}
|
|
1097
1174
|
}
|
|
1098
1175
|
//#endregion
|
|
1176
|
+
//#region src/hooks/useResolvedUsers.ts
|
|
1177
|
+
var caches = /* @__PURE__ */ new Map();
|
|
1178
|
+
function getCache(baseUrl) {
|
|
1179
|
+
let cache = caches.get(baseUrl);
|
|
1180
|
+
if (!cache) {
|
|
1181
|
+
cache = {
|
|
1182
|
+
users: /* @__PURE__ */ new Map(),
|
|
1183
|
+
pending: /* @__PURE__ */ new Map(),
|
|
1184
|
+
queue: /* @__PURE__ */ new Set()
|
|
1185
|
+
};
|
|
1186
|
+
caches.set(baseUrl, cache);
|
|
1187
|
+
}
|
|
1188
|
+
return cache;
|
|
1189
|
+
}
|
|
1190
|
+
async function fetchUsers(apiBase, ids, token) {
|
|
1191
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1192
|
+
for (const id of ids) resolved.set(id, null);
|
|
1193
|
+
const params = new URLSearchParams({ ids: ids.join(",") });
|
|
1194
|
+
const response = await fetch(`${apiBase}/admin/users?${params}`, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
|
|
1195
|
+
if (!response.ok) return resolved;
|
|
1196
|
+
const data = await response.json();
|
|
1197
|
+
const rows = data.users ?? data.data ?? [];
|
|
1198
|
+
for (const row of rows) {
|
|
1199
|
+
const uid = row.uid ?? row.id;
|
|
1200
|
+
if (!uid) continue;
|
|
1201
|
+
resolved.set(uid, {
|
|
1202
|
+
uid,
|
|
1203
|
+
email: row.email ?? "",
|
|
1204
|
+
displayName: row.displayName ?? row.display_name ?? null,
|
|
1205
|
+
photoURL: row.photoURL ?? row.photo_url ?? null,
|
|
1206
|
+
providerId: "custom",
|
|
1207
|
+
isAnonymous: false,
|
|
1208
|
+
roles: row.roles ?? []
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
return resolved;
|
|
1212
|
+
}
|
|
1213
|
+
function requestUser(baseUrl, uid, getAuthToken) {
|
|
1214
|
+
const cache = getCache(baseUrl);
|
|
1215
|
+
const existing = cache.pending.get(uid);
|
|
1216
|
+
if (existing) return existing;
|
|
1217
|
+
cache.queue.add(uid);
|
|
1218
|
+
const promise = new Promise((resolve, reject) => {
|
|
1219
|
+
if (cache.flush) clearTimeout(cache.flush);
|
|
1220
|
+
cache.flush = setTimeout(async () => {
|
|
1221
|
+
const ids = [...cache.queue];
|
|
1222
|
+
cache.queue.clear();
|
|
1223
|
+
cache.flush = void 0;
|
|
1224
|
+
try {
|
|
1225
|
+
const resolved = await fetchUsers(baseUrl, ids, await getAuthToken?.());
|
|
1226
|
+
for (const id of ids) {
|
|
1227
|
+
cache.users.set(id, resolved.get(id) ?? null);
|
|
1228
|
+
cache.pending.delete(id);
|
|
1229
|
+
}
|
|
1230
|
+
resolve();
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
for (const id of ids) cache.pending.delete(id);
|
|
1233
|
+
reject(error);
|
|
1234
|
+
}
|
|
1235
|
+
}, 0);
|
|
1236
|
+
});
|
|
1237
|
+
cache.pending.set(uid, promise);
|
|
1238
|
+
return promise;
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* The auth user behind a `userSelect` value, or `null` while it is loading or
|
|
1242
|
+
* when it cannot be resolved.
|
|
1243
|
+
*/
|
|
1244
|
+
function useResolvedUser(uid) {
|
|
1245
|
+
const client = useRebaseClient();
|
|
1246
|
+
const { getAuthToken } = useAuthController();
|
|
1247
|
+
const baseUrl = apiBaseOf(client);
|
|
1248
|
+
const [user, setUser] = useState(() => baseUrl && uid ? getCache(baseUrl).users.get(uid) ?? null : null);
|
|
1249
|
+
useEffect(() => {
|
|
1250
|
+
if (!baseUrl || !uid) {
|
|
1251
|
+
setUser(null);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
const cache = getCache(baseUrl);
|
|
1255
|
+
if (cache.users.has(uid)) {
|
|
1256
|
+
setUser(cache.users.get(uid) ?? null);
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
let active = true;
|
|
1260
|
+
requestUser(baseUrl, uid, getAuthToken).then(() => {
|
|
1261
|
+
if (active) setUser(getCache(baseUrl).users.get(uid) ?? null);
|
|
1262
|
+
}).catch(() => {
|
|
1263
|
+
if (active) setUser(null);
|
|
1264
|
+
});
|
|
1265
|
+
return () => {
|
|
1266
|
+
active = false;
|
|
1267
|
+
};
|
|
1268
|
+
}, [
|
|
1269
|
+
baseUrl,
|
|
1270
|
+
uid,
|
|
1271
|
+
getAuthToken
|
|
1272
|
+
]);
|
|
1273
|
+
return user;
|
|
1274
|
+
}
|
|
1275
|
+
/** Human-readable label for a resolved user. */
|
|
1276
|
+
function getUserLabel(user) {
|
|
1277
|
+
return user.displayName || user.email || user.uid;
|
|
1278
|
+
}
|
|
1279
|
+
//#endregion
|
|
1280
|
+
//#region src/hooks/useEntityDisplay.tsx
|
|
1281
|
+
/**
|
|
1282
|
+
* One shared store, so every surface agrees on a record and resolves it once.
|
|
1283
|
+
*
|
|
1284
|
+
* Module-level rather than a provider: two app instances on one page share a
|
|
1285
|
+
* cache keyed by collection path and record id, which *is* the same record.
|
|
1286
|
+
* Nothing here is user-specific — a resolver that reads per-user data must
|
|
1287
|
+
* invalidate on sign-out, which {@link useEntityDisplayCache} exposes.
|
|
1288
|
+
*/
|
|
1289
|
+
var sharedCache;
|
|
1290
|
+
/**
|
|
1291
|
+
* The store behind {@link useEntityDisplay}, for invalidating after a write.
|
|
1292
|
+
*
|
|
1293
|
+
* Built on first use rather than at module scope. Importing a module should not
|
|
1294
|
+
* construct anything: it runs during SSR where there is nothing to cache, and it
|
|
1295
|
+
* made this module unimportable from any test that stubs `@rebasepro/app` —
|
|
1296
|
+
* which several do, and which is a legitimate thing for them to do.
|
|
1297
|
+
*/
|
|
1298
|
+
function useEntityDisplayCache() {
|
|
1299
|
+
if (!sharedCache) sharedCache = new EntityDisplayCache();
|
|
1300
|
+
return sharedCache;
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* What fills one display role of one record.
|
|
1304
|
+
*
|
|
1305
|
+
* This is the only implementation. Before it there were seven for the title
|
|
1306
|
+
* alone — the identity bar, the detail header, inline previews, reference cards,
|
|
1307
|
+
* list slots, relation chips and the headless view — each re-deriving it from
|
|
1308
|
+
* `collection.properties` and disagreeing about the answer.
|
|
1309
|
+
*
|
|
1310
|
+
* Resolution order, per role:
|
|
1311
|
+
*
|
|
1312
|
+
* 1. `admin.display[role]` as a **resolver** — awaited, cached per record and
|
|
1313
|
+
* role, with the derived value showing meanwhile so nothing flickers empty.
|
|
1314
|
+
* 2. `admin.display[role]` as a **property path** (or, for the title, the
|
|
1315
|
+
* deprecated `titleProperty`).
|
|
1316
|
+
* 3. The **derived** answer: for the title, the best-ranked property carrying a
|
|
1317
|
+
* readable value; for the rest, today's per-role heuristics.
|
|
1318
|
+
*
|
|
1319
|
+
* A user-picker title resolves to the person behind the id, as it always did —
|
|
1320
|
+
* that path is now here rather than in two of the seven.
|
|
1321
|
+
*/
|
|
1322
|
+
function useEntityDisplay(role, { collection, entity, values: valuesProp, enabled = true }) {
|
|
1323
|
+
const context = useRebaseContext();
|
|
1324
|
+
const cache = useEntityDisplayCache();
|
|
1325
|
+
const values = valuesProp ?? entity?.values;
|
|
1326
|
+
const declaredKey = collection ? getDisplayPropertyKey(collection, role) : void 0;
|
|
1327
|
+
const propertyKey = collection && role === "title" && !declaredKey ? getEntityTitlePropertyKeyForEntity(collection, values, entity?.id) : declaredKey;
|
|
1328
|
+
const rawValue = values && propertyKey ? getValueInPath(values, propertyKey) : void 0;
|
|
1329
|
+
const titleUser = useResolvedUser(collection && role === "title" && isUserSelectProperty(collection, propertyKey) && typeof rawValue === "string" ? rawValue : void 0);
|
|
1330
|
+
const derived = useMemo(() => {
|
|
1331
|
+
if (titleUser) return getUserLabel(titleUser);
|
|
1332
|
+
if (rawValue === void 0 || rawValue === null) return void 0;
|
|
1333
|
+
if (role === "title" || role === "subtitle" || role === "status") return resolveTitleToString(rawValue) || void 0;
|
|
1334
|
+
return rawValue;
|
|
1335
|
+
}, [
|
|
1336
|
+
titleUser,
|
|
1337
|
+
rawValue,
|
|
1338
|
+
role
|
|
1339
|
+
]);
|
|
1340
|
+
const resolver = collection ? getDisplayResolver(collection, role) : void 0;
|
|
1341
|
+
const canResolve = Boolean(enabled && resolver && entity);
|
|
1342
|
+
const key = entity ? entityDisplayKey(entity.path, entity.id, role) : void 0;
|
|
1343
|
+
const resolved = useSyncExternalStore((cb) => cache.subscribe(cb), () => canResolve && key ? cache.peek(key) : void 0, () => void 0);
|
|
1344
|
+
const loading = Boolean(canResolve && key && resolved === void 0);
|
|
1345
|
+
useEffect(() => {
|
|
1346
|
+
if (!canResolve || !key || !resolver || !entity) return;
|
|
1347
|
+
if (cache.peek(key) !== void 0) return;
|
|
1348
|
+
cache.resolve(key, () => resolver({
|
|
1349
|
+
entity,
|
|
1350
|
+
context
|
|
1351
|
+
})).catch((error) => {
|
|
1352
|
+
console.warn(`[rebase] Could not resolve ${role} for ${key}:`, error);
|
|
1353
|
+
});
|
|
1354
|
+
}, [
|
|
1355
|
+
cache,
|
|
1356
|
+
canResolve,
|
|
1357
|
+
key,
|
|
1358
|
+
resolver,
|
|
1359
|
+
entity,
|
|
1360
|
+
context,
|
|
1361
|
+
role
|
|
1362
|
+
]);
|
|
1363
|
+
const value = (canResolve && resolved !== null ? resolved : void 0) ?? derived;
|
|
1364
|
+
return useMemo(() => ({
|
|
1365
|
+
value,
|
|
1366
|
+
loading,
|
|
1367
|
+
source: collection && propertyKey && value === derived && derived !== void 0 ? {
|
|
1368
|
+
key: propertyKey,
|
|
1369
|
+
property: getPropertyInPath(collection.properties, propertyKey),
|
|
1370
|
+
value: rawValue
|
|
1371
|
+
} : void 0
|
|
1372
|
+
}), [
|
|
1373
|
+
value,
|
|
1374
|
+
loading,
|
|
1375
|
+
propertyKey,
|
|
1376
|
+
derived,
|
|
1377
|
+
collection,
|
|
1378
|
+
rawValue
|
|
1379
|
+
]);
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* What the record is called — the one role asked for often enough on its own to
|
|
1383
|
+
* deserve a name.
|
|
1384
|
+
*/
|
|
1385
|
+
function useEntityTitle(params) {
|
|
1386
|
+
return useEntityDisplay("title", params);
|
|
1387
|
+
}
|
|
1388
|
+
//#endregion
|
|
1099
1389
|
//#region src/hooks/navigation/contexts/CollectionRegistryContext.tsx
|
|
1100
1390
|
var CollectionRegistryContext = createContext({
|
|
1101
1391
|
getCollection: () => void 0,
|
|
@@ -1172,6 +1462,10 @@ function EntityPreviewBindingData({ actions, collection: collectionProp, preview
|
|
|
1172
1462
|
const customizationController = useCustomizationController();
|
|
1173
1463
|
const collectionRegistryController = useCollectionRegistryController();
|
|
1174
1464
|
const collection = collectionProp ?? collectionRegistryController.getCollection(entity.path);
|
|
1465
|
+
const title = useEntityTitle({
|
|
1466
|
+
collection,
|
|
1467
|
+
entity
|
|
1468
|
+
});
|
|
1175
1469
|
const listProperties = useMemo(() => {
|
|
1176
1470
|
if (!collection) return [];
|
|
1177
1471
|
return previewKeys ?? getEntityPreviewKeys(authController, collection, customizationController.propertyConfigs, previewKeys, size === "medium" || size === "large" ? 3 : 2);
|
|
@@ -1183,7 +1477,7 @@ function EntityPreviewBindingData({ actions, collection: collectionProp, preview
|
|
|
1183
1477
|
customizationController.propertyConfigs
|
|
1184
1478
|
]);
|
|
1185
1479
|
if (!collection) return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ErrorView, { error: `Collection not found: ${entity.path}` }) });
|
|
1186
|
-
const titleProperty = includeTitle ?
|
|
1480
|
+
const titleProperty = includeTitle ? title.source?.key : void 0;
|
|
1187
1481
|
const imagePropertyKey = includeImage ? getEntityImagePreviewPropertyKey(collection) : void 0;
|
|
1188
1482
|
const imageProperty = imagePropertyKey ? collection.properties[imagePropertyKey] : void 0;
|
|
1189
1483
|
const ofProp = imageProperty && "of" in imageProperty ? imageProperty.of : void 0;
|
|
@@ -1222,20 +1516,20 @@ function EntityPreviewBindingData({ actions, collection: collectionProp, preview
|
|
|
1222
1516
|
children: entity.id
|
|
1223
1517
|
})
|
|
1224
1518
|
}) : /* @__PURE__ */ jsx(Skeleton, {})),
|
|
1225
|
-
titleProperty && /* @__PURE__ */ jsx("div", {
|
|
1519
|
+
includeTitle && (titleProperty || title.value) && /* @__PURE__ */ jsx("div", {
|
|
1226
1520
|
className: "truncate my-0.5 text-sm font-medium text-text-primary dark:text-text-primary-dark",
|
|
1227
|
-
children: entity ? /* @__PURE__ */ jsx(
|
|
1228
|
-
|
|
1229
|
-
value: getValueInPath(entity.values, titleProperty),
|
|
1230
|
-
property: collection.properties[titleProperty],
|
|
1521
|
+
children: !entity && titleProperty ? /* @__PURE__ */ jsx(SkeletonPropertyComponent, {
|
|
1522
|
+
property: getPropertyInPath$1(collection.properties, titleProperty),
|
|
1231
1523
|
size: "medium"
|
|
1232
|
-
}) : /* @__PURE__ */ jsx(
|
|
1233
|
-
|
|
1524
|
+
}) : titleProperty ? /* @__PURE__ */ jsx(PropertyPreview, {
|
|
1525
|
+
propertyKey: titleProperty,
|
|
1526
|
+
value: title.source?.value,
|
|
1527
|
+
property: getPropertyInPath$1(collection.properties, titleProperty),
|
|
1234
1528
|
size: "medium"
|
|
1235
|
-
})
|
|
1529
|
+
}) : title.value
|
|
1236
1530
|
}),
|
|
1237
1531
|
restProperties && restProperties.map((key) => {
|
|
1238
|
-
const childProperty = getPropertyInPath(collection.properties, key);
|
|
1532
|
+
const childProperty = getPropertyInPath$1(collection.properties, key);
|
|
1239
1533
|
if (!childProperty) return null;
|
|
1240
1534
|
const valueInPath = getValueInPath(entity.values, key);
|
|
1241
1535
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -1355,9 +1649,12 @@ function InlineEntityPreview({ entity, collection: collectionProp, label, disabl
|
|
|
1355
1649
|
const analyticsController = useAnalyticsController();
|
|
1356
1650
|
const sidePanelController = useSidePanel();
|
|
1357
1651
|
const collection = collectionProp ?? collectionRegistryController.getCollection(entity.path);
|
|
1358
|
-
const
|
|
1359
|
-
|
|
1360
|
-
|
|
1652
|
+
const { value: title } = useEntityTitle({
|
|
1653
|
+
collection,
|
|
1654
|
+
entity,
|
|
1655
|
+
enabled: Boolean(collection)
|
|
1656
|
+
});
|
|
1657
|
+
const resolvedLabel = label ?? title ?? String(entity.id);
|
|
1361
1658
|
const interactive = !disabled && (onClick !== void 0 || includeEntityLink && collection !== void 0);
|
|
1362
1659
|
const handleClick = (event) => {
|
|
1363
1660
|
if (!interactive) return;
|
|
@@ -2106,147 +2403,43 @@ function DatePreview({ date, mode = "date_time", timezone }) {
|
|
|
2106
2403
|
/**
|
|
2107
2404
|
* @group Preview components
|
|
2108
2405
|
*/
|
|
2109
|
-
function BooleanPreview({ value, size, property }) {
|
|
2406
|
+
function BooleanPreview({ value, size, property, hideLabel }) {
|
|
2110
2407
|
return /* @__PURE__ */ jsxs("div", {
|
|
2111
2408
|
className: "flex flex-row gap-2 items-center",
|
|
2112
2409
|
children: [/* @__PURE__ */ jsx(Checkbox, {
|
|
2113
2410
|
checked: value,
|
|
2114
2411
|
padding: false,
|
|
2115
|
-
size,
|
|
2116
|
-
color: "secondary"
|
|
2117
|
-
}), property.name && /* @__PURE__ */ jsx("span", {
|
|
2118
|
-
className: cls("text-text-secondary dark:text-text-secondary-dark", size === "small" ? "text-sm" : ""),
|
|
2119
|
-
children: property.name
|
|
2120
|
-
})]
|
|
2121
|
-
});
|
|
2122
|
-
}
|
|
2123
|
-
//#endregion
|
|
2124
|
-
//#region src/preview/property_previews/NumberPropertyPreview.tsx
|
|
2125
|
-
/**
|
|
2126
|
-
* @group Preview components
|
|
2127
|
-
*/
|
|
2128
|
-
function NumberPropertyPreview({ value, property, size }) {
|
|
2129
|
-
const numValue = value;
|
|
2130
|
-
if (property.enum) {
|
|
2131
|
-
const enumKey = numValue;
|
|
2132
|
-
const enumValues = enumToObjectEntries(property.enum);
|
|
2133
|
-
if (!enumValues) return /* @__PURE__ */ jsx("span", {
|
|
2134
|
-
className: size === "small" ? "text-sm" : "",
|
|
2135
|
-
children: numValue
|
|
2136
|
-
});
|
|
2137
|
-
return /* @__PURE__ */ jsx(EnumValuesChip, {
|
|
2138
|
-
enumKey,
|
|
2139
|
-
enumValues,
|
|
2140
|
-
size: size !== "medium" ? "small" : "medium"
|
|
2141
|
-
});
|
|
2142
|
-
} else return /* @__PURE__ */ jsx("span", {
|
|
2143
|
-
className: size === "small" ? "text-sm" : "",
|
|
2144
|
-
children: numValue
|
|
2145
|
-
});
|
|
2146
|
-
}
|
|
2147
|
-
//#endregion
|
|
2148
|
-
//#region src/hooks/useResolvedUsers.ts
|
|
2149
|
-
var caches = /* @__PURE__ */ new Map();
|
|
2150
|
-
function getCache(baseUrl) {
|
|
2151
|
-
let cache = caches.get(baseUrl);
|
|
2152
|
-
if (!cache) {
|
|
2153
|
-
cache = {
|
|
2154
|
-
users: /* @__PURE__ */ new Map(),
|
|
2155
|
-
pending: /* @__PURE__ */ new Map(),
|
|
2156
|
-
queue: /* @__PURE__ */ new Set()
|
|
2157
|
-
};
|
|
2158
|
-
caches.set(baseUrl, cache);
|
|
2159
|
-
}
|
|
2160
|
-
return cache;
|
|
2161
|
-
}
|
|
2162
|
-
async function fetchUsers(apiBase, ids, token) {
|
|
2163
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
2164
|
-
for (const id of ids) resolved.set(id, null);
|
|
2165
|
-
const params = new URLSearchParams({ ids: ids.join(",") });
|
|
2166
|
-
const response = await fetch(`${apiBase}/admin/users?${params}`, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
|
|
2167
|
-
if (!response.ok) return resolved;
|
|
2168
|
-
const data = await response.json();
|
|
2169
|
-
const rows = data.users ?? data.data ?? [];
|
|
2170
|
-
for (const row of rows) {
|
|
2171
|
-
const uid = row.uid ?? row.id;
|
|
2172
|
-
if (!uid) continue;
|
|
2173
|
-
resolved.set(uid, {
|
|
2174
|
-
uid,
|
|
2175
|
-
email: row.email ?? "",
|
|
2176
|
-
displayName: row.displayName ?? row.display_name ?? null,
|
|
2177
|
-
photoURL: row.photoURL ?? row.photo_url ?? null,
|
|
2178
|
-
providerId: "custom",
|
|
2179
|
-
isAnonymous: false,
|
|
2180
|
-
roles: row.roles ?? []
|
|
2181
|
-
});
|
|
2182
|
-
}
|
|
2183
|
-
return resolved;
|
|
2184
|
-
}
|
|
2185
|
-
function requestUser(baseUrl, uid, getAuthToken) {
|
|
2186
|
-
const cache = getCache(baseUrl);
|
|
2187
|
-
const existing = cache.pending.get(uid);
|
|
2188
|
-
if (existing) return existing;
|
|
2189
|
-
cache.queue.add(uid);
|
|
2190
|
-
const promise = new Promise((resolve, reject) => {
|
|
2191
|
-
if (cache.flush) clearTimeout(cache.flush);
|
|
2192
|
-
cache.flush = setTimeout(async () => {
|
|
2193
|
-
const ids = [...cache.queue];
|
|
2194
|
-
cache.queue.clear();
|
|
2195
|
-
cache.flush = void 0;
|
|
2196
|
-
try {
|
|
2197
|
-
const resolved = await fetchUsers(baseUrl, ids, await getAuthToken?.());
|
|
2198
|
-
for (const id of ids) {
|
|
2199
|
-
cache.users.set(id, resolved.get(id) ?? null);
|
|
2200
|
-
cache.pending.delete(id);
|
|
2201
|
-
}
|
|
2202
|
-
resolve();
|
|
2203
|
-
} catch (error) {
|
|
2204
|
-
for (const id of ids) cache.pending.delete(id);
|
|
2205
|
-
reject(error);
|
|
2206
|
-
}
|
|
2207
|
-
}, 0);
|
|
2208
|
-
});
|
|
2209
|
-
cache.pending.set(uid, promise);
|
|
2210
|
-
return promise;
|
|
2211
|
-
}
|
|
2212
|
-
/**
|
|
2213
|
-
* The auth user behind a `userSelect` value, or `null` while it is loading or
|
|
2214
|
-
* when it cannot be resolved.
|
|
2215
|
-
*/
|
|
2216
|
-
function useResolvedUser(uid) {
|
|
2217
|
-
const client = useRebaseClient();
|
|
2218
|
-
const { getAuthToken } = useAuthController();
|
|
2219
|
-
const baseUrl = apiBaseOf(client);
|
|
2220
|
-
const [user, setUser] = useState(() => baseUrl && uid ? getCache(baseUrl).users.get(uid) ?? null : null);
|
|
2221
|
-
useEffect(() => {
|
|
2222
|
-
if (!baseUrl || !uid) {
|
|
2223
|
-
setUser(null);
|
|
2224
|
-
return;
|
|
2225
|
-
}
|
|
2226
|
-
const cache = getCache(baseUrl);
|
|
2227
|
-
if (cache.users.has(uid)) {
|
|
2228
|
-
setUser(cache.users.get(uid) ?? null);
|
|
2229
|
-
return;
|
|
2230
|
-
}
|
|
2231
|
-
let active = true;
|
|
2232
|
-
requestUser(baseUrl, uid, getAuthToken).then(() => {
|
|
2233
|
-
if (active) setUser(getCache(baseUrl).users.get(uid) ?? null);
|
|
2234
|
-
}).catch(() => {
|
|
2235
|
-
if (active) setUser(null);
|
|
2236
|
-
});
|
|
2237
|
-
return () => {
|
|
2238
|
-
active = false;
|
|
2239
|
-
};
|
|
2240
|
-
}, [
|
|
2241
|
-
baseUrl,
|
|
2242
|
-
uid,
|
|
2243
|
-
getAuthToken
|
|
2244
|
-
]);
|
|
2245
|
-
return user;
|
|
2412
|
+
size,
|
|
2413
|
+
color: "secondary"
|
|
2414
|
+
}), !hideLabel && property.name && /* @__PURE__ */ jsx("span", {
|
|
2415
|
+
className: cls("text-text-secondary dark:text-text-secondary-dark", size === "small" ? "text-sm" : ""),
|
|
2416
|
+
children: property.name
|
|
2417
|
+
})]
|
|
2418
|
+
});
|
|
2246
2419
|
}
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2420
|
+
//#endregion
|
|
2421
|
+
//#region src/preview/property_previews/NumberPropertyPreview.tsx
|
|
2422
|
+
/**
|
|
2423
|
+
* @group Preview components
|
|
2424
|
+
*/
|
|
2425
|
+
function NumberPropertyPreview({ value, property, size }) {
|
|
2426
|
+
const numValue = value;
|
|
2427
|
+
if (property.enum) {
|
|
2428
|
+
const enumKey = numValue;
|
|
2429
|
+
const enumValues = enumToObjectEntries(property.enum);
|
|
2430
|
+
if (!enumValues) return /* @__PURE__ */ jsx("span", {
|
|
2431
|
+
className: size === "small" ? "text-sm" : "",
|
|
2432
|
+
children: numValue
|
|
2433
|
+
});
|
|
2434
|
+
return /* @__PURE__ */ jsx(EnumValuesChip, {
|
|
2435
|
+
enumKey,
|
|
2436
|
+
enumValues,
|
|
2437
|
+
size: size !== "medium" ? "small" : "medium"
|
|
2438
|
+
});
|
|
2439
|
+
} else return /* @__PURE__ */ jsx("span", {
|
|
2440
|
+
className: size === "small" ? "text-sm" : "",
|
|
2441
|
+
children: numValue
|
|
2442
|
+
});
|
|
2250
2443
|
}
|
|
2251
2444
|
//#endregion
|
|
2252
2445
|
//#region src/preview/components/UserPreview.tsx
|
|
@@ -2455,7 +2648,8 @@ var PropertyPreview = React.memo(function PropertyPreview(props) {
|
|
|
2455
2648
|
else if (property.type === "boolean") if (typeof value === "boolean") content = /* @__PURE__ */ jsx(BooleanPreview, {
|
|
2456
2649
|
value,
|
|
2457
2650
|
size,
|
|
2458
|
-
property
|
|
2651
|
+
property,
|
|
2652
|
+
hideLabel: props.hideLabel
|
|
2459
2653
|
});
|
|
2460
2654
|
else content = buildWrongValueType(propertyKey, property.type, value);
|
|
2461
2655
|
else if (property.type === "number") if (typeof value === "number") content = /* @__PURE__ */ jsx(NumberPropertyPreview, {
|
|
@@ -6369,13 +6563,6 @@ var CollectionTableBinding = function CollectionTableBinding({ className, style,
|
|
|
6369
6563
|
}), {}) : entity;
|
|
6370
6564
|
const Builder = additionalField.Builder;
|
|
6371
6565
|
if (!Builder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
|
|
6372
|
-
const child = Builder ? /* @__PURE__ */ jsx(Builder, {
|
|
6373
|
-
entity,
|
|
6374
|
-
context
|
|
6375
|
-
}) : /* @__PURE__ */ jsx(Fragment, { children: additionalField.value?.({
|
|
6376
|
-
entity,
|
|
6377
|
-
context
|
|
6378
|
-
})?.toString() });
|
|
6379
6566
|
return /* @__PURE__ */ jsx(EntityTableCell, {
|
|
6380
6567
|
width,
|
|
6381
6568
|
size,
|
|
@@ -6392,7 +6579,14 @@ var CollectionTableBinding = function CollectionTableBinding({ className, style,
|
|
|
6392
6579
|
isDragging,
|
|
6393
6580
|
isDraggable,
|
|
6394
6581
|
frozen,
|
|
6395
|
-
children: /* @__PURE__ */ jsx(ErrorBoundary, { children:
|
|
6582
|
+
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: Builder ? /* @__PURE__ */ jsx(Builder, {
|
|
6583
|
+
entity,
|
|
6584
|
+
context
|
|
6585
|
+
}) : /* @__PURE__ */ jsx(AdditionalFieldValue, {
|
|
6586
|
+
field: additionalField,
|
|
6587
|
+
entity,
|
|
6588
|
+
context
|
|
6589
|
+
}) })
|
|
6396
6590
|
}, `additional_table_cell_${entity.id}_${column.key}`);
|
|
6397
6591
|
}, [size]);
|
|
6398
6592
|
const engine = useCollectionScope()?.engine;
|
|
@@ -7471,7 +7665,7 @@ function convertDataToEntity(authController, navigation, data, idColumn, headers
|
|
|
7471
7665
|
if (idColumn) delete flatObject[idColumn];
|
|
7472
7666
|
const mappedKeysObject = Object.entries(flatObject).map(([key, value]) => {
|
|
7473
7667
|
const mappedKey = getIn(headersMapping, key) ?? key;
|
|
7474
|
-
const mappedProperty = getPropertyInPath(properties, mappedKey);
|
|
7668
|
+
const mappedProperty = getPropertyInPath$1(properties, mappedKey);
|
|
7475
7669
|
if (!mappedProperty) return {};
|
|
7476
7670
|
const processedValue = processValueMapping(authController, value, navigation, mappedProperty);
|
|
7477
7671
|
return { [mappedKey]: processedValue };
|
|
@@ -7592,8 +7786,8 @@ function DataNewPropertiesMapping({ importConfig, destinationProperties, buildPr
|
|
|
7592
7786
|
] }), /* @__PURE__ */ jsx(TableBody, { children: destinationProperties && headingsOrder.map((importKey) => {
|
|
7593
7787
|
const mappedKey = headersMapping[importKey];
|
|
7594
7788
|
const propertyKey = headersMapping[importKey];
|
|
7595
|
-
const property = mappedKey ? getPropertyInPath(destinationProperties, mappedKey) : null;
|
|
7596
|
-
const originProperty = getPropertyInPath(originProperties, importKey);
|
|
7789
|
+
const property = mappedKey ? getPropertyInPath$1(destinationProperties, mappedKey) : null;
|
|
7790
|
+
const originProperty = getPropertyInPath$1(originProperties, importKey);
|
|
7597
7791
|
const origintype = originProperty ? originProperty.type === "array" && typeof originProperty.of === "object" ? `${originProperty.type} - ${originProperty.of.type}` : originProperty.type : void 0;
|
|
7598
7792
|
return /* @__PURE__ */ jsxs(TableRow, {
|
|
7599
7793
|
style: { height: "90px" },
|
|
@@ -7646,7 +7840,7 @@ function DataNewPropertiesMapping({ importConfig, destinationProperties, buildPr
|
|
|
7646
7840
|
children: "Default value"
|
|
7647
7841
|
})
|
|
7648
7842
|
] }), /* @__PURE__ */ jsx(TableBody, { children: destinationProperties && getAllPropertyKeys(destinationProperties).map((key) => {
|
|
7649
|
-
const property = getPropertyInPath(destinationProperties, key);
|
|
7843
|
+
const property = getPropertyInPath$1(destinationProperties, key);
|
|
7650
7844
|
if (typeof property !== "object" || property === null) return null;
|
|
7651
7845
|
if (![
|
|
7652
7846
|
"number",
|
|
@@ -8072,8 +8266,8 @@ function EditorCollectionAction({ path, parentCollectionSlugs, parentEntityIds,
|
|
|
8072
8266
|
}
|
|
8073
8267
|
//#endregion
|
|
8074
8268
|
//#region src/components/CollectionViewBinding/CollectionViewActions.tsx
|
|
8075
|
-
var ImportCollectionAction = lazy(() => import("./import-
|
|
8076
|
-
var ExportCollectionAction = lazy(() => import("./export-
|
|
8269
|
+
var ImportCollectionAction = lazy(() => import("./import-DNrKrFQV.js").then((n) => n.t).then((m) => ({ default: m.ImportCollectionAction })));
|
|
8270
|
+
var ExportCollectionAction = lazy(() => import("./export-l19WevKo.js").then((n) => n.t).then((m) => ({ default: m.ExportCollectionAction })));
|
|
8077
8271
|
function CollectionViewActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, onNewClick, onAddExistingClick, onMultipleDeleteClick, selectionEnabled, path, selectionController, tableController, collectionEntitiesCount, compact, children, openNewDocument }) {
|
|
8078
8272
|
const context = useAdminContext();
|
|
8079
8273
|
const { canCreate, canDelete } = usePermissions();
|
|
@@ -8219,7 +8413,7 @@ function resolveCollectionSlotKeys(collection, authController, propertyConfigs)
|
|
|
8219
8413
|
const titleKeyCandidates = leadingRelationKey ? [leadingRelationKey, ...rankedCandidates.filter((key) => key !== leadingRelationKey)] : rankedCandidates;
|
|
8220
8414
|
const titleKey = titleKeyCandidates[0];
|
|
8221
8415
|
const imageKey = getEntityImagePreviewPropertyKey(collection);
|
|
8222
|
-
let statusKey;
|
|
8416
|
+
let statusKey = getDisplayPropertyKey(collection, "status");
|
|
8223
8417
|
if (!statusKey && collection.previewProperties) for (const key of collection.previewProperties) {
|
|
8224
8418
|
const p = collection.properties[key];
|
|
8225
8419
|
if (p?.type === "string" && "enum" in p && p.enum && key !== titleKey) {
|
|
@@ -8243,17 +8437,20 @@ function resolveCollectionSlotKeys(collection, authController, propertyConfigs)
|
|
|
8243
8437
|
break;
|
|
8244
8438
|
}
|
|
8245
8439
|
}
|
|
8246
|
-
let dateKey;
|
|
8247
|
-
|
|
8440
|
+
let dateKey = getDisplayPropertyKey(collection, "date");
|
|
8441
|
+
const dateCandidates = [
|
|
8248
8442
|
"updated_at",
|
|
8249
8443
|
"updatedAt",
|
|
8250
8444
|
"modified_at",
|
|
8251
8445
|
"modifiedAt",
|
|
8252
8446
|
"created_at",
|
|
8253
8447
|
"createdAt"
|
|
8254
|
-
]
|
|
8255
|
-
|
|
8256
|
-
|
|
8448
|
+
];
|
|
8449
|
+
if (!dateKey) {
|
|
8450
|
+
for (const candidate of dateCandidates) if (collection.properties[candidate]) {
|
|
8451
|
+
dateKey = candidate;
|
|
8452
|
+
break;
|
|
8453
|
+
}
|
|
8257
8454
|
}
|
|
8258
8455
|
if (!dateKey) {
|
|
8259
8456
|
for (const [key, prop] of Object.entries(collection.properties)) if (prop.type === "date") {
|
|
@@ -8284,7 +8481,7 @@ function resolveCollectionSlotKeys(collection, authController, propertyConfigs)
|
|
|
8284
8481
|
titleKey,
|
|
8285
8482
|
titleKeyCandidates,
|
|
8286
8483
|
imageKey,
|
|
8287
|
-
subtitleKey: sortedPreviewKeys.length > 0 ? sortedPreviewKeys[0] : void 0,
|
|
8484
|
+
subtitleKey: getDisplayPropertyKey(collection, "subtitle") ?? (sortedPreviewKeys.length > 0 ? sortedPreviewKeys[0] : void 0),
|
|
8288
8485
|
relationKeys,
|
|
8289
8486
|
statusKey,
|
|
8290
8487
|
dateKey
|
|
@@ -8480,29 +8677,11 @@ function resolveRelationDisplayName(relation, prop) {
|
|
|
8480
8677
|
if (resolved && typeof resolved === "object") targetCollection = resolved;
|
|
8481
8678
|
} catch {}
|
|
8482
8679
|
const extractDisplayName = (values) => {
|
|
8483
|
-
if (targetCollection) {
|
|
8484
|
-
const
|
|
8485
|
-
if (
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
if (typeof p.admin?.disabled === "object" && p.admin.disabled.hidden) return true;
|
|
8489
|
-
return false;
|
|
8490
|
-
};
|
|
8491
|
-
const isDisplayCandidate = (p) => {
|
|
8492
|
-
return p.type === "string" && !p.admin?.multiline && !p.admin?.markdown && !p.storage && !("isId" in p && p.isId) && !isHiddenProp(p);
|
|
8493
|
-
};
|
|
8494
|
-
for (const pk of [
|
|
8495
|
-
"name",
|
|
8496
|
-
"title",
|
|
8497
|
-
"label",
|
|
8498
|
-
"displayName"
|
|
8499
|
-
]) {
|
|
8500
|
-
const p = targetCollection.properties[pk];
|
|
8501
|
-
if (p && isDisplayCandidate(p) && values[pk] !== void 0 && values[pk] !== null) return String(values[pk]);
|
|
8502
|
-
}
|
|
8503
|
-
for (const [k, p] of Object.entries(targetCollection.properties)) if (isDisplayCandidate(p)) {
|
|
8504
|
-
if (values[k] !== void 0 && values[k] !== null) return String(values[k]);
|
|
8505
|
-
}
|
|
8680
|
+
if (targetCollection) for (const key of getTitlePropertyCandidates(targetCollection)) {
|
|
8681
|
+
const value = getValueInPath(values, key);
|
|
8682
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
8683
|
+
if (typeof value === "string" && looksLikeIdentifierValue(value)) continue;
|
|
8684
|
+
return String(value);
|
|
8506
8685
|
}
|
|
8507
8686
|
for (const [, v] of Object.entries(values)) if (typeof v === "string" && v.length > 0 && v.length < 200) return v;
|
|
8508
8687
|
};
|
|
@@ -8523,6 +8702,93 @@ function resolveRelationDisplayName(relation, prop) {
|
|
|
8523
8702
|
} catch {}
|
|
8524
8703
|
return id !== void 0 ? String(id) : void 0;
|
|
8525
8704
|
}
|
|
8705
|
+
/**
|
|
8706
|
+
* The slots for one record, with `admin.display` resolvers applied.
|
|
8707
|
+
*
|
|
8708
|
+
* {@link resolveEntitySlots} answers the question from values already in hand —
|
|
8709
|
+
* which is everything a slot filled by a property needs, and nothing a slot
|
|
8710
|
+
* filled by a *resolver* can use. A computed title may have to be fetched, so it
|
|
8711
|
+
* arrives after the first render; this hook is where the two meet.
|
|
8712
|
+
*
|
|
8713
|
+
* Until a resolver lands, the derived slot shows. That is deliberate: a list of
|
|
8714
|
+
* fifty rows must not blank its titles for a beat while fifty promises settle,
|
|
8715
|
+
* and the derived answer — the best-ranked property carrying a readable value —
|
|
8716
|
+
* is a reasonable thing to be looking at meanwhile. When the resolved value
|
|
8717
|
+
* arrives it replaces the slot outright, property and all, because there is no
|
|
8718
|
+
* property behind a computed value (see {@link PreviewSlot}).
|
|
8719
|
+
*
|
|
8720
|
+
* Every role is hooked unconditionally, so the hook order is fixed regardless of
|
|
8721
|
+
* what a given collection declares.
|
|
8722
|
+
*/
|
|
8723
|
+
function useEntitySlots(entity, collection, slotKeys) {
|
|
8724
|
+
const derived = useMemo(() => resolveEntitySlots(entity, collection, slotKeys), [
|
|
8725
|
+
entity,
|
|
8726
|
+
collection,
|
|
8727
|
+
slotKeys
|
|
8728
|
+
]);
|
|
8729
|
+
const params = {
|
|
8730
|
+
collection,
|
|
8731
|
+
entity
|
|
8732
|
+
};
|
|
8733
|
+
const title = useEntityDisplay("title", params);
|
|
8734
|
+
const subtitle = useEntityDisplay("subtitle", params);
|
|
8735
|
+
const image = useEntityDisplay("image", params);
|
|
8736
|
+
const status = useEntityDisplay("status", params);
|
|
8737
|
+
const date = useEntityDisplay("date", params);
|
|
8738
|
+
return useMemo(() => {
|
|
8739
|
+
const computed = (resolved) => resolved.value !== void 0 && resolved.source === void 0 ? { value: resolved.value } : void 0;
|
|
8740
|
+
const computedDate = computed(date);
|
|
8741
|
+
return {
|
|
8742
|
+
...derived,
|
|
8743
|
+
title: computed(title) ?? derived.title,
|
|
8744
|
+
subtitle: computed(subtitle) ?? derived.subtitle,
|
|
8745
|
+
image: computed(image) ?? derived.image,
|
|
8746
|
+
status: computed(status) ?? derived.status,
|
|
8747
|
+
date: computedDate ? {
|
|
8748
|
+
...computedDate,
|
|
8749
|
+
formatted: formatDateValue$1(computedDate.value)
|
|
8750
|
+
} : derived.date
|
|
8751
|
+
};
|
|
8752
|
+
}, [
|
|
8753
|
+
derived,
|
|
8754
|
+
title,
|
|
8755
|
+
subtitle,
|
|
8756
|
+
image,
|
|
8757
|
+
status,
|
|
8758
|
+
date
|
|
8759
|
+
]);
|
|
8760
|
+
}
|
|
8761
|
+
//#endregion
|
|
8762
|
+
//#region src/components/CollectionViewBinding/SlotValue.tsx
|
|
8763
|
+
/**
|
|
8764
|
+
* One display slot, rendered.
|
|
8765
|
+
*
|
|
8766
|
+
* A slot's value arrives one of two ways and they cannot be rendered the same:
|
|
8767
|
+
*
|
|
8768
|
+
* - **From a property** — render it with that property's own preview, so an
|
|
8769
|
+
* enum status keeps its colour, a date keeps its format and a storage path
|
|
8770
|
+
* becomes a thumbnail. This is why a declared path beats a resolver whenever
|
|
8771
|
+
* the value is actually on the record.
|
|
8772
|
+
* - **From a resolver** — there is no property, because the value was computed
|
|
8773
|
+
* or fetched. All that can be said about it is that it is text.
|
|
8774
|
+
*
|
|
8775
|
+
* The branch lives here, once, rather than at each of the fourteen places the
|
|
8776
|
+
* list row, the card and the board card render a slot. That count is the reason:
|
|
8777
|
+
* the last time a rule like this was inlined per call site, it drifted into
|
|
8778
|
+
* seven versions.
|
|
8779
|
+
*/
|
|
8780
|
+
function SlotValue({ slot, size, textOnly, fill }) {
|
|
8781
|
+
if (!slot || slot.value === void 0 || slot.value === null) return null;
|
|
8782
|
+
if (!slot.property || !slot.propertyKey) return /* @__PURE__ */ jsx(Fragment, { children: Array.isArray(slot.value) ? slot.value.map((item) => String(item)).join(", ") : String(slot.value) });
|
|
8783
|
+
return /* @__PURE__ */ jsx(PropertyPreview, {
|
|
8784
|
+
propertyKey: slot.propertyKey,
|
|
8785
|
+
value: slot.value,
|
|
8786
|
+
property: slot.property,
|
|
8787
|
+
size,
|
|
8788
|
+
textOnly,
|
|
8789
|
+
fill
|
|
8790
|
+
});
|
|
8791
|
+
}
|
|
8526
8792
|
//#endregion
|
|
8527
8793
|
//#region src/components/CollectionViewBinding/EntityCardBinding.tsx
|
|
8528
8794
|
/**
|
|
@@ -8532,12 +8798,7 @@ function resolveRelationDisplayName(relation, prop) {
|
|
|
8532
8798
|
function EntityCardBinding({ entity, collection, onClick, selected, highlighted, onSelectionChange, selectionEnabled, size = "m" }) {
|
|
8533
8799
|
const authController = useAuthController();
|
|
8534
8800
|
const analyticsController = useAnalyticsController();
|
|
8535
|
-
const
|
|
8536
|
-
const slots = useMemo(() => resolveEntitySlots(entity, collection, slotKeys), [
|
|
8537
|
-
entity,
|
|
8538
|
-
collection,
|
|
8539
|
-
slotKeys
|
|
8540
|
-
]);
|
|
8801
|
+
const slots = useEntitySlots(entity, collection, useCollectionSlotKeys(collection, authController, useCustomizationController().propertyConfigs));
|
|
8541
8802
|
const handleClick = (e) => {
|
|
8542
8803
|
if (e && (e.metaKey || e.ctrlKey) && selectionEnabled) {
|
|
8543
8804
|
e.preventDefault();
|
|
@@ -8566,11 +8827,9 @@ function EntityCardBinding({ entity, collection, onClick, selected, highlighted,
|
|
|
8566
8827
|
children: [
|
|
8567
8828
|
slots.image ? /* @__PURE__ */ jsx("div", {
|
|
8568
8829
|
className: "w-full h-full",
|
|
8569
|
-
children: /* @__PURE__ */ jsx(
|
|
8570
|
-
|
|
8571
|
-
propertyKey: slots.image.propertyKey,
|
|
8830
|
+
children: /* @__PURE__ */ jsx(SlotValue, {
|
|
8831
|
+
slot: slots.image,
|
|
8572
8832
|
size: "medium",
|
|
8573
|
-
value: slots.image.value,
|
|
8574
8833
|
fill: true
|
|
8575
8834
|
})
|
|
8576
8835
|
}) : /* @__PURE__ */ jsx("div", {
|
|
@@ -8602,10 +8861,8 @@ function EntityCardBinding({ entity, collection, onClick, selected, highlighted,
|
|
|
8602
8861
|
}),
|
|
8603
8862
|
/* @__PURE__ */ jsx("div", {
|
|
8604
8863
|
className: "truncate my-1 text-sm font-medium min-h-[20px]",
|
|
8605
|
-
children: slots.title ? /* @__PURE__ */ jsx(
|
|
8606
|
-
|
|
8607
|
-
value: slots.title.value,
|
|
8608
|
-
property: slots.title.property,
|
|
8864
|
+
children: slots.title ? /* @__PURE__ */ jsx(SlotValue, {
|
|
8865
|
+
slot: slots.title,
|
|
8609
8866
|
size: "small"
|
|
8610
8867
|
}) : /* @__PURE__ */ jsx(Typography, {
|
|
8611
8868
|
variant: "body2",
|
|
@@ -8615,10 +8872,8 @@ function EntityCardBinding({ entity, collection, onClick, selected, highlighted,
|
|
|
8615
8872
|
}),
|
|
8616
8873
|
slots.subtitle && /* @__PURE__ */ jsx("div", {
|
|
8617
8874
|
className: "line-clamp-3 [&_div]:line-clamp-3 text-xs text-surface-600 dark:text-surface-400 [&_p]:!my-1 [&_p:first-child]:!mt-0 [&_p:last-child]:!mb-0",
|
|
8618
|
-
children: /* @__PURE__ */ jsx(
|
|
8619
|
-
|
|
8620
|
-
value: slots.subtitle.value,
|
|
8621
|
-
property: slots.subtitle.property,
|
|
8875
|
+
children: /* @__PURE__ */ jsx(SlotValue, {
|
|
8876
|
+
slot: slots.subtitle,
|
|
8622
8877
|
size: "small"
|
|
8623
8878
|
})
|
|
8624
8879
|
}),
|
|
@@ -8636,10 +8891,8 @@ function EntityCardBinding({ entity, collection, onClick, selected, highlighted,
|
|
|
8636
8891
|
}),
|
|
8637
8892
|
slots.status && /* @__PURE__ */ jsx("div", {
|
|
8638
8893
|
className: "mt-1",
|
|
8639
|
-
children: /* @__PURE__ */ jsx(
|
|
8640
|
-
|
|
8641
|
-
value: slots.status.value,
|
|
8642
|
-
property: slots.status.property,
|
|
8894
|
+
children: /* @__PURE__ */ jsx(SlotValue, {
|
|
8895
|
+
slot: slots.status,
|
|
8643
8896
|
size: "small"
|
|
8644
8897
|
})
|
|
8645
8898
|
})
|
|
@@ -9077,7 +9330,7 @@ function CollectionListViewBinding({ collection, tableController, onEntityClick,
|
|
|
9077
9330
|
* the column system for developer-defined table layouts.
|
|
9078
9331
|
*/
|
|
9079
9332
|
var ListRow = React.memo(function ListRow({ entity, collection, onClick, selected, highlighted, onSelectionChange, selectionEnabled, columns, slotKeys, rowClasses, showImage, size, isLast, isActive = false, listViewActions = [], context, path, selectionController, openEntityMode }) {
|
|
9080
|
-
const slots =
|
|
9333
|
+
const slots = useEntitySlots(entity, collection, slotKeys);
|
|
9081
9334
|
const handleClick = useCallback((e) => {
|
|
9082
9335
|
if ((e.metaKey || e.ctrlKey) && selectionEnabled) {
|
|
9083
9336
|
e.preventDefault();
|
|
@@ -9118,10 +9371,8 @@ var ListRow = React.memo(function ListRow({ entity, collection, onClick, selecte
|
|
|
9118
9371
|
className: "flex-shrink-0",
|
|
9119
9372
|
children: slots.image ? /* @__PURE__ */ jsx("div", {
|
|
9120
9373
|
className: cls("w-10 h-10 rounded-lg border relative overflow-hidden bg-surface-100 dark:bg-surface-900", defaultBorderMixin),
|
|
9121
|
-
children: /* @__PURE__ */ jsx(
|
|
9122
|
-
|
|
9123
|
-
value: slots.image.value,
|
|
9124
|
-
property: slots.image.property,
|
|
9374
|
+
children: /* @__PURE__ */ jsx(SlotValue, {
|
|
9375
|
+
slot: slots.image,
|
|
9125
9376
|
size: "small",
|
|
9126
9377
|
fill: true
|
|
9127
9378
|
})
|
|
@@ -9143,10 +9394,8 @@ var ListRow = React.memo(function ListRow({ entity, collection, onClick, selecte
|
|
|
9143
9394
|
component: "div",
|
|
9144
9395
|
variant: "body2",
|
|
9145
9396
|
className: "font-semibold text-surface-900 dark:text-surface-50 truncate transition-colors group-hover:text-primary-600 dark:group-hover:text-primary-400",
|
|
9146
|
-
children: /* @__PURE__ */ jsx(
|
|
9147
|
-
|
|
9148
|
-
value: slots.title.value,
|
|
9149
|
-
property: slots.title.property,
|
|
9397
|
+
children: /* @__PURE__ */ jsx(SlotValue, {
|
|
9398
|
+
slot: slots.title,
|
|
9150
9399
|
size: "small",
|
|
9151
9400
|
textOnly: true
|
|
9152
9401
|
})
|
|
@@ -9163,10 +9412,8 @@ var ListRow = React.memo(function ListRow({ entity, collection, onClick, selecte
|
|
|
9163
9412
|
variant: "caption",
|
|
9164
9413
|
component: "div",
|
|
9165
9414
|
className: "text-surface-500 dark:text-surface-400 truncate",
|
|
9166
|
-
children: /* @__PURE__ */ jsx(
|
|
9167
|
-
|
|
9168
|
-
value: slots.subtitle.value,
|
|
9169
|
-
property: slots.subtitle.property,
|
|
9415
|
+
children: /* @__PURE__ */ jsx(SlotValue, {
|
|
9416
|
+
slot: slots.subtitle,
|
|
9170
9417
|
size: "small",
|
|
9171
9418
|
textOnly: true
|
|
9172
9419
|
})
|
|
@@ -9237,10 +9484,8 @@ var ListRow = React.memo(function ListRow({ entity, collection, onClick, selecte
|
|
|
9237
9484
|
className: "flex items-center gap-4 flex-shrink-0 ml-auto",
|
|
9238
9485
|
children: [slots.status && /* @__PURE__ */ jsx("div", {
|
|
9239
9486
|
className: "flex-shrink-0 @max-[400px]:hidden",
|
|
9240
|
-
children: /* @__PURE__ */ jsx(
|
|
9241
|
-
|
|
9242
|
-
value: slots.status.value,
|
|
9243
|
-
property: slots.status.property,
|
|
9487
|
+
children: /* @__PURE__ */ jsx(SlotValue, {
|
|
9488
|
+
slot: slots.status,
|
|
9244
9489
|
size: "small"
|
|
9245
9490
|
})
|
|
9246
9491
|
}), slots.date && /* @__PURE__ */ jsx("div", {
|
|
@@ -9273,260 +9518,174 @@ var ListRow = React.memo(function ListRow({ entity, collection, onClick, selecte
|
|
|
9273
9518
|
openEntityMode: openEntityMode ?? collection?.openEntityMode ?? "full_screen"
|
|
9274
9519
|
});
|
|
9275
9520
|
},
|
|
9276
|
-
children: getIcon(action.icon, void 0, void 0, "smallest")
|
|
9277
|
-
})
|
|
9278
|
-
}, action.key ?? index))
|
|
9279
|
-
})
|
|
9280
|
-
]
|
|
9281
|
-
});
|
|
9282
|
-
});
|
|
9283
|
-
//#endregion
|
|
9284
|
-
//#region src/components/PropertyCollectionView.tsx
|
|
9285
|
-
/**
|
|
9286
|
-
* Build a readable label for a path and resolve the property
|
|
9287
|
-
* Supports map and array (including arrays of maps)
|
|
9288
|
-
*/
|
|
9289
|
-
function buildPropertyLabelAndGetProperty(properties, key) {
|
|
9290
|
-
if (!key) return {
|
|
9291
|
-
label: "",
|
|
9292
|
-
property: void 0
|
|
9293
|
-
};
|
|
9294
|
-
const segments = [];
|
|
9295
|
-
const re = /([^[.\]]+)|\[(\d+)\]/g;
|
|
9296
|
-
let m;
|
|
9297
|
-
while ((m = re.exec(key)) !== null) if (m[1] !== void 0) segments.push(m[1]);
|
|
9298
|
-
else if (m[2] !== void 0) segments.push(Number(m[2]));
|
|
9299
|
-
let currentProps = properties;
|
|
9300
|
-
let currentProp;
|
|
9301
|
-
let lastLabel = "";
|
|
9302
|
-
const getArrayOfProp = (p) => {
|
|
9303
|
-
if (!p || p.type !== "array") return void 0;
|
|
9304
|
-
return Array.isArray(p.of) ? p.of[0] : p.of;
|
|
9305
|
-
};
|
|
9306
|
-
for (const seg of segments) {
|
|
9307
|
-
if (typeof seg === "number") {
|
|
9308
|
-
lastLabel = `[${seg}]`;
|
|
9309
|
-
if (currentProp?.type === "array") {
|
|
9310
|
-
currentProp = getArrayOfProp(currentProp);
|
|
9311
|
-
if (currentProp?.type === "map" && currentProp.properties) currentProps = currentProp.properties;
|
|
9312
|
-
else currentProps = void 0;
|
|
9313
|
-
} else {
|
|
9314
|
-
currentProp = void 0;
|
|
9315
|
-
currentProps = void 0;
|
|
9316
|
-
}
|
|
9317
|
-
continue;
|
|
9318
|
-
}
|
|
9319
|
-
if (currentProps && currentProps[seg]) {
|
|
9320
|
-
const nextProp = currentProps[seg];
|
|
9321
|
-
currentProp = nextProp;
|
|
9322
|
-
lastLabel = nextProp.name || String(seg);
|
|
9323
|
-
if (nextProp.type === "map" && nextProp.properties) currentProps = nextProp.properties;
|
|
9324
|
-
else if (nextProp.type === "array") currentProps = void 0;
|
|
9325
|
-
else currentProps = void 0;
|
|
9326
|
-
} else {
|
|
9327
|
-
currentProp = void 0;
|
|
9328
|
-
currentProps = void 0;
|
|
9329
|
-
lastLabel = String(seg);
|
|
9330
|
-
}
|
|
9331
|
-
}
|
|
9332
|
-
return {
|
|
9333
|
-
label: lastLabel,
|
|
9334
|
-
property: currentProp
|
|
9335
|
-
};
|
|
9336
|
-
}
|
|
9337
|
-
var pathEndsWithIndex = (p) => /\[\d+\]$/.test(p);
|
|
9338
|
-
/**
|
|
9339
|
-
* Improved simple layout for nested changes:
|
|
9340
|
-
* - Map or Array-of-Map -> section header + indented rows
|
|
9341
|
-
* - Leaf or Array-of-Primitives -> single row with label and value
|
|
9342
|
-
*/
|
|
9343
|
-
var PropertyCollectionView = ({ data, properties, baseKey = "", suppressHeader = false, size = "small" }) => {
|
|
9344
|
-
const isTopLevel = !!baseKey && !baseKey.includes(".") && !baseKey.includes("[");
|
|
9345
|
-
if (Array.isArray(data)) {
|
|
9346
|
-
const { label: arrayLabel, property } = baseKey ? buildPropertyLabelAndGetProperty(properties, baseKey) : {
|
|
9347
|
-
label: "",
|
|
9348
|
-
property: void 0
|
|
9349
|
-
};
|
|
9350
|
-
const ofProp = property?.type === "array" ? Array.isArray(property.of) ? property.of[0] : property.of : void 0;
|
|
9351
|
-
const isArrayOfMaps = ofProp?.type === "map";
|
|
9352
|
-
const isArrayOfPrimitives = property?.type === "array" && ofProp && ofProp.type !== "map";
|
|
9353
|
-
const isRelationArray = property?.type === "relation";
|
|
9354
|
-
if (baseKey && property && (isArrayOfPrimitives || isRelationArray)) return /* @__PURE__ */ jsxs("div", {
|
|
9355
|
-
className: `grid grid-cols-12 gap-x-4 ${isTopLevel ? "py-4" : "py-2"} items-start ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`,
|
|
9356
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
9357
|
-
className: "col-span-4 pr-2",
|
|
9358
|
-
children: /* @__PURE__ */ jsx(Typography, {
|
|
9359
|
-
variant: "caption",
|
|
9360
|
-
color: "secondary",
|
|
9361
|
-
component: "span",
|
|
9362
|
-
className: "break-words",
|
|
9363
|
-
children: arrayLabel
|
|
9364
|
-
})
|
|
9365
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
9366
|
-
className: "col-span-8",
|
|
9367
|
-
children: /* @__PURE__ */ jsx(PropertyPreview, {
|
|
9368
|
-
propertyKey: baseKey,
|
|
9369
|
-
value: data,
|
|
9370
|
-
property,
|
|
9371
|
-
size
|
|
9372
|
-
})
|
|
9373
|
-
})]
|
|
9374
|
-
});
|
|
9375
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
9376
|
-
className: `${isTopLevel ? "py-4" : "py-1"} ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`,
|
|
9377
|
-
children: [baseKey && arrayLabel && !suppressHeader && /* @__PURE__ */ jsx(Typography, {
|
|
9378
|
-
variant: "caption",
|
|
9379
|
-
color: "secondary",
|
|
9380
|
-
component: "span",
|
|
9381
|
-
children: arrayLabel
|
|
9382
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
9383
|
-
className: baseKey ? `pl-4 mt-1 border-l ${defaultBorderMixin}` : "",
|
|
9384
|
-
children: data.map((item, index) => {
|
|
9385
|
-
if (item === null || item === void 0) return null;
|
|
9386
|
-
const currentKey = baseKey ? `${baseKey}[${index}]` : `[${index}]`;
|
|
9387
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
9388
|
-
className: "py-1",
|
|
9389
|
-
children: [/* @__PURE__ */ jsx(Typography, {
|
|
9390
|
-
variant: "caption",
|
|
9391
|
-
color: "secondary",
|
|
9392
|
-
component: "span",
|
|
9393
|
-
children: isArrayOfMaps && ofProp?.name ? `${ofProp.name} [${index}]` : `[${index}]`
|
|
9394
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
9395
|
-
className: `pl-4 mt-1 border-l ${defaultBorderMixin}`,
|
|
9396
|
-
children: /* @__PURE__ */ jsx(PropertyCollectionView, {
|
|
9397
|
-
data: item,
|
|
9398
|
-
properties,
|
|
9399
|
-
baseKey: currentKey,
|
|
9400
|
-
suppressHeader: true,
|
|
9401
|
-
size
|
|
9402
|
-
})
|
|
9403
|
-
})]
|
|
9404
|
-
}, currentKey);
|
|
9405
|
-
})
|
|
9406
|
-
})]
|
|
9407
|
-
});
|
|
9408
|
-
}
|
|
9409
|
-
if (typeof data === "object" && data !== null) {
|
|
9410
|
-
const { label, property } = baseKey ? buildPropertyLabelAndGetProperty(properties, baseKey) : {
|
|
9411
|
-
label: "",
|
|
9412
|
-
property: void 0
|
|
9413
|
-
};
|
|
9414
|
-
if (baseKey && (!property || property.type !== "map" || !property.properties)) {
|
|
9415
|
-
if (!property) return null;
|
|
9416
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
9417
|
-
className: `grid grid-cols-12 gap-x-4 ${isTopLevel ? "py-4" : "py-2"} items-start ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`,
|
|
9418
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
9419
|
-
className: "col-span-4 pr-2",
|
|
9420
|
-
children: /* @__PURE__ */ jsx(Typography, {
|
|
9421
|
-
variant: "caption",
|
|
9422
|
-
color: "secondary",
|
|
9423
|
-
component: "span",
|
|
9424
|
-
className: "break-words",
|
|
9425
|
-
children: label
|
|
9426
|
-
})
|
|
9427
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
9428
|
-
className: "col-span-8",
|
|
9429
|
-
children: /* @__PURE__ */ jsx(PropertyPreview, {
|
|
9430
|
-
propertyKey: baseKey,
|
|
9431
|
-
value: data,
|
|
9432
|
-
property,
|
|
9433
|
-
size
|
|
9521
|
+
children: getIcon(action.icon, void 0, void 0, "smallest")
|
|
9434
9522
|
})
|
|
9435
|
-
})
|
|
9436
|
-
})
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
9441
|
-
className: `${isTopLevel ? "py-4" : "py-1"} ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`,
|
|
9442
|
-
children: [showMapHeader && /* @__PURE__ */ jsx(Typography, {
|
|
9443
|
-
variant: "caption",
|
|
9444
|
-
color: "secondary",
|
|
9445
|
-
component: "span",
|
|
9446
|
-
children: headerText
|
|
9447
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
9448
|
-
className: baseKey ? `pl-4 mt-1 border-l ${defaultBorderMixin}` : "",
|
|
9449
|
-
children: Object.entries(data).map(([key, value]) => {
|
|
9450
|
-
if (value === null || value === void 0) return null;
|
|
9451
|
-
if (baseKey === "" && !properties[key]) return null;
|
|
9452
|
-
const currentKey = baseKey ? `${baseKey}.${key}` : key;
|
|
9453
|
-
return /* @__PURE__ */ jsx(PropertyCollectionView, {
|
|
9454
|
-
data: value,
|
|
9455
|
-
properties,
|
|
9456
|
-
baseKey: currentKey,
|
|
9457
|
-
size
|
|
9458
|
-
}, currentKey);
|
|
9459
|
-
})
|
|
9460
|
-
})]
|
|
9461
|
-
});
|
|
9462
|
-
}
|
|
9463
|
-
if (baseKey) {
|
|
9464
|
-
const { label, property } = buildPropertyLabelAndGetProperty(properties, baseKey);
|
|
9465
|
-
if (!property) return null;
|
|
9466
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
9467
|
-
className: `grid grid-cols-12 gap-x-4 ${isTopLevel ? "py-4" : "py-2"} items-start ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`,
|
|
9468
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
9469
|
-
className: "col-span-4 pr-2",
|
|
9470
|
-
children: /* @__PURE__ */ jsx(Typography, {
|
|
9471
|
-
variant: "caption",
|
|
9472
|
-
color: "secondary",
|
|
9473
|
-
component: "span",
|
|
9474
|
-
className: "break-words",
|
|
9475
|
-
children: label
|
|
9476
|
-
})
|
|
9477
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
9478
|
-
className: "col-span-8",
|
|
9479
|
-
children: /* @__PURE__ */ jsx(PropertyPreview, {
|
|
9480
|
-
propertyKey: baseKey,
|
|
9481
|
-
value: data,
|
|
9482
|
-
property,
|
|
9483
|
-
size
|
|
9484
|
-
})
|
|
9485
|
-
})]
|
|
9486
|
-
});
|
|
9487
|
-
}
|
|
9488
|
-
return null;
|
|
9489
|
-
};
|
|
9523
|
+
}, action.key ?? index))
|
|
9524
|
+
})
|
|
9525
|
+
]
|
|
9526
|
+
});
|
|
9527
|
+
});
|
|
9490
9528
|
//#endregion
|
|
9491
9529
|
//#region src/components/EntityViewBinding.tsx
|
|
9530
|
+
/** Nothing can be in error in a view that cannot be submitted. */
|
|
9531
|
+
var NO_ERRORS = /* @__PURE__ */ new Set();
|
|
9532
|
+
/**
|
|
9533
|
+
* The fields the read-only view lays out, in render order.
|
|
9534
|
+
*
|
|
9535
|
+
* The same set the form uses, minus the `additionalFields` when there is no
|
|
9536
|
+
* form context to hand them: those entries are arbitrary components, not
|
|
9537
|
+
* values, and one rendered against no context is nothing at all. They are
|
|
9538
|
+
* dropped *before* the layout resolves rather than skipped while rendering,
|
|
9539
|
+
* because the resolver has by then given each of them a full row — skipping
|
|
9540
|
+
* later leaves that row as a hole in the grid.
|
|
9541
|
+
*/
|
|
9542
|
+
function readOnlyFieldKeys(collection, hasFormContext) {
|
|
9543
|
+
const keys = getFormFieldKeys(collection);
|
|
9544
|
+
if (hasFormContext) return keys;
|
|
9545
|
+
const additional = new Set((collection.additionalFields ?? []).map((field) => field.key));
|
|
9546
|
+
return keys.filter((key) => !additional.has(key));
|
|
9547
|
+
}
|
|
9492
9548
|
/**
|
|
9493
9549
|
* The read-only rendering of a record's values.
|
|
9494
9550
|
*
|
|
9495
|
-
* This
|
|
9496
|
-
*
|
|
9497
|
-
*
|
|
9498
|
-
*
|
|
9499
|
-
*
|
|
9551
|
+
* This is the same layout as {@link EntityForm}: the sections, the grid spans,
|
|
9552
|
+
* the labels and the rail all come from the one resolver, so the record does
|
|
9553
|
+
* not rearrange itself when you press Edit. Only the controls differ — each
|
|
9554
|
+
* field renders its {@link PropertyPreview} instead of an input.
|
|
9555
|
+
*
|
|
9556
|
+
* It used to be a flat two-column table of every value in the record: no
|
|
9557
|
+
* grouping, no widths, a label column sized for short names and a value column
|
|
9558
|
+
* that wrapped emails over two lines while the pane beside it stayed empty. A
|
|
9559
|
+
* boolean and a markdown body were given the same room.
|
|
9560
|
+
*
|
|
9561
|
+
* The synthetic `Id` row it used to prepend is gone with the same reasoning the
|
|
9562
|
+
* form applies: between that row, the collection's own id property and the
|
|
9563
|
+
* `path/id` chip above them, one UUID appeared three times. The id is a
|
|
9564
|
+
* copyable chip in the identity bar, and the rail's record block, once each.
|
|
9500
9565
|
*/
|
|
9501
|
-
function EntityViewBinding({ entity, collection,
|
|
9502
|
-
const
|
|
9503
|
-
const
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9566
|
+
function EntityViewBinding({ entity, collection, className, asPage = false, openEntityMode = "full_screen", formContext }) {
|
|
9567
|
+
const externalLink = useCustomizationController()?.entityLinkBuilder?.({ entity });
|
|
9568
|
+
const hasFormContext = Boolean(formContext);
|
|
9569
|
+
const fieldKeys = useMemo(() => readOnlyFieldKeys(collection, hasFormContext), [collection, hasFormContext]);
|
|
9570
|
+
const layout = useMemo(() => resolveFormLayout({
|
|
9571
|
+
collection,
|
|
9572
|
+
fieldKeys,
|
|
9573
|
+
status: "existing"
|
|
9574
|
+
}), [collection, fieldKeys]);
|
|
9575
|
+
const containerRef = useRef(null);
|
|
9576
|
+
const railVisible = useRailVisible(containerRef);
|
|
9577
|
+
const showRail = asPage && layout.hasRail && railVisible;
|
|
9578
|
+
const [collapsedSections, setCollapsedSections] = useState(() => new Set(layout.sections.filter((section) => section.collapsed).map((section) => section.key)));
|
|
9579
|
+
const toggleSection = useCallback((key) => {
|
|
9580
|
+
setCollapsedSections((prev) => {
|
|
9581
|
+
const next = new Set(prev);
|
|
9582
|
+
if (next.has(key)) next.delete(key);
|
|
9583
|
+
else next.add(key);
|
|
9584
|
+
return next;
|
|
9585
|
+
});
|
|
9586
|
+
}, []);
|
|
9587
|
+
const renderField = useCallback((field) => {
|
|
9588
|
+
if (field.additional) {
|
|
9589
|
+
const additionalField = collection.additionalFields?.find((f) => f.key === field.key);
|
|
9590
|
+
if (!additionalField || !formContext) return null;
|
|
9591
|
+
const AdditionalFieldBuilder = additionalField.Builder;
|
|
9592
|
+
const additionalFieldContext = formContext;
|
|
9593
|
+
const child = AdditionalFieldBuilder ? /* @__PURE__ */ jsx(AdditionalFieldBuilder, {
|
|
9594
|
+
entity,
|
|
9595
|
+
context: additionalFieldContext
|
|
9596
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
9597
|
+
className: "w-full",
|
|
9598
|
+
children: /* @__PURE__ */ jsx(AdditionalFieldValue, {
|
|
9599
|
+
field: additionalField,
|
|
9600
|
+
entity,
|
|
9601
|
+
context: additionalFieldContext
|
|
9602
|
+
})
|
|
9603
|
+
});
|
|
9604
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
9605
|
+
className: spanClass(field.span),
|
|
9606
|
+
children: [/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
9607
|
+
propertyKey: field.key,
|
|
9608
|
+
icon: /* @__PURE__ */ jsx(AlignLeftIcon, { size: iconSize.small }),
|
|
9609
|
+
title: additionalField.name,
|
|
9610
|
+
className: "text-text-secondary dark:text-text-secondary-dark"
|
|
9611
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
9612
|
+
className: cls(paperMixin, "w-full min-h-14 p-4 md:p-6 overflow-x-auto no-scrollbar"),
|
|
9613
|
+
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: child })
|
|
9614
|
+
})]
|
|
9615
|
+
}, `additional_${field.key}`);
|
|
9616
|
+
}
|
|
9617
|
+
const property = collection.properties?.[field.key];
|
|
9618
|
+
if (!property) return null;
|
|
9619
|
+
return /* @__PURE__ */ jsx("div", {
|
|
9620
|
+
className: spanClass(field.span),
|
|
9621
|
+
children: /* @__PURE__ */ jsx(FieldBlock, {
|
|
9622
|
+
propertyKey: field.key,
|
|
9623
|
+
property,
|
|
9624
|
+
showLabel: true,
|
|
9625
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
9626
|
+
className: "min-h-8 flex flex-col justify-center min-w-0 text-text-primary dark:text-text-primary-dark",
|
|
9627
|
+
children: /* @__PURE__ */ jsx(PropertyPreview, {
|
|
9628
|
+
propertyKey: field.key,
|
|
9629
|
+
value: getValueInPath(entity.values, field.key),
|
|
9630
|
+
property,
|
|
9631
|
+
hideLabel: true,
|
|
9632
|
+
size: "medium"
|
|
9523
9633
|
})
|
|
9524
9634
|
})
|
|
9525
9635
|
})
|
|
9526
|
-
}
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9636
|
+
}, `field_${field.key}`);
|
|
9637
|
+
}, [
|
|
9638
|
+
collection,
|
|
9639
|
+
entity,
|
|
9640
|
+
formContext
|
|
9641
|
+
]);
|
|
9642
|
+
const sections = showRail || !layout.sidebar.length ? layout.sections : [...layout.sections, {
|
|
9643
|
+
key: "__rail",
|
|
9644
|
+
title: "Settings",
|
|
9645
|
+
collapsible: false,
|
|
9646
|
+
collapsed: false,
|
|
9647
|
+
fields: layout.sidebar
|
|
9648
|
+
}];
|
|
9649
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [externalLink && /* @__PURE__ */ jsx("div", {
|
|
9650
|
+
className: "flex justify-end mb-2",
|
|
9651
|
+
children: /* @__PURE__ */ jsx(Tooltip, {
|
|
9652
|
+
title: "Open in the live site",
|
|
9653
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
9654
|
+
href: externalLink,
|
|
9655
|
+
rel: "noopener noreferrer",
|
|
9656
|
+
target: "_blank",
|
|
9657
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9658
|
+
size: "small",
|
|
9659
|
+
children: /* @__PURE__ */ jsx(ExternalLinkIcon, {})
|
|
9660
|
+
})
|
|
9661
|
+
})
|
|
9662
|
+
})
|
|
9663
|
+
}), /* @__PURE__ */ jsx(FormSections, {
|
|
9664
|
+
sections,
|
|
9665
|
+
collapsed: collapsedSections,
|
|
9666
|
+
onToggle: toggleSection,
|
|
9667
|
+
errorKeys: NO_ERRORS,
|
|
9668
|
+
renderField
|
|
9669
|
+
})] });
|
|
9670
|
+
if (!asPage) return /* @__PURE__ */ jsx("div", {
|
|
9671
|
+
ref: containerRef,
|
|
9672
|
+
className: cls("@container/col w-full min-w-0", className),
|
|
9673
|
+
children: content
|
|
9674
|
+
});
|
|
9675
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
9676
|
+
ref: containerRef,
|
|
9677
|
+
className: cls("flex-1 flex flex-row w-full min-h-0", className),
|
|
9678
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
9679
|
+
className: "flex-1 min-w-0 overflow-y-auto flex justify-center items-start",
|
|
9680
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
9681
|
+
className: cls("@container/col w-full max-w-3xl 2xl:max-w-4xl flex flex-col", openEntityMode === "dialog" ? "pt-5 pb-8 px-6 sm:px-8" : "pt-6 pb-12 px-5 sm:px-8"),
|
|
9682
|
+
children: content
|
|
9683
|
+
})
|
|
9684
|
+
}), showRail && /* @__PURE__ */ jsx(FormRail, {
|
|
9685
|
+
fields: layout.sidebar,
|
|
9686
|
+
showRecordMeta: layout.showRecordMeta,
|
|
9687
|
+
entity,
|
|
9688
|
+
renderField
|
|
9530
9689
|
})]
|
|
9531
9690
|
});
|
|
9532
9691
|
}
|
|
@@ -9542,7 +9701,7 @@ function EntityViewBinding({ entity, collection, path, className }) {
|
|
|
9542
9701
|
* editing. They live here instead, and the left half of a bar that was
|
|
9543
9702
|
* previously empty now does the work.
|
|
9544
9703
|
*/
|
|
9545
|
-
function EntityIdentityBar({ collection, title, entityId, status, dirty, saving, onSave, onDiscard, saveDisabled, hasErrors, onSaveAndClose, onBack, onInspect, onViewHistory, recordActions, pluginActions, trailing, leading }) {
|
|
9704
|
+
function EntityIdentityBar({ collection, collectionUrl, title, entityId, status, dirty, saving, onSave, onDiscard, saveDisabled, hasErrors, onSaveAndClose, onBack, onInspect, onViewHistory, recordActions, pluginActions, trailing, leading }) {
|
|
9546
9705
|
const { t } = useTranslation();
|
|
9547
9706
|
const saveLabel = status === "existing" ? t("save") : status === "copy" ? t("create_copy") : t("create");
|
|
9548
9707
|
const closeLabel = status === "existing" ? t("save_and_close") : status === "copy" ? t("create_copy_and_close") : t("create_and_close");
|
|
@@ -9560,7 +9719,15 @@ function EntityIdentityBar({ collection, title, entityId, status, dirty, saving,
|
|
|
9560
9719
|
children: /* @__PURE__ */ jsx(ArrowLeftIcon, { size: iconSize.smallest })
|
|
9561
9720
|
})
|
|
9562
9721
|
}),
|
|
9563
|
-
/* @__PURE__ */
|
|
9722
|
+
collectionUrl ? /* @__PURE__ */ jsx(Link, {
|
|
9723
|
+
to: collectionUrl,
|
|
9724
|
+
className: "visited:text-inherit dark:visited:text-inherit hidden sm:block",
|
|
9725
|
+
children: /* @__PURE__ */ jsxs(Typography, {
|
|
9726
|
+
variant: "caption",
|
|
9727
|
+
className: cls("text-text-disabled dark:text-text-disabled-dark whitespace-nowrap", "hover:text-text-primary dark:hover:text-text-primary-dark transition-colors"),
|
|
9728
|
+
children: [collection.name, "\xA0/"]
|
|
9729
|
+
})
|
|
9730
|
+
}) : /* @__PURE__ */ jsxs(Typography, {
|
|
9564
9731
|
variant: "caption",
|
|
9565
9732
|
className: "text-text-disabled dark:text-text-disabled-dark whitespace-nowrap hidden sm:block",
|
|
9566
9733
|
children: [collection.name, "\xA0/"]
|
|
@@ -9696,9 +9863,11 @@ function SplitViewProvider({ value, children }) {
|
|
|
9696
9863
|
* Closes the list of a split view, leaving the open record on its own.
|
|
9697
9864
|
*
|
|
9698
9865
|
* It leads the record's own app bar, where the breadcrumb starts — this is the
|
|
9699
|
-
* entity pane's old "Open full screen" button, moved to that edge and wearing
|
|
9700
|
-
*
|
|
9701
|
-
*
|
|
9866
|
+
* entity pane's old "Open full screen" button, moved to that edge and wearing a
|
|
9867
|
+
* double chevron pointing at the list it folds away: closing the list and
|
|
9868
|
+
* opening the record full screen are the same thing, so there is one control
|
|
9869
|
+
* and one route (`#full`) rather than two. Full screen carries the mirrored
|
|
9870
|
+
* chevron to bring the list back.
|
|
9702
9871
|
*/
|
|
9703
9872
|
function SplitListCloseButton() {
|
|
9704
9873
|
const { t } = useTranslation();
|
|
@@ -9710,7 +9879,29 @@ function SplitListCloseButton() {
|
|
|
9710
9879
|
size: "small",
|
|
9711
9880
|
onClick: splitView.openFullScreen,
|
|
9712
9881
|
"aria-label": t("hide_list"),
|
|
9713
|
-
children: /* @__PURE__ */ jsx(
|
|
9882
|
+
children: /* @__PURE__ */ jsx(ChevronsLeftIcon, { size: iconSize.smallest })
|
|
9883
|
+
})
|
|
9884
|
+
});
|
|
9885
|
+
}
|
|
9886
|
+
//#endregion
|
|
9887
|
+
//#region src/components/CollectionViewBinding/SplitListShowButton.tsx
|
|
9888
|
+
/**
|
|
9889
|
+
* Brings the list back beside a record that was opened full screen.
|
|
9890
|
+
*
|
|
9891
|
+
* The mirror of {@link SplitListCloseButton}: the same edge of the same bar,
|
|
9892
|
+
* the same double chevron pointing the other way, and the same single route —
|
|
9893
|
+
* showing the list is dropping the `#full` hash. It replaces the back arrow
|
|
9894
|
+
* where it applies, because the list it unfolds already holds the record.
|
|
9895
|
+
*/
|
|
9896
|
+
function SplitListShowButton({ onClick }) {
|
|
9897
|
+
const { t } = useTranslation();
|
|
9898
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
9899
|
+
title: t("show_list"),
|
|
9900
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9901
|
+
size: "small",
|
|
9902
|
+
onClick,
|
|
9903
|
+
"aria-label": t("show_list"),
|
|
9904
|
+
children: /* @__PURE__ */ jsx(ChevronsRightIcon, { size: iconSize.smallest })
|
|
9714
9905
|
})
|
|
9715
9906
|
});
|
|
9716
9907
|
}
|
|
@@ -9744,7 +9935,7 @@ function JsonPreviewBinding({ values }) {
|
|
|
9744
9935
|
}
|
|
9745
9936
|
//#endregion
|
|
9746
9937
|
//#region src/components/EntityInspector.tsx
|
|
9747
|
-
var EntityHistoryView = lazy(() => import("./history-
|
|
9938
|
+
var EntityHistoryView = lazy(() => import("./history-JL1ZmSKD.js").then((m) => ({ default: m.EntityHistoryView })));
|
|
9748
9939
|
/**
|
|
9749
9940
|
* Raw values and revision history, as an inspector rather than as tabs.
|
|
9750
9941
|
*
|
|
@@ -9820,6 +10011,36 @@ function EntityInspector({ tab, onTabChange, onClose, collection, entity, formCo
|
|
|
9820
10011
|
});
|
|
9821
10012
|
}
|
|
9822
10013
|
//#endregion
|
|
10014
|
+
//#region src/hooks/useEntityDisplayTitle.tsx
|
|
10015
|
+
/**
|
|
10016
|
+
* The heading for a record: the identity bar and the breadcrumb.
|
|
10017
|
+
*
|
|
10018
|
+
* Everything about *what a record is called* lives in {@link useEntityTitle}.
|
|
10019
|
+
* What is left here is the heading's own two rules, which are presentation, not
|
|
10020
|
+
* identity:
|
|
10021
|
+
*
|
|
10022
|
+
* The guard on `status` is the first. The title resolver returns the first
|
|
10023
|
+
* candidate carrying a value, and on a brand new entity the only values are the
|
|
10024
|
+
* property defaults — so creating a blog post opened a page headed **draft**,
|
|
10025
|
+
* the default of its `status` enum. A record that does not exist yet has no
|
|
10026
|
+
* name; it has an intent.
|
|
10027
|
+
*
|
|
10028
|
+
* The fallback is the second. A page needs a heading even when the record has
|
|
10029
|
+
* nothing readable in it, and the honest one is what kind of thing it is.
|
|
10030
|
+
*/
|
|
10031
|
+
function useEntityDisplayTitle({ collection, entity, values, status }) {
|
|
10032
|
+
const isNew = status === "new";
|
|
10033
|
+
const singular = collection.singularName ?? collection.name;
|
|
10034
|
+
const { value: title } = useEntityTitle({
|
|
10035
|
+
collection,
|
|
10036
|
+
entity,
|
|
10037
|
+
values,
|
|
10038
|
+
enabled: !isNew
|
|
10039
|
+
});
|
|
10040
|
+
if (isNew) return `New ${singular.toLowerCase()}`;
|
|
10041
|
+
return title ?? singular;
|
|
10042
|
+
}
|
|
10043
|
+
//#endregion
|
|
9823
10044
|
//#region src/util/view_constants.ts
|
|
9824
10045
|
/**
|
|
9825
10046
|
* Shared tab value constants used by EditViewBinding and DetailViewBinding.
|
|
@@ -9850,7 +10071,7 @@ function DetailViewBinding({ entityId, ...props }) {
|
|
|
9850
10071
|
children: content
|
|
9851
10072
|
});
|
|
9852
10073
|
}
|
|
9853
|
-
function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onTabChange, onEditClick, entity, dataLoading, layout = "full_screen", barActions, barActionsStart }) {
|
|
10074
|
+
function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onTabChange, onEditClick, entity, dataLoading, layout = "full_screen", barActions, barActionsStart, onShowList }) {
|
|
9854
10075
|
const ResolvedCollectionView = useComponentOverride("Collection.View", CollectionViewBinding);
|
|
9855
10076
|
const { t } = useTranslation();
|
|
9856
10077
|
const context = useRebaseContext();
|
|
@@ -9966,10 +10187,11 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
9966
10187
|
]));
|
|
9967
10188
|
const formViewConfig = collection.formView;
|
|
9968
10189
|
const FormViewBuilder = formViewConfig?.Builder ? resolveComponentRef(formViewConfig.Builder) : null;
|
|
9969
|
-
const
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
|
|
10190
|
+
const title = useEntityDisplayTitle({
|
|
10191
|
+
collection,
|
|
10192
|
+
entity: usedEntity,
|
|
10193
|
+
status: "existing"
|
|
10194
|
+
});
|
|
9973
10195
|
const nonActionCustomViews = useMemo(() => resolvedEntityViews.filter((e) => !e.includeActions), [resolvedEntityViews]);
|
|
9974
10196
|
const customViewsView = customViews && nonActionCustomViews.map((customView) => {
|
|
9975
10197
|
if (!customView) return null;
|
|
@@ -10035,21 +10257,30 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
10035
10257
|
collection
|
|
10036
10258
|
]);
|
|
10037
10259
|
const propertyDetailView = () => {
|
|
10038
|
-
if (FormViewBuilder && usedEntity) return /* @__PURE__ */ jsx(
|
|
10039
|
-
|
|
10040
|
-
children: /* @__PURE__ */ jsx(
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10260
|
+
if (FormViewBuilder && usedEntity) return /* @__PURE__ */ jsx("div", {
|
|
10261
|
+
className: "flex-1 min-w-0 overflow-y-auto flex justify-center items-start",
|
|
10262
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
10263
|
+
className: cls("w-full max-w-3xl 2xl:max-w-4xl flex flex-col", layout === "dialog" ? "pt-5 pb-12 px-6 sm:px-8" : "pt-6 pb-16 px-5 sm:px-8"),
|
|
10264
|
+
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(Suspense, {
|
|
10265
|
+
fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
|
|
10266
|
+
children: /* @__PURE__ */ jsx(FormViewBuilder, {
|
|
10267
|
+
collection,
|
|
10268
|
+
parentCollectionSlugs,
|
|
10269
|
+
parentEntityIds,
|
|
10270
|
+
entity: usedEntity,
|
|
10271
|
+
modifiedValues: usedEntity?.values,
|
|
10272
|
+
formContext: readOnlyFormContext
|
|
10273
|
+
})
|
|
10274
|
+
}) })
|
|
10047
10275
|
})
|
|
10048
|
-
})
|
|
10276
|
+
});
|
|
10049
10277
|
return /* @__PURE__ */ jsx(Fragment, { children: usedEntity && /* @__PURE__ */ jsx(EntityViewBinding, {
|
|
10050
10278
|
entity: usedEntity,
|
|
10051
10279
|
collection,
|
|
10052
|
-
path
|
|
10280
|
+
path,
|
|
10281
|
+
asPage: true,
|
|
10282
|
+
openEntityMode: layout,
|
|
10283
|
+
formContext: readOnlyFormContext
|
|
10053
10284
|
}) });
|
|
10054
10285
|
};
|
|
10055
10286
|
const subcollectionTabs = subcollections && subcollections.map((subcollection) => {
|
|
@@ -10085,15 +10316,8 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
10085
10316
|
})
|
|
10086
10317
|
}, `entity_detail_collection_tab_${view.name}`);
|
|
10087
10318
|
});
|
|
10088
|
-
const
|
|
10089
|
-
|
|
10090
|
-
children: /* @__PURE__ */ jsx(IconButton, {
|
|
10091
|
-
size: "small",
|
|
10092
|
-
"aria-label": t("back"),
|
|
10093
|
-
onClick: () => navigate(urlController.buildUrlCollectionPath(path)),
|
|
10094
|
-
children: /* @__PURE__ */ jsx(ArrowLeftIcon, { size: iconSize.smallest })
|
|
10095
|
-
})
|
|
10096
|
-
}) : null;
|
|
10319
|
+
const collectionUrl = withViewMode(urlController.buildUrlCollectionPath(path));
|
|
10320
|
+
const backToCollection = layout === "full_screen" && !onShowList ? () => navigate(collectionUrl) : void 0;
|
|
10097
10321
|
const fullScreenButton = !barActions && (layout === "side_panel" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
|
|
10098
10322
|
title: "Open full screen",
|
|
10099
10323
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
@@ -10114,29 +10338,31 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
10114
10338
|
children: t("edit_entity")
|
|
10115
10339
|
}) : null;
|
|
10116
10340
|
const mainView = /* @__PURE__ */ jsx("div", {
|
|
10117
|
-
className: cls("flex-1 flex flex-row w-full
|
|
10118
|
-
children:
|
|
10119
|
-
className: cls("w-full max-w-3xl 2xl:max-w-4xl flex flex-col", layout === "dialog" ? "pt-5 pb-12 px-6 sm:px-8" : "pt-6 pb-16 px-5 sm:px-8"),
|
|
10120
|
-
children: propertyDetailView()
|
|
10121
|
-
})
|
|
10341
|
+
className: cls("flex-1 flex flex-row w-full min-h-0", !mainViewVisible ? "hidden" : ""),
|
|
10342
|
+
children: propertyDetailView()
|
|
10122
10343
|
});
|
|
10123
10344
|
let result = /* @__PURE__ */ jsxs("div", {
|
|
10124
10345
|
className: "relative flex flex-col h-full w-full bg-white dark:bg-surface-800",
|
|
10125
10346
|
children: [
|
|
10126
10347
|
/* @__PURE__ */ jsx(EntityIdentityBar, {
|
|
10127
10348
|
collection,
|
|
10349
|
+
collectionUrl: layout === "full_screen" || layout === "split" ? collectionUrl : void 0,
|
|
10128
10350
|
title,
|
|
10129
10351
|
entityId,
|
|
10130
10352
|
status: "existing",
|
|
10131
10353
|
dirty: false,
|
|
10132
10354
|
saving: false,
|
|
10355
|
+
onBack: backToCollection,
|
|
10133
10356
|
onInspect: includeJsonView ? () => setInspectorTab("json") : void 0,
|
|
10134
10357
|
onViewHistory: includeHistoryView ? () => setInspectorTab("history") : void 0,
|
|
10135
|
-
leading: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10358
|
+
leading: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10359
|
+
barActionsStart,
|
|
10360
|
+
layout === "split" && /* @__PURE__ */ jsx(SplitListCloseButton, {}),
|
|
10361
|
+
layout === "full_screen" && onShowList && /* @__PURE__ */ jsx(SplitListShowButton, { onClick: onShowList })
|
|
10362
|
+
] }),
|
|
10136
10363
|
trailing: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10137
10364
|
editButton,
|
|
10138
10365
|
pluginActionsTop,
|
|
10139
|
-
backToCollectionButton,
|
|
10140
10366
|
fullScreenButton,
|
|
10141
10367
|
barActions?.({
|
|
10142
10368
|
path,
|
|
@@ -10552,12 +10778,7 @@ function SplitListView({ collection, tableController, onEntityClick: externalOnE
|
|
|
10552
10778
|
*/
|
|
10553
10779
|
function BoardCardBindingInner({ item, isDragging, isGroupedOver, style, collection, onClick, selected, onSelectionChange, selectionEnabled = false }) {
|
|
10554
10780
|
const entity = item.data;
|
|
10555
|
-
const
|
|
10556
|
-
const slots = useMemo(() => resolveEntitySlots(entity, collection, slotKeys), [
|
|
10557
|
-
entity,
|
|
10558
|
-
collection,
|
|
10559
|
-
slotKeys
|
|
10560
|
-
]);
|
|
10781
|
+
const slots = useEntitySlots(entity, collection, useCollectionSlotKeys(collection, useAuthController(), useCustomizationController().propertyConfigs));
|
|
10561
10782
|
const handleClick = useCallback((e) => {
|
|
10562
10783
|
if ((e.metaKey || e.ctrlKey) && selectionEnabled) {
|
|
10563
10784
|
e.preventDefault();
|
|
@@ -10614,11 +10835,9 @@ function BoardCardBindingInner({ item, isDragging, isGroupedOver, style, collect
|
|
|
10614
10835
|
onClick: handleThumbnailClick,
|
|
10615
10836
|
children: [slots.image ? /* @__PURE__ */ jsx("div", {
|
|
10616
10837
|
className: cls("w-10 h-10 rounded-md overflow-hidden transition-opacity duration-200", selectionEnabled && "group-hover/card:opacity-30", selected && "opacity-0"),
|
|
10617
|
-
children: /* @__PURE__ */ jsx(
|
|
10618
|
-
|
|
10619
|
-
propertyKey: slots.image.propertyKey,
|
|
10838
|
+
children: /* @__PURE__ */ jsx(SlotValue, {
|
|
10839
|
+
slot: slots.image,
|
|
10620
10840
|
size: "small",
|
|
10621
|
-
value: slots.image.value,
|
|
10622
10841
|
fill: true
|
|
10623
10842
|
})
|
|
10624
10843
|
}) : /* @__PURE__ */ jsx("div", {
|
|
@@ -10645,10 +10864,8 @@ function BoardCardBindingInner({ item, isDragging, isGroupedOver, style, collect
|
|
|
10645
10864
|
children: [
|
|
10646
10865
|
/* @__PURE__ */ jsx("div", {
|
|
10647
10866
|
className: "line-clamp-2 text-sm font-medium",
|
|
10648
|
-
children: slots.title ? /* @__PURE__ */ jsx(
|
|
10649
|
-
|
|
10650
|
-
value: slots.title.value,
|
|
10651
|
-
property: slots.title.property,
|
|
10867
|
+
children: slots.title ? /* @__PURE__ */ jsx(SlotValue, {
|
|
10868
|
+
slot: slots.title,
|
|
10652
10869
|
size: "small"
|
|
10653
10870
|
}) : /* @__PURE__ */ jsx("span", {
|
|
10654
10871
|
className: "text-surface-500",
|
|
@@ -10660,10 +10877,8 @@ function BoardCardBindingInner({ item, isDragging, isGroupedOver, style, collect
|
|
|
10660
10877
|
children: typeof slots.subtitle.value === "string" ? /* @__PURE__ */ jsx(Markdown, {
|
|
10661
10878
|
source: slots.subtitle.value,
|
|
10662
10879
|
size: "small"
|
|
10663
|
-
}) : /* @__PURE__ */ jsx(
|
|
10664
|
-
|
|
10665
|
-
value: slots.subtitle.value,
|
|
10666
|
-
property: slots.subtitle.property,
|
|
10880
|
+
}) : /* @__PURE__ */ jsx(SlotValue, {
|
|
10881
|
+
slot: slots.subtitle,
|
|
10667
10882
|
size: "small"
|
|
10668
10883
|
})
|
|
10669
10884
|
}) : /* @__PURE__ */ jsx("div", {
|
|
@@ -11324,14 +11539,14 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
|
|
|
11324
11539
|
const rawOrderProperty = collection.orderProperty;
|
|
11325
11540
|
const orderProperty = useMemo(() => {
|
|
11326
11541
|
if (!rawOrderProperty) return void 0;
|
|
11327
|
-
if (!getPropertyInPath(collection.properties, rawOrderProperty)) {
|
|
11542
|
+
if (!getPropertyInPath$1(collection.properties, rawOrderProperty)) {
|
|
11328
11543
|
console.warn(`orderProperty "${rawOrderProperty}" is defined but does not exist in the collection properties. Treating as unconfigured.`);
|
|
11329
11544
|
return;
|
|
11330
11545
|
}
|
|
11331
11546
|
return rawOrderProperty;
|
|
11332
11547
|
}, [rawOrderProperty, collection.properties]);
|
|
11333
11548
|
const { enumColumns, columnLabels, columnColors } = useMemo(() => {
|
|
11334
|
-
const property = getPropertyInPath(collection.properties, columnProperty);
|
|
11549
|
+
const property = getPropertyInPath$1(collection.properties, columnProperty);
|
|
11335
11550
|
if (!property || !("type" in property) || property.type !== "string") return {
|
|
11336
11551
|
enumColumns: [],
|
|
11337
11552
|
columnLabels: {}
|
|
@@ -12722,7 +12937,7 @@ function PopupFormFieldInternal({ tableKey, entityId, customFieldValidator, prop
|
|
|
12722
12937
|
openEntityMode: "side_panel",
|
|
12723
12938
|
disabled: false
|
|
12724
12939
|
};
|
|
12725
|
-
const property = propertyKey && getPropertyInPath(collection?.properties ?? {}, propertyKey);
|
|
12940
|
+
const property = propertyKey && getPropertyInPath$1(collection?.properties ?? {}, propertyKey);
|
|
12726
12941
|
const fieldProps = propertyKey && property ? {
|
|
12727
12942
|
propertyKey,
|
|
12728
12943
|
disabled: isSubmitting || isReadOnly(property) || !!property.admin?.disabled,
|
|
@@ -13716,7 +13931,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
13716
13931
|
}, [userConfigPersistence]);
|
|
13717
13932
|
const onColumnResize = useCallback(({ width, key }) => {
|
|
13718
13933
|
const collection = collectionRef.current;
|
|
13719
|
-
if (!getPropertyInPath(collection.properties, key)) return;
|
|
13934
|
+
if (!getPropertyInPath$1(collection.properties, key)) return;
|
|
13720
13935
|
const localCollection = buildPropertyWidthOverwrite(key, width);
|
|
13721
13936
|
onCollectionModifiedForUser(path, localCollection);
|
|
13722
13937
|
}, [onCollectionModifiedForUser, path]);
|
|
@@ -13762,7 +13977,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
13762
13977
|
const resolvedCollection = collection;
|
|
13763
13978
|
useMemo(() => {
|
|
13764
13979
|
if (!collection.kanban?.columnProperty) return false;
|
|
13765
|
-
const property = getPropertyInPath(resolvedCollection.properties, collection.kanban.columnProperty);
|
|
13980
|
+
const property = getPropertyInPath$1(resolvedCollection.properties, collection.kanban.columnProperty);
|
|
13766
13981
|
if (!property || property.type !== "string") return false;
|
|
13767
13982
|
return Boolean("enum" in property && property.enum);
|
|
13768
13983
|
}, [collection.kanban?.columnProperty, resolvedCollection.properties]);
|
|
@@ -13827,8 +14042,8 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
13827
14042
|
analyticsController
|
|
13828
14043
|
]);
|
|
13829
14044
|
const getPropertyFor = useCallback(({ propertyKey, entity }) => {
|
|
13830
|
-
let property = getPropertyInPath(collection.properties, propertyKey);
|
|
13831
|
-
if (!property) property = getPropertyInPath(resolvedCollection.properties, propertyKey);
|
|
14045
|
+
let property = getPropertyInPath$1(collection.properties, propertyKey);
|
|
14046
|
+
if (!property) property = getPropertyInPath$1(resolvedCollection.properties, propertyKey);
|
|
13832
14047
|
return property ?? null;
|
|
13833
14048
|
}, [collection.properties, resolvedCollection.properties]);
|
|
13834
14049
|
const displayedColumnIds = useColumnIds(useMemo(() => {
|
|
@@ -13967,7 +14182,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
13967
14182
|
} : void 0;
|
|
13968
14183
|
const onColumnsOrderChange = useCallback((newColumns) => {
|
|
13969
14184
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
13970
|
-
const newPropertiesOrder = newColumns.filter((col) => !col.frozen && getPropertyInPath(collection.properties, col.key)).map((col) => col.key).filter((key) => {
|
|
14185
|
+
const newPropertiesOrder = newColumns.filter((col) => !col.frozen && getPropertyInPath$1(collection.properties, col.key)).map((col) => col.key).filter((key) => {
|
|
13971
14186
|
if (seenKeys.has(key)) return false;
|
|
13972
14187
|
seenKeys.add(key);
|
|
13973
14188
|
return true;
|
|
@@ -14641,31 +14856,6 @@ function useRecordActions({ collection, path, entity }) {
|
|
|
14641
14856
|
]);
|
|
14642
14857
|
}
|
|
14643
14858
|
//#endregion
|
|
14644
|
-
//#region src/hooks/useEntityDisplayTitle.tsx
|
|
14645
|
-
/**
|
|
14646
|
-
* The human name for a record, for the identity bar and the breadcrumb.
|
|
14647
|
-
*
|
|
14648
|
-
* The guard on `status` is the point. The title resolver walks candidate
|
|
14649
|
-
* properties and returns the first non-empty one, and on a brand new entity the
|
|
14650
|
-
* only populated values are the property defaults — so creating a blog post
|
|
14651
|
-
* opened a page headed **draft**, the default of its `status` enum. A record
|
|
14652
|
-
* that does not exist yet has no name; it has an intent.
|
|
14653
|
-
*/
|
|
14654
|
-
function useEntityDisplayTitle({ collection, values, entityId, status }) {
|
|
14655
|
-
const isNew = status === "new";
|
|
14656
|
-
const titlePropertyKey = getEntityTitlePropertyKeyForEntity(collection, values, entityId);
|
|
14657
|
-
const rawTitle = !isNew && values && titlePropertyKey ? getValueInPath(values, titlePropertyKey) : void 0;
|
|
14658
|
-
const titleUser = useResolvedUser(isUserSelectProperty(collection, titlePropertyKey) && typeof rawTitle === "string" ? rawTitle : void 0);
|
|
14659
|
-
const singular = collection.singularName ?? collection.name;
|
|
14660
|
-
if (isNew) return `New ${singular.toLowerCase()}`;
|
|
14661
|
-
if (titleUser) return getUserLabel(titleUser);
|
|
14662
|
-
if (rawTitle !== void 0 && rawTitle !== null) {
|
|
14663
|
-
const resolved = resolveTitleToString(rawTitle);
|
|
14664
|
-
if (resolved) return resolved;
|
|
14665
|
-
}
|
|
14666
|
-
return singular;
|
|
14667
|
-
}
|
|
14668
|
-
//#endregion
|
|
14669
14859
|
//#region src/components/EditViewBinding.tsx
|
|
14670
14860
|
/**
|
|
14671
14861
|
* This is the default view that is used as the content of a side panel when
|
|
@@ -14715,7 +14905,7 @@ function EditViewBinding({ entityId, ...props }) {
|
|
|
14715
14905
|
children: content
|
|
14716
14906
|
});
|
|
14717
14907
|
}
|
|
14718
|
-
function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onValuesModified, onSaved, onTabChange, navigateBack, entity, initialDirtyValues, dataLoading, layout = "side_panel", barActions, barActionsStart, status, setStatus, formProps, canEdit }) {
|
|
14908
|
+
function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onValuesModified, onSaved, onTabChange, navigateBack, entity, initialDirtyValues, dataLoading, layout = "side_panel", barActions, barActionsStart, onShowList, status, setStatus, formProps, canEdit }) {
|
|
14719
14909
|
const ResolvedFormActions = useComponentOverride("EditView.FormActions", EditFormActions);
|
|
14720
14910
|
const ResolvedEntityForm = useComponentOverride("Entity.Form", EntityFormBinding);
|
|
14721
14911
|
const ResolvedCollectionView = useComponentOverride("Collection.View", CollectionViewBinding);
|
|
@@ -14860,8 +15050,8 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
14860
15050
|
const globalLoading = dataLoading && !usedEntity || canEdit === void 0 && (status === "existing" || status === "copy");
|
|
14861
15051
|
const displayTitle = useEntityDisplayTitle({
|
|
14862
15052
|
collection,
|
|
15053
|
+
entity: usedEntity,
|
|
14863
15054
|
values: formContext?.values ?? usedEntity?.values,
|
|
14864
|
-
entityId,
|
|
14865
15055
|
status
|
|
14866
15056
|
});
|
|
14867
15057
|
const formex = formContext?.formex;
|
|
@@ -14927,48 +15117,43 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
14927
15117
|
if (status === "existing") onTabChange?.({
|
|
14928
15118
|
path,
|
|
14929
15119
|
entityId,
|
|
14930
|
-
selectedTab: value === "__main_##Q$SC^#S6" ? void 0 : value,
|
|
14931
|
-
collection
|
|
14932
|
-
});
|
|
14933
|
-
}, [
|
|
14934
|
-
status,
|
|
14935
|
-
onTabChange,
|
|
14936
|
-
path,
|
|
14937
|
-
entityId,
|
|
14938
|
-
collection
|
|
14939
|
-
]);
|
|
14940
|
-
const formViewConfig = collection.formView;
|
|
14941
|
-
const FormViewBuilder = formViewConfig?.Builder ? resolveComponentRef(formViewConfig.Builder) : null;
|
|
14942
|
-
formViewConfig?.includeActions;
|
|
14943
|
-
const entityReadOnlyView = !canEdit && entity ? /* @__PURE__ */ jsx("div", {
|
|
14944
|
-
className: cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", canEdit || !mainViewVisible || selectedSecondaryForm ? "hidden" : ""),
|
|
14945
|
-
children: /* @__PURE__ */
|
|
14946
|
-
className:
|
|
14947
|
-
children:
|
|
14948
|
-
/* @__PURE__ */ jsx(
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
parentEntityIds,
|
|
14959
|
-
entity: usedEntity,
|
|
14960
|
-
modifiedValues: usedEntity?.values,
|
|
14961
|
-
formContext: readOnlyFormContext
|
|
14962
|
-
})
|
|
14963
|
-
}) }) : /* @__PURE__ */ jsx(EntityViewBinding, {
|
|
14964
|
-
className: "px-8 h-full overflow-auto",
|
|
14965
|
-
entity,
|
|
14966
|
-
path,
|
|
14967
|
-
collection
|
|
14968
|
-
}),
|
|
14969
|
-
/* @__PURE__ */ jsx("div", { className: "h-16" })
|
|
14970
|
-
]
|
|
15120
|
+
selectedTab: value === "__main_##Q$SC^#S6" ? void 0 : value,
|
|
15121
|
+
collection
|
|
15122
|
+
});
|
|
15123
|
+
}, [
|
|
15124
|
+
status,
|
|
15125
|
+
onTabChange,
|
|
15126
|
+
path,
|
|
15127
|
+
entityId,
|
|
15128
|
+
collection
|
|
15129
|
+
]);
|
|
15130
|
+
const formViewConfig = collection.formView;
|
|
15131
|
+
const FormViewBuilder = formViewConfig?.Builder ? resolveComponentRef(formViewConfig.Builder) : null;
|
|
15132
|
+
formViewConfig?.includeActions;
|
|
15133
|
+
const entityReadOnlyView = !canEdit && entity ? FormViewBuilder && readOnlyFormContext ? /* @__PURE__ */ jsx("div", {
|
|
15134
|
+
className: cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", canEdit || !mainViewVisible || selectedSecondaryForm ? "hidden" : ""),
|
|
15135
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
15136
|
+
className: "w-full max-w-3xl 2xl:max-w-4xl flex flex-col pt-6 pb-16 px-5 sm:px-8",
|
|
15137
|
+
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(Suspense, {
|
|
15138
|
+
fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
|
|
15139
|
+
children: /* @__PURE__ */ jsx(FormViewBuilder, {
|
|
15140
|
+
collection,
|
|
15141
|
+
parentCollectionSlugs,
|
|
15142
|
+
parentEntityIds,
|
|
15143
|
+
entity: usedEntity,
|
|
15144
|
+
modifiedValues: usedEntity?.values,
|
|
15145
|
+
formContext: readOnlyFormContext
|
|
15146
|
+
})
|
|
15147
|
+
}) })
|
|
14971
15148
|
})
|
|
15149
|
+
}) : /* @__PURE__ */ jsx(EntityViewBinding, {
|
|
15150
|
+
className: cls(canEdit || !mainViewVisible || selectedSecondaryForm ? "hidden" : ""),
|
|
15151
|
+
entity,
|
|
15152
|
+
path,
|
|
15153
|
+
collection,
|
|
15154
|
+
asPage: true,
|
|
15155
|
+
openEntityMode: layout,
|
|
15156
|
+
formContext: readOnlyFormContext
|
|
14972
15157
|
}) : null;
|
|
14973
15158
|
const entityView = FormViewBuilder ? /* @__PURE__ */ jsx("div", {
|
|
14974
15159
|
className: cls("relative flex-1 w-full h-full overflow-auto", (!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : ""),
|
|
@@ -15071,6 +15256,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
15071
15256
|
children: [
|
|
15072
15257
|
/* @__PURE__ */ jsx(EntityIdentityBar, {
|
|
15073
15258
|
collection,
|
|
15259
|
+
collectionUrl: layout === "full_screen" || layout === "split" ? withViewMode(urlController.buildUrlCollectionPath(path)) : void 0,
|
|
15074
15260
|
title: displayTitle,
|
|
15075
15261
|
entityId: status === "existing" ? entityId : void 0,
|
|
15076
15262
|
status,
|
|
@@ -15078,7 +15264,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
15078
15264
|
saving: Boolean(formContext?.isSaving),
|
|
15079
15265
|
hasErrors: hasFormErrors,
|
|
15080
15266
|
saveDisabled: !canEdit || saveDisabled,
|
|
15081
|
-
onBack: layout === "full_screen" ? () => navigate(withViewMode(urlController.buildUrlCollectionPath(path))) : void 0,
|
|
15267
|
+
onBack: layout === "full_screen" && !onShowList ? () => navigate(withViewMode(urlController.buildUrlCollectionPath(path))) : void 0,
|
|
15082
15268
|
onSave: canEdit && formContext ? () => {
|
|
15083
15269
|
sideDialogContext.setPendingClose?.(false);
|
|
15084
15270
|
formContext.submit();
|
|
@@ -15092,7 +15278,11 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
15092
15278
|
onViewHistory: includeHistoryView ? () => setInspectorTab("history") : void 0,
|
|
15093
15279
|
recordActions: recordActionItems,
|
|
15094
15280
|
pluginActions: formPluginActions,
|
|
15095
|
-
leading: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
15281
|
+
leading: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
15282
|
+
barActionsStart,
|
|
15283
|
+
layout === "split" && /* @__PURE__ */ jsx(SplitListCloseButton, {}),
|
|
15284
|
+
layout === "full_screen" && onShowList && /* @__PURE__ */ jsx(SplitListShowButton, { onClick: onShowList })
|
|
15285
|
+
] }),
|
|
15096
15286
|
trailing: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
15097
15287
|
pluginActionsTop,
|
|
15098
15288
|
fullScreenButton,
|
|
@@ -15877,10 +16067,11 @@ function useBuildCollectionRegistryController(props) {
|
|
|
15877
16067
|
if (pathSegments.length > 0 && pathSegments.length % 2 === 0) collectionPath = pathSegments.slice(0, -1).join("/");
|
|
15878
16068
|
if (!collectionPath) return void 0;
|
|
15879
16069
|
let collection;
|
|
15880
|
-
|
|
16070
|
+
collection = registry.get(collectionPath);
|
|
16071
|
+
if (!collection) try {
|
|
15881
16072
|
collection = registry.resolvePathToCollections(collectionPath).finalCollection;
|
|
15882
16073
|
} catch (e) {
|
|
15883
|
-
console.
|
|
16074
|
+
console.warn(`[rebase] Could not resolve a collection for "${collectionPath}"`, e);
|
|
15884
16075
|
return;
|
|
15885
16076
|
}
|
|
15886
16077
|
if (!collection) return;
|
|
@@ -20310,6 +20501,213 @@ function buildBottomActions({ savingError, entity, path, formActions, collection
|
|
|
20310
20501
|
});
|
|
20311
20502
|
}
|
|
20312
20503
|
//#endregion
|
|
20504
|
+
//#region src/components/PropertyCollectionView.tsx
|
|
20505
|
+
/**
|
|
20506
|
+
* Build a readable label for a path and resolve the property
|
|
20507
|
+
* Supports map and array (including arrays of maps)
|
|
20508
|
+
*/
|
|
20509
|
+
function buildPropertyLabelAndGetProperty(properties, key) {
|
|
20510
|
+
if (!key) return {
|
|
20511
|
+
label: "",
|
|
20512
|
+
property: void 0
|
|
20513
|
+
};
|
|
20514
|
+
const segments = [];
|
|
20515
|
+
const re = /([^[.\]]+)|\[(\d+)\]/g;
|
|
20516
|
+
let m;
|
|
20517
|
+
while ((m = re.exec(key)) !== null) if (m[1] !== void 0) segments.push(m[1]);
|
|
20518
|
+
else if (m[2] !== void 0) segments.push(Number(m[2]));
|
|
20519
|
+
let currentProps = properties;
|
|
20520
|
+
let currentProp;
|
|
20521
|
+
let lastLabel = "";
|
|
20522
|
+
const getArrayOfProp = (p) => {
|
|
20523
|
+
if (!p || p.type !== "array") return void 0;
|
|
20524
|
+
return Array.isArray(p.of) ? p.of[0] : p.of;
|
|
20525
|
+
};
|
|
20526
|
+
for (const seg of segments) {
|
|
20527
|
+
if (typeof seg === "number") {
|
|
20528
|
+
lastLabel = `[${seg}]`;
|
|
20529
|
+
if (currentProp?.type === "array") {
|
|
20530
|
+
currentProp = getArrayOfProp(currentProp);
|
|
20531
|
+
if (currentProp?.type === "map" && currentProp.properties) currentProps = currentProp.properties;
|
|
20532
|
+
else currentProps = void 0;
|
|
20533
|
+
} else {
|
|
20534
|
+
currentProp = void 0;
|
|
20535
|
+
currentProps = void 0;
|
|
20536
|
+
}
|
|
20537
|
+
continue;
|
|
20538
|
+
}
|
|
20539
|
+
if (currentProps && currentProps[seg]) {
|
|
20540
|
+
const nextProp = currentProps[seg];
|
|
20541
|
+
currentProp = nextProp;
|
|
20542
|
+
lastLabel = nextProp.name || String(seg);
|
|
20543
|
+
if (nextProp.type === "map" && nextProp.properties) currentProps = nextProp.properties;
|
|
20544
|
+
else if (nextProp.type === "array") currentProps = void 0;
|
|
20545
|
+
else currentProps = void 0;
|
|
20546
|
+
} else {
|
|
20547
|
+
currentProp = void 0;
|
|
20548
|
+
currentProps = void 0;
|
|
20549
|
+
lastLabel = String(seg);
|
|
20550
|
+
}
|
|
20551
|
+
}
|
|
20552
|
+
return {
|
|
20553
|
+
label: lastLabel,
|
|
20554
|
+
property: currentProp
|
|
20555
|
+
};
|
|
20556
|
+
}
|
|
20557
|
+
var pathEndsWithIndex = (p) => /\[\d+\]$/.test(p);
|
|
20558
|
+
/**
|
|
20559
|
+
* Improved simple layout for nested changes:
|
|
20560
|
+
* - Map or Array-of-Map -> section header + indented rows
|
|
20561
|
+
* - Leaf or Array-of-Primitives -> single row with label and value
|
|
20562
|
+
*/
|
|
20563
|
+
var PropertyCollectionView = ({ data, properties, baseKey = "", suppressHeader = false, size = "small" }) => {
|
|
20564
|
+
const isTopLevel = !!baseKey && !baseKey.includes(".") && !baseKey.includes("[");
|
|
20565
|
+
if (Array.isArray(data)) {
|
|
20566
|
+
const { label: arrayLabel, property } = baseKey ? buildPropertyLabelAndGetProperty(properties, baseKey) : {
|
|
20567
|
+
label: "",
|
|
20568
|
+
property: void 0
|
|
20569
|
+
};
|
|
20570
|
+
const ofProp = property?.type === "array" ? Array.isArray(property.of) ? property.of[0] : property.of : void 0;
|
|
20571
|
+
const isArrayOfMaps = ofProp?.type === "map";
|
|
20572
|
+
const isArrayOfPrimitives = property?.type === "array" && ofProp && ofProp.type !== "map";
|
|
20573
|
+
const isRelationArray = property?.type === "relation";
|
|
20574
|
+
if (baseKey && property && (isArrayOfPrimitives || isRelationArray)) return /* @__PURE__ */ jsxs("div", {
|
|
20575
|
+
className: `grid grid-cols-12 gap-x-4 ${isTopLevel ? "py-4" : "py-2"} items-start ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`,
|
|
20576
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
20577
|
+
className: "col-span-4 pr-2",
|
|
20578
|
+
children: /* @__PURE__ */ jsx(Typography, {
|
|
20579
|
+
variant: "caption",
|
|
20580
|
+
color: "secondary",
|
|
20581
|
+
component: "span",
|
|
20582
|
+
className: "break-words",
|
|
20583
|
+
children: arrayLabel
|
|
20584
|
+
})
|
|
20585
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
20586
|
+
className: "col-span-8",
|
|
20587
|
+
children: /* @__PURE__ */ jsx(PropertyPreview, {
|
|
20588
|
+
propertyKey: baseKey,
|
|
20589
|
+
value: data,
|
|
20590
|
+
property,
|
|
20591
|
+
size
|
|
20592
|
+
})
|
|
20593
|
+
})]
|
|
20594
|
+
});
|
|
20595
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
20596
|
+
className: `${isTopLevel ? "py-4" : "py-1"} ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`,
|
|
20597
|
+
children: [baseKey && arrayLabel && !suppressHeader && /* @__PURE__ */ jsx(Typography, {
|
|
20598
|
+
variant: "caption",
|
|
20599
|
+
color: "secondary",
|
|
20600
|
+
component: "span",
|
|
20601
|
+
children: arrayLabel
|
|
20602
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
20603
|
+
className: baseKey ? `pl-4 mt-1 border-l ${defaultBorderMixin}` : "",
|
|
20604
|
+
children: data.map((item, index) => {
|
|
20605
|
+
if (item === null || item === void 0) return null;
|
|
20606
|
+
const currentKey = baseKey ? `${baseKey}[${index}]` : `[${index}]`;
|
|
20607
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
20608
|
+
className: "py-1",
|
|
20609
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
20610
|
+
variant: "caption",
|
|
20611
|
+
color: "secondary",
|
|
20612
|
+
component: "span",
|
|
20613
|
+
children: isArrayOfMaps && ofProp?.name ? `${ofProp.name} [${index}]` : `[${index}]`
|
|
20614
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
20615
|
+
className: `pl-4 mt-1 border-l ${defaultBorderMixin}`,
|
|
20616
|
+
children: /* @__PURE__ */ jsx(PropertyCollectionView, {
|
|
20617
|
+
data: item,
|
|
20618
|
+
properties,
|
|
20619
|
+
baseKey: currentKey,
|
|
20620
|
+
suppressHeader: true,
|
|
20621
|
+
size
|
|
20622
|
+
})
|
|
20623
|
+
})]
|
|
20624
|
+
}, currentKey);
|
|
20625
|
+
})
|
|
20626
|
+
})]
|
|
20627
|
+
});
|
|
20628
|
+
}
|
|
20629
|
+
if (typeof data === "object" && data !== null) {
|
|
20630
|
+
const { label, property } = baseKey ? buildPropertyLabelAndGetProperty(properties, baseKey) : {
|
|
20631
|
+
label: "",
|
|
20632
|
+
property: void 0
|
|
20633
|
+
};
|
|
20634
|
+
if (baseKey && (!property || property.type !== "map" || !property.properties)) {
|
|
20635
|
+
if (!property) return null;
|
|
20636
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
20637
|
+
className: `grid grid-cols-12 gap-x-4 ${isTopLevel ? "py-4" : "py-2"} items-start ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`,
|
|
20638
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
20639
|
+
className: "col-span-4 pr-2",
|
|
20640
|
+
children: /* @__PURE__ */ jsx(Typography, {
|
|
20641
|
+
variant: "caption",
|
|
20642
|
+
color: "secondary",
|
|
20643
|
+
component: "span",
|
|
20644
|
+
className: "break-words",
|
|
20645
|
+
children: label
|
|
20646
|
+
})
|
|
20647
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
20648
|
+
className: "col-span-8",
|
|
20649
|
+
children: /* @__PURE__ */ jsx(PropertyPreview, {
|
|
20650
|
+
propertyKey: baseKey,
|
|
20651
|
+
value: data,
|
|
20652
|
+
property,
|
|
20653
|
+
size
|
|
20654
|
+
})
|
|
20655
|
+
})]
|
|
20656
|
+
});
|
|
20657
|
+
}
|
|
20658
|
+
const showMapHeader = baseKey && !suppressHeader && property?.type === "map" && (property.name || !pathEndsWithIndex(baseKey));
|
|
20659
|
+
const headerText = property?.name || label;
|
|
20660
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
20661
|
+
className: `${isTopLevel ? "py-4" : "py-1"} ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`,
|
|
20662
|
+
children: [showMapHeader && /* @__PURE__ */ jsx(Typography, {
|
|
20663
|
+
variant: "caption",
|
|
20664
|
+
color: "secondary",
|
|
20665
|
+
component: "span",
|
|
20666
|
+
children: headerText
|
|
20667
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
20668
|
+
className: baseKey ? `pl-4 mt-1 border-l ${defaultBorderMixin}` : "",
|
|
20669
|
+
children: Object.entries(data).map(([key, value]) => {
|
|
20670
|
+
if (value === null || value === void 0) return null;
|
|
20671
|
+
if (baseKey === "" && !properties[key]) return null;
|
|
20672
|
+
const currentKey = baseKey ? `${baseKey}.${key}` : key;
|
|
20673
|
+
return /* @__PURE__ */ jsx(PropertyCollectionView, {
|
|
20674
|
+
data: value,
|
|
20675
|
+
properties,
|
|
20676
|
+
baseKey: currentKey,
|
|
20677
|
+
size
|
|
20678
|
+
}, currentKey);
|
|
20679
|
+
})
|
|
20680
|
+
})]
|
|
20681
|
+
});
|
|
20682
|
+
}
|
|
20683
|
+
if (baseKey) {
|
|
20684
|
+
const { label, property } = buildPropertyLabelAndGetProperty(properties, baseKey);
|
|
20685
|
+
if (!property) return null;
|
|
20686
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
20687
|
+
className: `grid grid-cols-12 gap-x-4 ${isTopLevel ? "py-4" : "py-2"} items-start ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`,
|
|
20688
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
20689
|
+
className: "col-span-4 pr-2",
|
|
20690
|
+
children: /* @__PURE__ */ jsx(Typography, {
|
|
20691
|
+
variant: "caption",
|
|
20692
|
+
color: "secondary",
|
|
20693
|
+
component: "span",
|
|
20694
|
+
className: "break-words",
|
|
20695
|
+
children: label
|
|
20696
|
+
})
|
|
20697
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
20698
|
+
className: "col-span-8",
|
|
20699
|
+
children: /* @__PURE__ */ jsx(PropertyPreview, {
|
|
20700
|
+
propertyKey: baseKey,
|
|
20701
|
+
value: data,
|
|
20702
|
+
property,
|
|
20703
|
+
size
|
|
20704
|
+
})
|
|
20705
|
+
})]
|
|
20706
|
+
});
|
|
20707
|
+
}
|
|
20708
|
+
return null;
|
|
20709
|
+
};
|
|
20710
|
+
//#endregion
|
|
20313
20711
|
//#region src/form/components/LocalChangesMenu.tsx
|
|
20314
20712
|
function LocalChangesMenu({ cachedData, formex, onClearLocalChanges, cacheKey, properties }) {
|
|
20315
20713
|
const snackbarController = useSnackbarController();
|
|
@@ -20775,10 +21173,11 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
20775
21173
|
context: additionalFieldContext
|
|
20776
21174
|
}) : /* @__PURE__ */ jsx("div", {
|
|
20777
21175
|
className: "w-full",
|
|
20778
|
-
children:
|
|
21176
|
+
children: /* @__PURE__ */ jsx(AdditionalFieldValue, {
|
|
21177
|
+
field: additionalField,
|
|
20779
21178
|
entity,
|
|
20780
21179
|
context: additionalFieldContext
|
|
20781
|
-
})
|
|
21180
|
+
})
|
|
20782
21181
|
});
|
|
20783
21182
|
return /* @__PURE__ */ jsxs("div", {
|
|
20784
21183
|
className: spanClass(field.span),
|
|
@@ -20956,24 +21355,6 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
20956
21355
|
})
|
|
20957
21356
|
});
|
|
20958
21357
|
}
|
|
20959
|
-
/**
|
|
20960
|
-
* Grid placement for a span.
|
|
20961
|
-
*
|
|
20962
|
-
* Written out as literal class strings rather than composed, because Tailwind
|
|
20963
|
-
* scans source text: a template literal would produce classes that exist at
|
|
20964
|
-
* runtime and were never generated into the stylesheet.
|
|
20965
|
-
*
|
|
20966
|
-
* Below the column breakpoint every field takes the single available column, so
|
|
20967
|
-
* the side panel, the dialog and mobile all get a plain stack.
|
|
20968
|
-
*/
|
|
20969
|
-
function spanClass(span) {
|
|
20970
|
-
switch (span) {
|
|
20971
|
-
case 1: return "@2xl/col:col-span-1";
|
|
20972
|
-
case 2: return "@2xl/col:col-span-2";
|
|
20973
|
-
case 3: return "@2xl/col:col-span-3";
|
|
20974
|
-
case 4: return "@2xl/col:col-span-4";
|
|
20975
|
-
}
|
|
20976
|
-
}
|
|
20977
21358
|
function useOnAutoSave(autoSave, formex, lastSavedValues, save) {
|
|
20978
21359
|
useEffect(() => {
|
|
20979
21360
|
if (!autoSave) return;
|
|
@@ -22087,6 +22468,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
|
|
|
22087
22468
|
return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
|
|
22088
22469
|
}
|
|
22089
22470
|
//#endregion
|
|
22090
|
-
export { useBuildCollectionRegistryController as $, sanitizeUrl as $n, CollectionTableBinding as $t, getFieldId as A,
|
|
22471
|
+
export { useBuildCollectionRegistryController as $, sanitizeUrl as $n, CollectionTableBinding as $t, getFieldId as A, ReferencePreview as An, convertFileToJson as At, MapFieldBinding as B, getEntityTitlePropertyKeyForEntity as Bn, mergeEntityActions as Bt, removeEmptyContainers as C, ArrayOfStringsPreview as Cn, convertDataToEntity as Ct, getDefaultFieldConfig as D, RelationPreview as Dn, useImportConfig as Dt, DEFAULT_FIELD_CONFIGS as E, ArrayOfStorageComponentsPreview as En, getInferenceType as Et, SelectFieldBinding as F, useCollectionRegistryController as Fn, FieldCaption as Ft, useSelectionDialog as G, StorageThumbnailInternal as Gn, removeInitialAndTrailingSlashes$1 as Gt, DateTimeFieldBinding as H, StringPropertyPreview as Hn, getCollectionBySlugWithin as Ht, RepeatFieldBinding as I, getUserLabel as In, useBreadcrumbsController as It, useBuildUrlController as J, renderSkeletonIcon as Jn, resolveCollectionPathIds$1 as Jt, SelectionTableBinding as K, SkeletonPropertyComponent as Kn, removeInitialSlash as Kt, ReferenceFieldBinding as L, useResolvedUser as Ln, BreadcrumbsProvider as Lt, TextFieldBinding as M, SidePanelControllerContext as Mn, ArrayContainer as Mt, SwitchFieldBinding as N, useSidePanel as Nn, PropertyConfigBadge as Nt, getDefaultFieldId as O, ArrayOfReferencesPreview as On, ImportSaveInProgress as Ot, StorageUploadFieldBinding as P, CollectionRegistryContext as Pn, SearchIconsView as Pt, useResolvedCollections as Q, ImagePreview as Qn, VirtualTableInput$1 as Qt, MultiSelectFieldBinding as R, getEntityPreviewKeys as Rn, resolveEntityAction as Rt, getInitialEntityValues as S, ArrayOneOfPreview as Sn, DataNewPropertiesMapping as St, PropertyFieldBinding as T, ArrayEnumPreview as Tn, processValueMapping as Tt, BlockFieldBinding as U, EnumValuesChip as Un, getCollectionPathsCombinations as Ut, KeyValueFieldBinding as V, ArrayPropertyPreview as Vn, addInitialSlash as Vt, ArrayOfReferencesFieldBinding as W, StorageThumbnail as Wn, getLastSegment$1 as Wt, useTopLevelNavigation as X, renderSkeletonText as Xn, resolveViewMode as Xt, useBuildNavigationStateController as Y, renderSkeletonImageThumbnail as Yn, resolveOpenEntityMode as Yt, useResolvedViews as Z, UrlComponentPreview as Zn, useSelectionController as Zt, useCollectionsConfigController as _, NumberPropertyPreview as _n, isRelationProperty as _r, CollectionViewActions as _t, namespaceToPropertiesPath as a, NavigationStateContext as an, FieldBlock as ar, useBuildSidePanel as at, extractTouchedValues as b, KeyValuePreview as bn, useCollectionEditorDialogsState as bt, buildCollectionGenerationCallback as c, SideDialogsControllerContext as cn, PropertyIdCopyTooltip as cr, copyEntityAction as ct, fromSerializableCollectionConfigs as d, useClearRestoreValue as dn, getIconForProperty as dr, resetPasswordAction as dt, SelectableTable as en, EmptyValue as er, useHistory as et, fromSerializableProperties as f, ReadOnlyFieldBinding as fn, getIconForWidget as fr, CreationResultDialog as ft, toSerializableProperty as g, UserPreview as gn, isReferenceProperty as gr, EntityCardBinding as gt, toSerializableProperties as h, PropertyPreview as hn, getResolvedPropertyInPath as hr, CollectionCardViewBinding as ht, namespaceToPropertiesOrderPath as i, useUrlController as in, FormRail as ir, getEntityViewWidth as it, VectorFieldBinding as j, EntityPreviewBinding as jn, unflattenObject as jt, getFieldConfig as k, InlineEntityListPreview as kn, ImportFileUpload as kt, validateCollectionJson as l, SelectableTableContext as ln, getBracketNotation as lr, deleteEntityAction as lt, toSerializableCollectionConfig as m, ArrayOfMapsPreview as mn, getPropertyInPath$1 as mr, EntityViewBinding as mt, getFullIdPath as n, useAdminContext as nn, LabelWithIcon as nr, useResolvedNavigationFrom as nt, CollectionGenerationApiError as o, useNavigationStateController as on, isSelfLabellingProperty as or, EditViewBinding as ot, fromSerializableProperty as p, FieldHelperText as pn, getPropertiesWithPropertiesOrder as pr, DetailViewBinding as pt, SideDialogs as q, renderSkeletonCaptionText as qn, removeTrailingSlash$1 as qt, idToPropertiesPath as r, UrlContext as rn, FormSections as rr, buildSidePanelsFromUrl as rt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as s, useSideDialogsController as sn, spanClass as sr, CollectionViewBinding as st, getFullId as t, CollectionRowActions as tn, LabelWithIconAndTooltip as tr, resolveNavigationFrom as tt, fromSerializableCollectionConfig as u, ArrayCustomShapedFieldBinding as un, getDefaultPropertiesOrder as ur, editEntityAction as ut, EntityFormBinding as v, BooleanPreview as vn, useCollectionEditorController as vt, zodToFormErrors as w, ArrayPropertyEnumPreview as wn, flattenEntry as wt, getChanges as x, MapPropertyPreview as xn, ImportNewPropertyFieldPreview as xt, EntityForm as y, DatePreview as yn, ConfigControllerProvider as yt, MarkdownEditorFieldBinding as z, getEntityTitlePropertyKey as zn, resolveEntityView as zt };
|
|
22091
22472
|
|
|
22092
|
-
//# sourceMappingURL=util-
|
|
22473
|
+
//# sourceMappingURL=util-pQ3YwBve.js.map
|