@rebasepro/admin 0.13.1-canary.gef9608c → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{CollectionEditorDialog-7b-kSLno.js → CollectionEditorDialog-CSheud_E.js} +19 -9
- package/dist/CollectionEditorDialog-CSheud_E.js.map +1 -0
- package/dist/{PropertyEditView-C-31uvpG.js → PropertyEditView-CWZlC1gq.js} +5 -5
- package/dist/PropertyEditView-CWZlC1gq.js.map +1 -0
- package/dist/{RouterCollectionsStudioView-BJylABq3.js → RouterCollectionsStudioView-CwTpdzWF.js} +9 -13
- package/dist/RouterCollectionsStudioView-CwTpdzWF.js.map +1 -0
- package/dist/collection_editor/serializable_types.d.ts +1 -0
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/ClearFilterSortButton.d.ts +7 -1
- package/dist/components/CollectionViewBinding/EntityCardBinding.d.ts +3 -1
- package/dist/components/CollectionViewBinding/SearchExplanation.d.ts +48 -0
- package/dist/components/CollectionViewBinding/SearchHighlight.d.ts +101 -0
- package/dist/components/CollectionViewBinding/SlotValue.d.ts +26 -0
- package/dist/components/CollectionViewBinding/SortButton.d.ts +7 -1
- package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +17 -6
- package/dist/components/CollectionViewBinding/SplitListShowButton.d.ts +1 -1
- package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +26 -1
- package/dist/components/DetailViewBinding.d.ts +7 -0
- package/dist/components/DrawerNavigationGroup.d.ts +4 -2
- package/dist/components/DrawerNavigationItem.d.ts +13 -5
- package/dist/components/EditViewBinding.d.ts +22 -7
- package/dist/components/EntityIdentityBar.d.ts +24 -1
- package/dist/components/RelationSelector.d.ts +13 -0
- package/dist/data_export/export/export.d.ts +7 -0
- package/dist/data_export/export/fetch_export_data.d.ts +48 -0
- package/dist/data_export/export/index.d.ts +1 -0
- package/dist/data_import/utils/csv.d.ts +30 -0
- package/dist/data_import/utils/index.d.ts +2 -0
- package/dist/data_import/utils/save_entities.d.ts +38 -0
- package/dist/data_import/utils/transforms.d.ts +9 -0
- package/dist/editor.js +3 -0
- package/dist/editor.js.map +1 -1
- package/dist/{export-C-XG3aRh.js → export-L53WektO.js} +175 -39
- package/dist/export-L53WektO.js.map +1 -0
- package/dist/form/form_utils.d.ts +38 -1
- package/dist/{history-DfMuvPfr.js → history-D5SKygpJ.js} +2 -2
- package/dist/{history-DfMuvPfr.js.map → history-D5SKygpJ.js.map} +1 -1
- package/dist/{import-B0GyllB0.js → import-C_4edkoD.js} +5 -4
- package/dist/import-C_4edkoD.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +96 -80
- package/dist/index.js.map +1 -1
- package/dist/preview/components/date_fns_locales.d.ts +9 -0
- package/dist/util/view_constants.d.ts +24 -0
- package/dist/util/view_mode.d.ts +18 -0
- package/dist/{util-DHNXodBo.js → util-C-D5yD2n.js} +2173 -673
- package/dist/util-C-D5yD2n.js.map +1 -0
- package/package.json +10 -9
- package/src/collection_editor/serializable_types.ts +1 -0
- package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +8 -3
- package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +5 -2
- package/src/collection_editor/ui/collection_editor/CollectionJsonImportDialog.tsx +1 -0
- package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +9 -8
- package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +9 -11
- package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +5 -2
- package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +5 -2
- package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +1 -0
- package/src/collection_editor/useJsonCollectionsConfigController.tsx +92 -84
- package/src/collection_editor/useLocalCollectionsConfigController.tsx +10 -3
- package/src/components/ClearFilterSortButton.tsx +16 -7
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +2 -2
- package/src/components/CollectionTableBinding/PropertyTableCell.tsx +2 -2
- package/src/components/CollectionTableBinding/column_utils.tsx +15 -6
- package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +2 -2
- package/src/components/CollectionTableBinding/table_bindings.tsx +7 -1
- package/src/components/CollectionViewBinding/BoardCardBinding.tsx +18 -1
- package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +13 -7
- package/src/components/CollectionViewBinding/CollectionCardViewBinding.tsx +2 -1
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +816 -335
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +1 -1
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +26 -6
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +9 -0
- package/src/components/CollectionViewBinding/EntityCardBinding.tsx +45 -10
- package/src/components/CollectionViewBinding/SearchExplanation.tsx +106 -0
- package/src/components/CollectionViewBinding/SearchHighlight.tsx +311 -0
- package/src/components/CollectionViewBinding/SlotValue.tsx +96 -0
- package/src/components/CollectionViewBinding/SortButton.tsx +16 -4
- package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +23 -9
- package/src/components/CollectionViewBinding/SplitListShowButton.tsx +3 -3
- package/src/components/CollectionViewBinding/SplitListView.tsx +36 -9
- package/src/components/CollectionViewBinding/ViewModeToggle.tsx +10 -5
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +11 -3
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +76 -24
- package/src/components/CollectionViewBinding/utils.ts +7 -4
- package/src/components/DefaultDrawer.tsx +7 -0
- package/src/components/DetailViewBinding.tsx +33 -9
- package/src/components/DrawerNavigationGroup.tsx +12 -32
- package/src/components/DrawerNavigationItem.tsx +33 -12
- package/src/components/EditViewBinding.tsx +175 -28
- package/src/components/EntityIdentityBar.tsx +109 -17
- package/src/components/EntityInspector.tsx +24 -6
- package/src/components/EntityViewBinding.tsx +4 -2
- package/src/components/ReferenceTable/SelectionTableBinding.tsx +4 -2
- package/src/components/RelationSelector.tsx +61 -11
- package/src/components/SearchIconsView.tsx +14 -33
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -0
- package/src/components/SidePanelBinding.tsx +36 -25
- package/src/components/UserSelector.tsx +52 -9
- package/src/components/history/LastEditedByIndicator.tsx +7 -12
- package/src/data_export/export/ExportCollectionAction.tsx +101 -48
- package/src/data_export/export/export.ts +63 -16
- package/src/data_export/export/fetch_export_data.ts +90 -0
- package/src/data_export/export/index.ts +1 -0
- package/src/data_import/components/DataNewPropertiesMapping.tsx +20 -3
- package/src/data_import/components/ImportNewPropertyFieldPreview.tsx +1 -0
- package/src/data_import/components/ImportSaveInProgress.tsx +35 -36
- package/src/data_import/import/ImportCollectionAction.tsx +13 -2
- package/src/data_import/utils/csv.ts +153 -0
- package/src/data_import/utils/data.ts +16 -2
- package/src/data_import/utils/file_to_json.ts +77 -6
- package/src/data_import/utils/index.ts +2 -0
- package/src/data_import/utils/save_entities.ts +138 -0
- package/src/data_import/utils/transforms.ts +23 -0
- package/src/editor/components/image-bubble.tsx +2 -0
- package/src/editor/selectors/link-selector.tsx +1 -0
- package/src/form/EntityForm.tsx +12 -3
- package/src/form/EntityFormBinding.tsx +8 -12
- package/src/form/PropertyFieldBinding.tsx +2 -2
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -2
- package/src/form/field_bindings/BinaryFieldBinding.tsx +1 -0
- package/src/form/field_bindings/BlockFieldBinding.tsx +3 -3
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +4 -0
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +3 -0
- package/src/form/field_bindings/MapFieldBinding.tsx +2 -2
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +15 -2
- package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +2 -2
- package/src/form/field_bindings/TextFieldBinding.tsx +18 -0
- package/src/form/field_bindings/VectorFieldBinding.tsx +1 -0
- package/src/form/form_utils.ts +78 -0
- package/src/index.ts +9 -1
- package/src/preview/PropertyPreview.tsx +7 -3
- package/src/preview/components/DatePreview.tsx +15 -2
- package/src/preview/components/ReferencePreview.tsx +5 -8
- package/src/preview/components/RelationPreview.tsx +8 -7
- package/src/preview/components/date_fns_locales.ts +125 -0
- package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +6 -3
- package/src/util/navigation_utils.ts +4 -2
- package/src/util/property_utils.tsx +6 -1
- package/src/util/view_constants.ts +27 -0
- package/src/util/view_mode.ts +36 -0
- package/dist/CollectionEditorDialog-7b-kSLno.js.map +0 -1
- package/dist/PropertyEditView-C-31uvpG.js.map +0 -1
- package/dist/RouterCollectionsStudioView-BJylABq3.js.map +0 -1
- package/dist/export-C-XG3aRh.js.map +0 -1
- package/dist/import-B0GyllB0.js.map +0 -1
- package/dist/util-DHNXodBo.js.map +0 -1
|
@@ -4,7 +4,7 @@ import { deepEqual as equal } from "fast-equals"
|
|
|
4
4
|
|
|
5
5
|
import { EntityReference, EntityRelation } from "@rebasepro/types";
|
|
6
6
|
import type { PropertyPreviewProps } from "../types/components/PropertyPreviewProps";
|
|
7
|
-
import { resolveProperty, normalizeToEntityRelation } from "@rebasepro/common";
|
|
7
|
+
import { resolveProperty, normalizeToEntityRelation, getRelationTargetPath } from "@rebasepro/common";
|
|
8
8
|
import { useAuthController, useCustomizationController, resolveComponentRef } from "@rebasepro/app";
|
|
9
9
|
import { EmptyValue } from "./components/EmptyValue";
|
|
10
10
|
import { UrlComponentPreview } from "./components/UrlComponentPreview";
|
|
@@ -234,12 +234,16 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
} else if (property.type === "relation") {
|
|
237
|
+
// A relation column arrives either hydrated or as the bare foreign key,
|
|
238
|
+
// depending on the fetch path. The declared target resolves the latter;
|
|
239
|
+
// see `normalizeToEntityRelation`.
|
|
240
|
+
const relationTargetPath = getRelationTargetPath(property);
|
|
237
241
|
if (!value) {
|
|
238
242
|
content = <EmptyValue/>;
|
|
239
243
|
} else if (Array.isArray(value)) {
|
|
240
244
|
// Many-cardinality relation: value is an array of EntityRelation (or plain objects)
|
|
241
245
|
const relations = (value as unknown[])
|
|
242
|
-
.map(item => normalizeToEntityRelation(item, "relation"))
|
|
246
|
+
.map(item => normalizeToEntityRelation(item, "relation", relationTargetPath))
|
|
243
247
|
.filter(Boolean) as EntityRelation[];
|
|
244
248
|
const renderRelation = (entityRelation: EntityRelation, index: number) => <RelationPreview
|
|
245
249
|
key={`preview_rel_${propertyKey}_${index}`}
|
|
@@ -268,7 +272,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
268
272
|
);
|
|
269
273
|
} else {
|
|
270
274
|
// Single-cardinality relation
|
|
271
|
-
const relationValue = normalizeToEntityRelation(value, "relation");
|
|
275
|
+
const relationValue = normalizeToEntityRelation(value, "relation", relationTargetPath);
|
|
272
276
|
if (relationValue) {
|
|
273
277
|
content = <RelationPreview
|
|
274
278
|
disabled={!property.relation}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
3
|
import { format } from "date-fns";
|
|
4
|
-
import * as locales from "date-fns/locale";
|
|
5
4
|
import { useCustomizationController } from "@rebasepro/app";
|
|
6
5
|
import { defaultDateFormat } from "@rebasepro/utils";
|
|
6
|
+
import { getLoadedDateFnsLocale, loadDateFnsLocale } from "./date_fns_locales";
|
|
7
7
|
|
|
8
8
|
export interface DatePreviewProps {
|
|
9
9
|
date: Date;
|
|
@@ -28,7 +28,20 @@ export function DatePreview({
|
|
|
28
28
|
}: DatePreviewProps): React.ReactElement {
|
|
29
29
|
|
|
30
30
|
const customizationController = useCustomizationController();
|
|
31
|
-
const
|
|
31
|
+
const configuredLocale = customizationController?.locale;
|
|
32
|
+
|
|
33
|
+
// The locale arrives asynchronously now (see `date_fns_locales`). Seed from
|
|
34
|
+
// the cache so only the very first date on a page renders un-localised: by
|
|
35
|
+
// the time a table of them mounts, the module is already resolved and there
|
|
36
|
+
// is no second pass.
|
|
37
|
+
const [dateUtilsLocale, setDateUtilsLocale] = React.useState(() => getLoadedDateFnsLocale(configuredLocale));
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
let cancelled = false;
|
|
40
|
+
loadDateFnsLocale(configuredLocale)
|
|
41
|
+
.then(loaded => { if (!cancelled) setDateUtilsLocale(loaded); })
|
|
42
|
+
.catch(error => console.error(`[rebase] could not load the "${configuredLocale}" date locale`, error));
|
|
43
|
+
return () => { cancelled = true; };
|
|
44
|
+
}, [configuredLocale]);
|
|
32
45
|
|
|
33
46
|
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
|
34
47
|
return <></>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { EntityReference } from "@rebasepro/types";
|
|
5
5
|
import type { PreviewSize } from "../../types/components/PropertyPreviewProps";
|
|
6
6
|
import { useCustomizationController, useFetch, useComponentOverride, CollectionScopeProvider } from "@rebasepro/app";
|
|
7
7
|
import { Skeleton } from "@rebasepro/ui";
|
|
@@ -157,11 +157,10 @@ function ReferencePreviewExisting<M extends Record<string, unknown> = Record<str
|
|
|
157
157
|
useCache: true
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const usedEntity = entity ?? referencesCache.get(reference.pathWithId);
|
|
160
|
+
// Only what the fetch currently reports — see the note in RelationPreview:
|
|
161
|
+
// the module-level Map that used to back this kept a deleted row on screen
|
|
162
|
+
// for the rest of the session, and across a sign-out.
|
|
163
|
+
const usedEntity = entity;
|
|
165
164
|
|
|
166
165
|
let body: React.ReactNode;
|
|
167
166
|
|
|
@@ -234,5 +233,3 @@ function ReferencePreviewExisting<M extends Record<string, unknown> = Record<str
|
|
|
234
233
|
hover={hover}/>;
|
|
235
234
|
|
|
236
235
|
}
|
|
237
|
-
|
|
238
|
-
const referencesCache = new Map<string, Entity<any>>();
|
|
@@ -189,11 +189,14 @@ function RelationPreviewExisting<M extends Record<string, unknown> = Record<stri
|
|
|
189
189
|
useCache: true
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
192
|
+
// Only what the fetch currently reports. There used to be a module-level
|
|
193
|
+
// Map behind this, written on every successful fetch and never invalidated:
|
|
194
|
+
// when the target row was deleted `useFetch` correctly reported it gone and
|
|
195
|
+
// the card carried on rendering the copy in the Map, so the "missing"
|
|
196
|
+
// branches below were unreachable for anything previewed this session — and
|
|
197
|
+
// the Map outlived a sign-out. `useFetch` keeps its own cache, seeds the
|
|
198
|
+
// first render from it, and clears the entry when the row disappears.
|
|
199
|
+
const usedEntity = passedEntity ?? entity;
|
|
197
200
|
|
|
198
201
|
let body: React.ReactNode;
|
|
199
202
|
|
|
@@ -266,5 +269,3 @@ function RelationPreviewExisting<M extends Record<string, unknown> = Record<stri
|
|
|
266
269
|
hover={hover}/>;
|
|
267
270
|
|
|
268
271
|
}
|
|
269
|
-
|
|
270
|
-
const relationsCache = new Map<string, Entity<any>>();
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Locale as LocaleKey } from "@rebasepro/admin-types";
|
|
2
|
+
import type { Locale as DateFnsLocale } from "date-fns";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One date-fns locale, fetched when the admin is actually configured to use it.
|
|
6
|
+
*
|
|
7
|
+
* `DatePreview` used to open with:
|
|
8
|
+
*
|
|
9
|
+
* import * as locales from "date-fns/locale";
|
|
10
|
+
*
|
|
11
|
+
* and then index that namespace with the configured locale. A namespace import
|
|
12
|
+
* of a barrel is a reference to every member of it, so all ~77 locales were
|
|
13
|
+
* bundled — 640 kB, in the entry chunk's static graph, to format dates in the
|
|
14
|
+
* one locale a given deployment has configured (and `undefined`, meaning
|
|
15
|
+
* en-US, for nearly all of them).
|
|
16
|
+
*
|
|
17
|
+
* A map of thunks is written out rather than derived from the locale key,
|
|
18
|
+
* because `import(`date-fns/locale/${name}`)` is not statically analysable:
|
|
19
|
+
* the bundler cannot see which modules it must emit, and the import fails at
|
|
20
|
+
* runtime in a build. Each entry below is its own async chunk, and exactly one
|
|
21
|
+
* of them is ever fetched.
|
|
22
|
+
*
|
|
23
|
+
* `fil` is in the `Locale` union and has no date-fns locale to import; it
|
|
24
|
+
* resolved to `undefined` through the namespace too, so it still does.
|
|
25
|
+
*/
|
|
26
|
+
const dateFnsLocaleLoaders: Partial<Record<LocaleKey, () => Promise<DateFnsLocale>>> = {
|
|
27
|
+
af: () => import("date-fns/locale/af").then(m => m.af),
|
|
28
|
+
ar: () => import("date-fns/locale/ar").then(m => m.ar),
|
|
29
|
+
arDZ: () => import("date-fns/locale/ar-DZ").then(m => m.arDZ),
|
|
30
|
+
arMA: () => import("date-fns/locale/ar-MA").then(m => m.arMA),
|
|
31
|
+
arSA: () => import("date-fns/locale/ar-SA").then(m => m.arSA),
|
|
32
|
+
az: () => import("date-fns/locale/az").then(m => m.az),
|
|
33
|
+
be: () => import("date-fns/locale/be").then(m => m.be),
|
|
34
|
+
bg: () => import("date-fns/locale/bg").then(m => m.bg),
|
|
35
|
+
bn: () => import("date-fns/locale/bn").then(m => m.bn),
|
|
36
|
+
ca: () => import("date-fns/locale/ca").then(m => m.ca),
|
|
37
|
+
cs: () => import("date-fns/locale/cs").then(m => m.cs),
|
|
38
|
+
cy: () => import("date-fns/locale/cy").then(m => m.cy),
|
|
39
|
+
da: () => import("date-fns/locale/da").then(m => m.da),
|
|
40
|
+
de: () => import("date-fns/locale/de").then(m => m.de),
|
|
41
|
+
el: () => import("date-fns/locale/el").then(m => m.el),
|
|
42
|
+
enAU: () => import("date-fns/locale/en-AU").then(m => m.enAU),
|
|
43
|
+
enCA: () => import("date-fns/locale/en-CA").then(m => m.enCA),
|
|
44
|
+
enGB: () => import("date-fns/locale/en-GB").then(m => m.enGB),
|
|
45
|
+
enIN: () => import("date-fns/locale/en-IN").then(m => m.enIN),
|
|
46
|
+
enNZ: () => import("date-fns/locale/en-NZ").then(m => m.enNZ),
|
|
47
|
+
enUS: () => import("date-fns/locale/en-US").then(m => m.enUS),
|
|
48
|
+
eo: () => import("date-fns/locale/eo").then(m => m.eo),
|
|
49
|
+
es: () => import("date-fns/locale/es").then(m => m.es),
|
|
50
|
+
et: () => import("date-fns/locale/et").then(m => m.et),
|
|
51
|
+
eu: () => import("date-fns/locale/eu").then(m => m.eu),
|
|
52
|
+
faIR: () => import("date-fns/locale/fa-IR").then(m => m.faIR),
|
|
53
|
+
fi: () => import("date-fns/locale/fi").then(m => m.fi),
|
|
54
|
+
fr: () => import("date-fns/locale/fr").then(m => m.fr),
|
|
55
|
+
frCA: () => import("date-fns/locale/fr-CA").then(m => m.frCA),
|
|
56
|
+
frCH: () => import("date-fns/locale/fr-CH").then(m => m.frCH),
|
|
57
|
+
gd: () => import("date-fns/locale/gd").then(m => m.gd),
|
|
58
|
+
gl: () => import("date-fns/locale/gl").then(m => m.gl),
|
|
59
|
+
gu: () => import("date-fns/locale/gu").then(m => m.gu),
|
|
60
|
+
he: () => import("date-fns/locale/he").then(m => m.he),
|
|
61
|
+
hi: () => import("date-fns/locale/hi").then(m => m.hi),
|
|
62
|
+
hr: () => import("date-fns/locale/hr").then(m => m.hr),
|
|
63
|
+
hu: () => import("date-fns/locale/hu").then(m => m.hu),
|
|
64
|
+
hy: () => import("date-fns/locale/hy").then(m => m.hy),
|
|
65
|
+
id: () => import("date-fns/locale/id").then(m => m.id),
|
|
66
|
+
is: () => import("date-fns/locale/is").then(m => m.is),
|
|
67
|
+
it: () => import("date-fns/locale/it").then(m => m.it),
|
|
68
|
+
ja: () => import("date-fns/locale/ja").then(m => m.ja),
|
|
69
|
+
ka: () => import("date-fns/locale/ka").then(m => m.ka),
|
|
70
|
+
kk: () => import("date-fns/locale/kk").then(m => m.kk),
|
|
71
|
+
kn: () => import("date-fns/locale/kn").then(m => m.kn),
|
|
72
|
+
ko: () => import("date-fns/locale/ko").then(m => m.ko),
|
|
73
|
+
lb: () => import("date-fns/locale/lb").then(m => m.lb),
|
|
74
|
+
lt: () => import("date-fns/locale/lt").then(m => m.lt),
|
|
75
|
+
lv: () => import("date-fns/locale/lv").then(m => m.lv),
|
|
76
|
+
mk: () => import("date-fns/locale/mk").then(m => m.mk),
|
|
77
|
+
ms: () => import("date-fns/locale/ms").then(m => m.ms),
|
|
78
|
+
mt: () => import("date-fns/locale/mt").then(m => m.mt),
|
|
79
|
+
nb: () => import("date-fns/locale/nb").then(m => m.nb),
|
|
80
|
+
nl: () => import("date-fns/locale/nl").then(m => m.nl),
|
|
81
|
+
nlBE: () => import("date-fns/locale/nl-BE").then(m => m.nlBE),
|
|
82
|
+
nn: () => import("date-fns/locale/nn").then(m => m.nn),
|
|
83
|
+
pl: () => import("date-fns/locale/pl").then(m => m.pl),
|
|
84
|
+
pt: () => import("date-fns/locale/pt").then(m => m.pt),
|
|
85
|
+
ptBR: () => import("date-fns/locale/pt-BR").then(m => m.ptBR),
|
|
86
|
+
ro: () => import("date-fns/locale/ro").then(m => m.ro),
|
|
87
|
+
ru: () => import("date-fns/locale/ru").then(m => m.ru),
|
|
88
|
+
sk: () => import("date-fns/locale/sk").then(m => m.sk),
|
|
89
|
+
sl: () => import("date-fns/locale/sl").then(m => m.sl),
|
|
90
|
+
sr: () => import("date-fns/locale/sr").then(m => m.sr),
|
|
91
|
+
srLatn: () => import("date-fns/locale/sr-Latn").then(m => m.srLatn),
|
|
92
|
+
sv: () => import("date-fns/locale/sv").then(m => m.sv),
|
|
93
|
+
ta: () => import("date-fns/locale/ta").then(m => m.ta),
|
|
94
|
+
te: () => import("date-fns/locale/te").then(m => m.te),
|
|
95
|
+
th: () => import("date-fns/locale/th").then(m => m.th),
|
|
96
|
+
tr: () => import("date-fns/locale/tr").then(m => m.tr),
|
|
97
|
+
ug: () => import("date-fns/locale/ug").then(m => m.ug),
|
|
98
|
+
uk: () => import("date-fns/locale/uk").then(m => m.uk),
|
|
99
|
+
uz: () => import("date-fns/locale/uz").then(m => m.uz),
|
|
100
|
+
vi: () => import("date-fns/locale/vi").then(m => m.vi),
|
|
101
|
+
zhCN: () => import("date-fns/locale/zh-CN").then(m => m.zhCN),
|
|
102
|
+
zhTW: () => import("date-fns/locale/zh-TW").then(m => m.zhTW),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const cache = new Map<LocaleKey, DateFnsLocale>();
|
|
106
|
+
|
|
107
|
+
/** The locale if it has already been fetched, otherwise `undefined`. Never fetches. */
|
|
108
|
+
export function getLoadedDateFnsLocale(locale: LocaleKey | undefined): DateFnsLocale | undefined {
|
|
109
|
+
return locale ? cache.get(locale) : undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Fetch a date-fns locale. Resolves to `undefined` for a locale date-fns does
|
|
114
|
+
* not ship, which is what indexing the namespace used to yield.
|
|
115
|
+
*/
|
|
116
|
+
export async function loadDateFnsLocale(locale: LocaleKey | undefined): Promise<DateFnsLocale | undefined> {
|
|
117
|
+
if (!locale) return undefined;
|
|
118
|
+
const cached = cache.get(locale);
|
|
119
|
+
if (cached) return cached;
|
|
120
|
+
const loader = dateFnsLocaleLoaders[locale];
|
|
121
|
+
if (!loader) return undefined;
|
|
122
|
+
const loaded = await loader();
|
|
123
|
+
cache.set(locale, loaded);
|
|
124
|
+
return loaded;
|
|
125
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ArrayProperty, RelationProperty } from "@rebasepro/types";
|
|
2
2
|
import { EntityRelation } from "@rebasepro/types";
|
|
3
3
|
import type { PropertyPreviewProps } from "../../types/components/PropertyPreviewProps";
|
|
4
|
-
import { normalizeToEntityRelation } from "@rebasepro/common";
|
|
4
|
+
import { normalizeToEntityRelation, getRelationTargetPath } from "@rebasepro/common";
|
|
5
5
|
import { RelationPreview } from "../components/RelationPreview";
|
|
6
6
|
import { useIsNestedEntityPreview } from "../../components/EntityPreviewNesting";
|
|
7
7
|
import { InlineEntityListPreview } from "../components/InlineEntityListPreview";
|
|
@@ -27,11 +27,14 @@ export function ArrayOfRelationsPreview({
|
|
|
27
27
|
throw Error("Picked wrong preview component ArrayOfRelationsPreview");
|
|
28
28
|
|
|
29
29
|
const ofProperty = property.of as RelationProperty;
|
|
30
|
+
// Elements arrive hydrated or as bare foreign keys depending on the fetch
|
|
31
|
+
// path; the declared target resolves the latter rather than dropping them.
|
|
32
|
+
const relationTargetPath = getRelationTargetPath(ofProperty);
|
|
30
33
|
|
|
31
34
|
// See ArrayOfReferencesPreview: nested lists stay on one wrapping line.
|
|
32
35
|
if (nested || textOnly) {
|
|
33
36
|
const relations = ((value ?? []) as unknown[])
|
|
34
|
-
.map(relation => normalizeToEntityRelation(relation))
|
|
37
|
+
.map(relation => normalizeToEntityRelation(relation, "relation", relationTargetPath))
|
|
35
38
|
.filter((relation): relation is EntityRelation => Boolean(relation));
|
|
36
39
|
return <InlineEntityListPreview
|
|
37
40
|
items={relations}
|
|
@@ -51,7 +54,7 @@ export function ArrayOfRelationsPreview({
|
|
|
51
54
|
<div className="flex flex-col w-full gap-0.5">
|
|
52
55
|
{value ?
|
|
53
56
|
(value as unknown[]).map((relation: unknown, index: number) => {
|
|
54
|
-
const entityRelation = normalizeToEntityRelation(relation);
|
|
57
|
+
const entityRelation = normalizeToEntityRelation(relation, "relation", relationTargetPath);
|
|
55
58
|
|
|
56
59
|
if (!entityRelation) return null;
|
|
57
60
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import type { UrlController, SidePanelController, NavigateOptions, AdminCollection } from "@rebasepro/admin-types";
|
|
3
|
-
import { withViewMode } from "./view_mode";
|
|
3
|
+
import { withListState, withViewMode } from "./view_mode";
|
|
4
4
|
|
|
5
5
|
// Canonical path utilities — single source of truth in @rebasepro/common
|
|
6
6
|
export { removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, addInitialSlash, getLastSegment, resolveCollectionPathIds, getCollectionBySlugWithin, getCollectionPathsCombinations } from "@rebasepro/app";
|
|
@@ -63,7 +63,9 @@ export function navigateToEntity({
|
|
|
63
63
|
if (entityId && selectedTab) {
|
|
64
64
|
to += `/${selectedTab}`;
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
// The whole list state, not just the view mode: a record opened from a
|
|
67
|
+
// search must come back to that search.
|
|
68
|
+
to = withListState(to);
|
|
67
69
|
if (!entityId) {
|
|
68
70
|
to += "#new";
|
|
69
71
|
}
|
|
@@ -159,8 +159,13 @@ export function getResolvedPropertyInPath(properties: Record<string, Property>,
|
|
|
159
159
|
}
|
|
160
160
|
if (path.includes(".")) {
|
|
161
161
|
const pathSegments = path.split(".");
|
|
162
|
+
// A path whose root is not a property at all is simply not found.
|
|
163
|
+
// Reading `.type` off the miss threw a TypeError instead, which
|
|
164
|
+
// turned one unresolvable column key into a crash for whatever was
|
|
165
|
+
// walking the properties — see `propertiesToColumns`, where that is
|
|
166
|
+
// the whole table. `getPropertyInPath` above already guards this.
|
|
162
167
|
const childProperty = properties[pathSegments[0]];
|
|
163
|
-
if (childProperty
|
|
168
|
+
if (childProperty?.type === "map" && childProperty.properties) {
|
|
164
169
|
return getResolvedPropertyInPath(childProperty.properties, pathSegments.slice(1).join("."))
|
|
165
170
|
}
|
|
166
171
|
}
|
|
@@ -4,3 +4,30 @@
|
|
|
4
4
|
export const MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
|
|
5
5
|
export const JSON_TAB_VALUE = "__json";
|
|
6
6
|
export const HISTORY_TAB_VALUE = "__rebase_history";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The tab row belongs to the header band above the record, not to the record
|
|
10
|
+
* itself, so it carries the same surface the identity bar over it and the form
|
|
11
|
+
* rail beside it carry. Against that, the active tab takes the record's own
|
|
12
|
+
* white and reads as connected to what it opens; on a white row it had to be
|
|
13
|
+
* the tinted one, which is the relationship backwards.
|
|
14
|
+
*/
|
|
15
|
+
export const ENTITY_VIEW_TAB_BAR_CLASS = "bg-surface-50 dark:bg-surface-900";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Every tab in that row, a custom `tabComponent` included.
|
|
19
|
+
*
|
|
20
|
+
* `h-full` over the variant's `h-9`: the row is 40px and the tab 36px, so a
|
|
21
|
+
* centred tab floated ~2px clear of the row's bottom border and the active
|
|
22
|
+
* tab's underline stopped short of the record it opens. Paired with
|
|
23
|
+
* `items-stretch` on the Tabs root, which the variant leaves centred.
|
|
24
|
+
*/
|
|
25
|
+
export const ENTITY_VIEW_TAB_CLASS = "h-full data-[state=active]:bg-white dark:data-[state=active]:bg-surface-800";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Added on top for the tabs this row labels itself — a custom `tabComponent`
|
|
29
|
+
* sizes its own contents. The main tab was the only one rendered without it,
|
|
30
|
+
* and so the only one left at the boxy variant's `text-xs` while its
|
|
31
|
+
* neighbours sat at `text-sm`.
|
|
32
|
+
*/
|
|
33
|
+
export const ENTITY_VIEW_TAB_LABEL_CLASS = "text-sm min-w-[90px]";
|
package/src/util/view_mode.ts
CHANGED
|
@@ -58,6 +58,42 @@ export function withViewMode(url: string, search?: string): string {
|
|
|
58
58
|
return `${base}${separator}${VIEW_MODE_PARAM}=${viewMode}${hash}`;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Carry the whole of the collection list's URL state onto a navigation target.
|
|
63
|
+
*
|
|
64
|
+
* `withViewMode` exists because losing the view mode mid-session is jarring.
|
|
65
|
+
* Everything else the list writes to the URL — the search string, the filters,
|
|
66
|
+
* the sort — is state the user built up deliberately, and losing *that* is
|
|
67
|
+
* worse: opening a record from a search and pressing back returned an
|
|
68
|
+
* unfiltered list with an empty search box, so the search had to be retyped
|
|
69
|
+
* for every record they wanted to look at.
|
|
70
|
+
*
|
|
71
|
+
* Copies every param the list currently has rather than an allowlist, because
|
|
72
|
+
* filters are encoded under the field's own name — there is no fixed set to
|
|
73
|
+
* list. Params already on the target win: the caller is more specific.
|
|
74
|
+
*
|
|
75
|
+
* @param url the URL to decorate. May carry a query string and/or a hash.
|
|
76
|
+
* @param search query string to read from. Defaults to the browser location.
|
|
77
|
+
*/
|
|
78
|
+
export function withListState(url: string, search?: string): string {
|
|
79
|
+
const source = search ?? (typeof window !== "undefined" ? window.location.search : "");
|
|
80
|
+
const carried = new URLSearchParams(source);
|
|
81
|
+
if ([...carried].length === 0) return url;
|
|
82
|
+
|
|
83
|
+
const hashIndex = url.indexOf("#");
|
|
84
|
+
const hash = hashIndex >= 0 ? url.substring(hashIndex) : "";
|
|
85
|
+
const base = hashIndex >= 0 ? url.substring(0, hashIndex) : url;
|
|
86
|
+
|
|
87
|
+
const [pathname, existing] = base.split("?");
|
|
88
|
+
const target = new URLSearchParams(existing ?? "");
|
|
89
|
+
for (const [key, value] of carried) {
|
|
90
|
+
if (!target.has(key)) target.set(key, value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const query = target.toString();
|
|
94
|
+
return query ? `${pathname}?${query}${hash}` : `${pathname}${hash}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
61
97
|
/**
|
|
62
98
|
* Resolve the view mode for a collection, honouring the documented priority:
|
|
63
99
|
* URL param > saved user config > collection default.
|