@rebasepro/app 0.13.0 → 0.13.1-canary.g249daa1
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/collections/entity-display-cache.d.ts +75 -0
- package/dist/collections/entity-display.d.ts +35 -0
- package/dist/collections/entity_image_preview.d.ts +8 -0
- package/dist/collections/form-layout.d.ts +6 -0
- package/dist/collections/index.d.ts +4 -0
- package/dist/collections/property-path.d.ts +16 -0
- package/dist/collections/property_presentation.d.ts +18 -3
- package/dist/collections/summary-property.d.ts +65 -0
- package/dist/components/common/useColumnsIds.d.ts +22 -0
- package/dist/core/Rebase.d.ts +1 -1
- package/dist/core/RebaseProps.d.ts +10 -2
- package/dist/hooks/data/collectionQuery.d.ts +45 -0
- package/dist/index.es.js +686 -217
- package/dist/index.es.js.map +1 -1
- package/dist/util/entity_cache.d.ts +19 -3
- package/dist/util/previews.d.ts +19 -0
- package/package.json +7 -7
- package/src/auth/useRebaseAuthController.ts +9 -1
- package/src/collections/entity-display-cache.ts +195 -0
- package/src/collections/entity-display.ts +105 -0
- package/src/collections/entity_image_preview.ts +13 -0
- package/src/collections/form-layout.ts +8 -1
- package/src/collections/index.ts +4 -0
- package/src/collections/property-path.ts +30 -0
- package/src/collections/property_presentation.ts +39 -4
- package/src/collections/summary-property.ts +114 -0
- package/src/collections/title-property.ts +13 -16
- package/src/components/common/useColumnsIds.tsx +67 -29
- package/src/components/common/useDataTableController.tsx +10 -14
- package/src/core/Rebase.tsx +20 -2
- package/src/core/RebaseProps.tsx +10 -2
- package/src/hooks/data/collectionQuery.ts +55 -0
- package/src/hooks/data/useCollection.tsx +29 -21
- package/src/hooks/data/useFetch.tsx +17 -2
- package/src/hooks/data/useRelationSelector.tsx +20 -4
- package/src/hooks/useAuthSubscription.ts +18 -2
- package/src/hooks/useBuildLocalConfigurationPersistence.tsx +20 -12
- package/src/locales/de.ts +1 -0
- package/src/locales/en.ts +2 -0
- package/src/locales/es.ts +1 -0
- package/src/locales/fr.ts +1 -0
- package/src/locales/hi.ts +2 -1
- package/src/locales/it.ts +1 -0
- package/src/locales/pt.ts +1 -0
- package/src/util/entity_cache.ts +34 -31
- package/src/util/previews.ts +49 -29
package/dist/index.es.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import React, { createContext, lazy, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Alert, AlertCircleIcon, AlertTriangleIcon, AppWindow, ArrowDownIcon, ArrowLeftIcon, ArrowRightLeftIcon, ArrowUpDownIcon, ArrowUpIcon, Avatar, BooleanSwitch, Button, CalendarIcon, Card, CenteredView, CheckCircle2Icon, CheckIcon, Checkbox, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, Chip, CircleDotIcon, CircleUserIcon, CircularProgress, ColumnsIcon, Container, Dialog, DialogActions, DialogContent, DialogTitle, DollarSignIcon, ErrorBoundary, FileIcon, FileTextIcon, FilterChip, FilterIcon, FolderIcon, FolderKanbanIcon, IconButton, KanbanIcon, KanbanView, LanguagesIcon, LayoutGridIcon, ListIcon, ListPlusIcon, ListTodoIcon, LoadingButton, LogOutIcon, MailIcon, Menu, MenuItem, MessageCircleIcon, MoonIcon, MultiSelect, MultiSelectItem, PanelLeftIcon, Paper, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlusIcon, Popover, RefreshCwIcon, SearchBar, Select, SelectItem, Separator, SettingsIcon, Skeleton, StickyNoteIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableRow, Tabs, TagIcon, TextField, ToggleButtonGroup, Tooltip, Trash2Icon, TrendingUpIcon, TypeIcon, Typography, UserIcon, UserPlus, UsersIcon, VideoIcon, Wand2Icon, WrenchIcon, XIcon, cls, colorClassesMapping, coolIconKeys, defaultBorderMixin, getColorSchemeForKey, getColorSchemeForSeed, iconKeys, iconSize, lucideIcons } from "@rebasepro/ui";
|
|
3
3
|
import { ALL_WHERE_FILTER_OPS, DEFAULT_DATA_SOURCE_KEY, DEFAULT_FILTERABLE_RELATION_KINDS, DEFAULT_STORAGE_SOURCE_KEY, EntityReference, EntityRelation, GeoPoint, RebaseApiError, Vector, getDataSourceCapabilities, isLazyComponentRef, isRelationalCollectionConfig } from "@rebasepro/types";
|
|
4
|
+
import { UNRENDERED_SLOTS, resolveAdminCollection } from "@rebasepro/admin-types";
|
|
4
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
import { SnackbarProvider as SnackbarProvider$1, useSnackbar } from "notistack";
|
|
6
|
-
import { buildRebaseData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, evaluateCondition, getEntityChildViews, getLabelOrConfigFrom, getPrimaryKeys, getSubcollections, isPropertyBuilder, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, stripCollectionPath, wrapAsEntityData, wrapAsSdkData } from "@rebasepro/common";
|
|
7
|
+
import { buildRebaseData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, evaluateCondition, getChildViewDeclaringProperties, getChildViewRelationPropertyKeys, getEntityChildViews, getLabelOrConfigFrom, getPrimaryKeys, getSubcollections, isPropertyBuilder, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, stripCollectionPath, wrapAsEntityData, wrapAsSdkData } from "@rebasepro/common";
|
|
7
8
|
import { getTitlePropertyKey as getTitlePropertyKey$1, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1 } from "@rebasepro/app";
|
|
8
9
|
import { Link, Routes, createBrowserRouter, useBlocker, useLocation } from "react-router";
|
|
9
|
-
import { generateForeignKeyName, hashString, isObject, isPlainObject, mergeDeep, randomString, slugify } from "@rebasepro/utils";
|
|
10
|
+
import { generateForeignKeyName, hashString, isArrayValue, isObject, isPlainObject, isRecordValue, mergeDeep, randomString, readStoredJson, slugify, writeStoredJson } from "@rebasepro/utils";
|
|
10
11
|
import { I18nextProvider, initReactI18next, useTranslation as useTranslation$1 } from "react-i18next";
|
|
11
12
|
import { deepEqual } from "fast-equals";
|
|
12
13
|
import i18next from "i18next";
|
|
13
14
|
import { RouterProvider } from "react-router/dom";
|
|
14
15
|
import Fuse from "fuse.js";
|
|
15
16
|
import Compressor from "compressorjs";
|
|
16
|
-
import { resolveAdminCollection } from "@rebasepro/admin-types";
|
|
17
17
|
//#region src/core/PluginProviderStack.tsx
|
|
18
18
|
/**
|
|
19
19
|
* Wraps children with all provider components from plugins that match the given scope.
|
|
@@ -414,6 +414,116 @@ function SchemaDriftBanner({ className }) {
|
|
|
414
414
|
});
|
|
415
415
|
}
|
|
416
416
|
//#endregion
|
|
417
|
+
//#region src/hooks/data/collectionQuery.ts
|
|
418
|
+
/**
|
|
419
|
+
* Build the parameters for one collection read.
|
|
420
|
+
*
|
|
421
|
+
* Derived options live here rather than at the call sites, so they cannot
|
|
422
|
+
* disagree: `searchExplain` rides along exactly when there is a search string
|
|
423
|
+
* to explain, which is also the only time it costs anything.
|
|
424
|
+
*/
|
|
425
|
+
function toFindParams(input) {
|
|
426
|
+
const { searchString, ...rest } = input;
|
|
427
|
+
return {
|
|
428
|
+
...rest,
|
|
429
|
+
...searchString ? {
|
|
430
|
+
searchString,
|
|
431
|
+
searchExplain: true
|
|
432
|
+
} : {}
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
//#endregion
|
|
436
|
+
//#region src/collections/summary-property.ts
|
|
437
|
+
/**
|
|
438
|
+
* Whether a property can stand in for a record in *one line*.
|
|
439
|
+
*
|
|
440
|
+
* Preview surfaces — the reference card, a list row, a board card — have a
|
|
441
|
+
* single line per property and no way to grow. That is a constraint on the
|
|
442
|
+
* *value*, not on the property's importance: a Markdown biography is the most
|
|
443
|
+
* interesting column on an author, and it is still the wrong thing to paste
|
|
444
|
+
* into a 44px card. Ranking it here means the picker can skip it without
|
|
445
|
+
* anyone having to configure their way out of a broken card.
|
|
446
|
+
*
|
|
447
|
+
* Kept beside {@link getTitlePropertyCandidates} because the two answer
|
|
448
|
+
* neighbouring questions — "what is this record called" and "what else can be
|
|
449
|
+
* said about it in a line" — from the same property schema.
|
|
450
|
+
*
|
|
451
|
+
* @group Collections
|
|
452
|
+
*/
|
|
453
|
+
var SUMMARY_RANK = {
|
|
454
|
+
/**
|
|
455
|
+
* No single-line form exists. A map renders as a key/value table and an
|
|
456
|
+
* array of maps as a stack of them; there is no first line to take.
|
|
457
|
+
*/
|
|
458
|
+
UNUSABLE: 0,
|
|
459
|
+
/**
|
|
460
|
+
* Readable in one line, but only as an excerpt — the property holds a
|
|
461
|
+
* document, and the line is its opening. Worth showing when nothing better
|
|
462
|
+
* is available, never in preference to a value that fits whole.
|
|
463
|
+
*/
|
|
464
|
+
EXCERPT: 1,
|
|
465
|
+
/** Short and self-contained: the line *is* the value. */
|
|
466
|
+
DIRECT: 2
|
|
467
|
+
};
|
|
468
|
+
/**
|
|
469
|
+
* File-storage backed content (single image, array of images, generic upload…).
|
|
470
|
+
* Preview surfaces give these their own image slot, so they are never also a
|
|
471
|
+
* text line.
|
|
472
|
+
*
|
|
473
|
+
* @group Collections
|
|
474
|
+
*/
|
|
475
|
+
function isStorageProperty(property) {
|
|
476
|
+
if (!property) return false;
|
|
477
|
+
if (property.type === "string" && (property.storage || property.admin?.urlPreview === "image")) return true;
|
|
478
|
+
if (property.type === "array" && property.of && !Array.isArray(property.of)) {
|
|
479
|
+
const inner = property.of;
|
|
480
|
+
if (inner.type === "string" && (inner.storage || inner.admin?.urlPreview === "image")) return true;
|
|
481
|
+
}
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* True when the property holds free text long enough to be a document rather
|
|
486
|
+
* than a value — Markdown, or an explicitly multi-line string.
|
|
487
|
+
*
|
|
488
|
+
* @group Collections
|
|
489
|
+
*/
|
|
490
|
+
function isLongTextProperty(property) {
|
|
491
|
+
if (!property || property.type !== "string") return false;
|
|
492
|
+
return Boolean(property.admin?.markdown || property.admin?.multiline);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* How well a property reads as one line of a preview. See {@link SUMMARY_RANK}.
|
|
496
|
+
*
|
|
497
|
+
* Decided from the property *schema* only: no values are consulted, so the
|
|
498
|
+
* answer is stable for a collection and can be memoised per collection rather
|
|
499
|
+
* than per row.
|
|
500
|
+
*
|
|
501
|
+
* @group Collections
|
|
502
|
+
*/
|
|
503
|
+
function rankSummaryProperty(property) {
|
|
504
|
+
if (!property) return SUMMARY_RANK.UNUSABLE;
|
|
505
|
+
if (isStorageProperty(property)) return SUMMARY_RANK.UNUSABLE;
|
|
506
|
+
if (property.type === "map") return SUMMARY_RANK.UNUSABLE;
|
|
507
|
+
if (property.type === "array") {
|
|
508
|
+
if (Array.isArray(property.of)) return SUMMARY_RANK.UNUSABLE;
|
|
509
|
+
if (!property.of) return property.oneOf ? SUMMARY_RANK.UNUSABLE : SUMMARY_RANK.DIRECT;
|
|
510
|
+
const innerRank = rankSummaryProperty(property.of);
|
|
511
|
+
if (innerRank === SUMMARY_RANK.UNUSABLE) return SUMMARY_RANK.UNUSABLE;
|
|
512
|
+
if (innerRank === SUMMARY_RANK.EXCERPT) return SUMMARY_RANK.UNUSABLE;
|
|
513
|
+
return SUMMARY_RANK.DIRECT;
|
|
514
|
+
}
|
|
515
|
+
if (isLongTextProperty(property)) return SUMMARY_RANK.EXCERPT;
|
|
516
|
+
return SUMMARY_RANK.DIRECT;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* True when the property has any single-line form at all.
|
|
520
|
+
*
|
|
521
|
+
* @group Collections
|
|
522
|
+
*/
|
|
523
|
+
function canSummariseProperty(property) {
|
|
524
|
+
return rankSummaryProperty(property) !== SUMMARY_RANK.UNUSABLE;
|
|
525
|
+
}
|
|
526
|
+
//#endregion
|
|
417
527
|
//#region src/util/previews.ts
|
|
418
528
|
function isReferenceProperty(property) {
|
|
419
529
|
if (!property) return null;
|
|
@@ -434,35 +544,47 @@ function isHiddenProperty(property) {
|
|
|
434
544
|
return Boolean(property.admin?.hideFromCollection);
|
|
435
545
|
}
|
|
436
546
|
/**
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
547
|
+
* The properties a preview surface should render for a record, best first.
|
|
548
|
+
*
|
|
549
|
+
* Three things decide the answer, in this order:
|
|
550
|
+
*
|
|
551
|
+
* 1. `previewProperties` — passed in, or declared on the collection. A stated
|
|
552
|
+
* list is returned verbatim, ranking and limit included: a developer who
|
|
553
|
+
* asks for the Markdown biography gets the Markdown biography.
|
|
554
|
+
* 2. Whether the value has a one-line form at all. A map renders as a
|
|
555
|
+
* key/value table and a Markdown field as a document; neither fits a card
|
|
556
|
+
* line, so they never take a slot from a value that does. See
|
|
557
|
+
* {@link rankSummaryProperty}.
|
|
558
|
+
* 3. `propertiesOrder`, which breaks ties.
|
|
559
|
+
*
|
|
560
|
+
* The middle step is the one that is easy to get wrong by leaving out.
|
|
561
|
+
* `propertiesOrder` states the *column* order of a collection table — it is
|
|
562
|
+
* not a statement that the first three columns summarise a record, and reading
|
|
563
|
+
* it as one is how a card ends up rendering somebody's entire biography.
|
|
440
564
|
*/
|
|
441
|
-
function isStorageProperty$1(property) {
|
|
442
|
-
if (!property) return false;
|
|
443
|
-
if (property.type === "string" && property.storage) return true;
|
|
444
|
-
if (property.type === "string" && property.admin?.urlPreview === "image") return true;
|
|
445
|
-
if (property.type === "array" && property.of && !Array.isArray(property.of)) {
|
|
446
|
-
const inner = property.of;
|
|
447
|
-
if (inner.type === "string" && (inner.storage || inner.admin?.urlPreview === "image")) return true;
|
|
448
|
-
}
|
|
449
|
-
return false;
|
|
450
|
-
}
|
|
451
565
|
function getEntityPreviewKeys(authController, targetCollection, fields, previewProperties, limit = 3) {
|
|
452
566
|
const allProperties = Object.keys(targetCollection.properties);
|
|
453
567
|
let listProperties = previewProperties?.filter((p) => allProperties.includes(p));
|
|
454
568
|
if (!listProperties && targetCollection.previewProperties) listProperties = targetCollection.previewProperties?.filter((p) => allProperties.includes(p));
|
|
455
569
|
if (listProperties && listProperties.length > 0) return listProperties;
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
570
|
+
const hasExplicitOrder = !!targetCollection.propertiesOrder;
|
|
571
|
+
const order = targetCollection.propertiesOrder || allProperties;
|
|
572
|
+
const direct = [];
|
|
573
|
+
const excerpt = [];
|
|
574
|
+
for (const key of order) {
|
|
575
|
+
const property = targetCollection.properties[key];
|
|
576
|
+
if (!property || isPropertyBuilder(property)) continue;
|
|
577
|
+
if (key === "id" || "isId" in property && Boolean(property.isId)) continue;
|
|
578
|
+
if (isReferenceProperty(property)) continue;
|
|
579
|
+
if (!hasExplicitOrder && isRelationProperty(property)) continue;
|
|
580
|
+
if (isHiddenProperty(property)) continue;
|
|
581
|
+
if (isStorageProperty(property)) continue;
|
|
582
|
+
const rank = rankSummaryProperty(property);
|
|
583
|
+
if (rank === SUMMARY_RANK.UNUSABLE) continue;
|
|
584
|
+
(rank === SUMMARY_RANK.EXCERPT ? excerpt : direct).push(key);
|
|
585
|
+
if (direct.length >= limit) break;
|
|
465
586
|
}
|
|
587
|
+
return [...direct, ...excerpt].slice(0, limit);
|
|
466
588
|
}
|
|
467
589
|
var INCLUDE_ALL_RELATIONS = ["*"];
|
|
468
590
|
/**
|
|
@@ -531,7 +653,7 @@ function useCollection({ path, collection, filterValues, sortBy, itemCount, offs
|
|
|
531
653
|
};
|
|
532
654
|
const accessor = dataClient.collection(path);
|
|
533
655
|
const includeParams = getRelationIncludeParams(collection);
|
|
534
|
-
if (accessor.listen) return accessor.listen({
|
|
656
|
+
if (accessor.listen) return accessor.listen(toFindParams({
|
|
535
657
|
where: whereParams,
|
|
536
658
|
limit: itemCount,
|
|
537
659
|
offset,
|
|
@@ -539,12 +661,13 @@ function useCollection({ path, collection, filterValues, sortBy, itemCount, offs
|
|
|
539
661
|
orderBy: orderByParams,
|
|
540
662
|
searchString,
|
|
541
663
|
include: includeParams
|
|
542
|
-
}, (res) => onEntitiesUpdate({
|
|
664
|
+
}), (res) => onEntitiesUpdate({
|
|
543
665
|
data: res.data,
|
|
544
666
|
meta: res.meta
|
|
545
667
|
}), onError);
|
|
546
668
|
else {
|
|
547
|
-
|
|
669
|
+
let cancelled = false;
|
|
670
|
+
accessor.find(toFindParams({
|
|
548
671
|
where: whereParams,
|
|
549
672
|
limit: itemCount,
|
|
550
673
|
offset,
|
|
@@ -552,11 +675,19 @@ function useCollection({ path, collection, filterValues, sortBy, itemCount, offs
|
|
|
552
675
|
orderBy: orderByParams,
|
|
553
676
|
searchString,
|
|
554
677
|
include: includeParams
|
|
555
|
-
}).then((res) =>
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
678
|
+
})).then((res) => {
|
|
679
|
+
if (cancelled) return;
|
|
680
|
+
onEntitiesUpdate({
|
|
681
|
+
data: res.data,
|
|
682
|
+
meta: res.meta
|
|
683
|
+
});
|
|
684
|
+
}).catch((e) => {
|
|
685
|
+
if (cancelled) return;
|
|
686
|
+
onError(e);
|
|
687
|
+
});
|
|
688
|
+
return () => {
|
|
689
|
+
cancelled = true;
|
|
690
|
+
};
|
|
560
691
|
}
|
|
561
692
|
}, [
|
|
562
693
|
path,
|
|
@@ -836,8 +967,17 @@ function useFetch({ path, entityId, collection, databaseId, useCache = false })
|
|
|
836
967
|
const accessor = dataClient.collection(path);
|
|
837
968
|
if (accessor.listenById) return accessor.listenById(entityId, (entity) => onEntityUpdate(entity), onError);
|
|
838
969
|
else {
|
|
839
|
-
|
|
840
|
-
|
|
970
|
+
let cancelled = false;
|
|
971
|
+
accessor.findById(entityId).then((entity) => {
|
|
972
|
+
if (cancelled) return;
|
|
973
|
+
onEntityUpdate(entity);
|
|
974
|
+
}).catch((e) => {
|
|
975
|
+
if (cancelled) return;
|
|
976
|
+
onError(e);
|
|
977
|
+
});
|
|
978
|
+
return () => {
|
|
979
|
+
cancelled = true;
|
|
980
|
+
};
|
|
841
981
|
}
|
|
842
982
|
} else {
|
|
843
983
|
onEntityUpdate(void 0);
|
|
@@ -948,6 +1088,7 @@ function useRelationSelector({ path, collection, fixedFilter, pageSize = DEFAULT
|
|
|
948
1088
|
meta: res.meta
|
|
949
1089
|
}), onErrorUpdate);
|
|
950
1090
|
else {
|
|
1091
|
+
let cancelled = false;
|
|
951
1092
|
accessor.find({
|
|
952
1093
|
where: whereParams,
|
|
953
1094
|
limit,
|
|
@@ -955,11 +1096,19 @@ function useRelationSelector({ path, collection, fixedFilter, pageSize = DEFAULT
|
|
|
955
1096
|
orderBy: void 0,
|
|
956
1097
|
searchString: currentSearch,
|
|
957
1098
|
include: includeParams
|
|
958
|
-
}).then((res) =>
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1099
|
+
}).then((res) => {
|
|
1100
|
+
if (cancelled) return;
|
|
1101
|
+
onEntitiesUpdate({
|
|
1102
|
+
data: res.data,
|
|
1103
|
+
meta: res.meta
|
|
1104
|
+
});
|
|
1105
|
+
}).catch((e) => {
|
|
1106
|
+
if (cancelled) return;
|
|
1107
|
+
onErrorUpdate(e);
|
|
1108
|
+
});
|
|
1109
|
+
unsubscribe = () => {
|
|
1110
|
+
cancelled = true;
|
|
1111
|
+
};
|
|
963
1112
|
}
|
|
964
1113
|
unsubscribeRef.current = unsubscribe || null;
|
|
965
1114
|
}, [
|
|
@@ -1254,7 +1403,15 @@ function useAuthSubscription(authClient) {
|
|
|
1254
1403
|
setInitialLoading(true);
|
|
1255
1404
|
authClient.getUser().then((user) => {
|
|
1256
1405
|
if (user) setUser(user);
|
|
1257
|
-
|
|
1406
|
+
setAuthError(void 0);
|
|
1407
|
+
}).catch((e) => {
|
|
1408
|
+
const status = e?.status;
|
|
1409
|
+
if (status === 401 || status === 403) {
|
|
1410
|
+
setAuthError(void 0);
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
setAuthError(e instanceof Error ? e : new Error(String(e)));
|
|
1414
|
+
}).finally(() => {
|
|
1258
1415
|
setInitialLoading(false);
|
|
1259
1416
|
});
|
|
1260
1417
|
}
|
|
@@ -1630,8 +1787,10 @@ function useSlot(slot, props) {
|
|
|
1630
1787
|
function useBuildLocalConfigurationPersistence() {
|
|
1631
1788
|
const configCache = useRef({});
|
|
1632
1789
|
const getCollectionFromStorage = useCallback((storageKey) => {
|
|
1633
|
-
|
|
1634
|
-
|
|
1790
|
+
return readStoredJson(storageKey, {
|
|
1791
|
+
fallback: {},
|
|
1792
|
+
accept: isRecordValue
|
|
1793
|
+
});
|
|
1635
1794
|
}, []);
|
|
1636
1795
|
const getCollectionConfig = useCallback((path) => {
|
|
1637
1796
|
const storageKey = `collection_config::${stripCollectionPath(path)}`;
|
|
@@ -1640,29 +1799,33 @@ function useBuildLocalConfigurationPersistence() {
|
|
|
1640
1799
|
}, [getCollectionFromStorage]);
|
|
1641
1800
|
const onCollectionModified = useCallback((path, data) => {
|
|
1642
1801
|
const storageKey = `collection_config::${stripCollectionPath(path)}`;
|
|
1643
|
-
|
|
1802
|
+
writeStoredJson(storageKey, data);
|
|
1644
1803
|
const cachedConfig = configCache.current[storageKey];
|
|
1645
|
-
const newConfig = mergeDeep(cachedConfig ?? getCollectionFromStorage(
|
|
1804
|
+
const newConfig = mergeDeep(cachedConfig ?? getCollectionFromStorage(storageKey), data);
|
|
1646
1805
|
configCache.current[storageKey] = mergeDeep(configCache.current[storageKey], newConfig);
|
|
1647
1806
|
}, [getCollectionFromStorage]);
|
|
1648
1807
|
const [recentlyVisitedPaths, _setRecentlyVisitedPaths] = useState([]);
|
|
1649
1808
|
const [favouritePaths, _setFavouritePaths] = useState([]);
|
|
1650
1809
|
const [collapsedGroups, _setCollapsedGroups] = useState([]);
|
|
1651
1810
|
useEffect(() => {
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1811
|
+
const readPaths = (key) => readStoredJson(key, {
|
|
1812
|
+
fallback: [],
|
|
1813
|
+
accept: isArrayValue
|
|
1814
|
+
});
|
|
1815
|
+
_setRecentlyVisitedPaths(readPaths("recently_visited_paths"));
|
|
1816
|
+
_setFavouritePaths(readPaths("favourite_paths"));
|
|
1817
|
+
_setCollapsedGroups(readPaths("collapsed_groups"));
|
|
1655
1818
|
}, []);
|
|
1656
1819
|
const setRecentlyVisitedPaths = useCallback((paths) => {
|
|
1657
|
-
|
|
1820
|
+
writeStoredJson("recently_visited_paths", paths);
|
|
1658
1821
|
_setRecentlyVisitedPaths(paths);
|
|
1659
1822
|
}, []);
|
|
1660
1823
|
const setFavouritePaths = useCallback((paths) => {
|
|
1661
|
-
|
|
1824
|
+
writeStoredJson("favourite_paths", paths);
|
|
1662
1825
|
_setFavouritePaths(paths);
|
|
1663
1826
|
}, []);
|
|
1664
1827
|
const setCollapsedGroups = useCallback((paths) => {
|
|
1665
|
-
|
|
1828
|
+
writeStoredJson("collapsed_groups", paths);
|
|
1666
1829
|
_setCollapsedGroups(paths);
|
|
1667
1830
|
}, []);
|
|
1668
1831
|
return useMemo(() => ({
|
|
@@ -2325,24 +2488,202 @@ function useDebouncedData(data, deps, timeoutMs = 5e3) {
|
|
|
2325
2488
|
return immediateUpdate ? data : deferredData;
|
|
2326
2489
|
}
|
|
2327
2490
|
//#endregion
|
|
2328
|
-
//#region src/
|
|
2491
|
+
//#region src/collections/property-path.ts
|
|
2329
2492
|
/**
|
|
2330
|
-
*
|
|
2331
|
-
*
|
|
2493
|
+
* The property at a dotted path, walking `map` children — `address.street`.
|
|
2494
|
+
*
|
|
2495
|
+
* The value counterpart is `getValueInPath` in `@rebasepro/utils`; this is the
|
|
2496
|
+
* schema half, and the two have to be used together. Reading a dotted path off
|
|
2497
|
+
* an entity while looking its property up with a flat `properties[path]` gives
|
|
2498
|
+
* the value and `undefined` for how to render it, which is how a declared title
|
|
2499
|
+
* on a nested field silently fell back to a derived one.
|
|
2500
|
+
*
|
|
2501
|
+
* There were three copies of this: one private to `useColumnsIds`, one exported
|
|
2502
|
+
* from the admin layer, and the flat lookup in the title resolver that was not
|
|
2503
|
+
* this function at all. This is the one, in the lowest layer that needs it —
|
|
2504
|
+
* admin re-exports it under the name it already published.
|
|
2332
2505
|
*/
|
|
2333
2506
|
function getPropertyInPath(properties, path) {
|
|
2334
|
-
if (typeof properties
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2507
|
+
if (typeof properties !== "object" || !properties) return void 0;
|
|
2508
|
+
if (path in properties) return properties[path];
|
|
2509
|
+
if (path.includes(".")) {
|
|
2510
|
+
const pathSegments = path.split(".");
|
|
2511
|
+
const childProperty = properties[pathSegments[0]];
|
|
2512
|
+
if (typeof childProperty === "object" && childProperty?.type === "map" && childProperty.properties) return getPropertyInPath(childProperty.properties, pathSegments.slice(1).join("."));
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
//#endregion
|
|
2516
|
+
//#region src/collections/property_presentation.ts
|
|
2517
|
+
/**
|
|
2518
|
+
* A condition stated as a literal rather than as a rule.
|
|
2519
|
+
*
|
|
2520
|
+
* `PropertyConditions` accepts either, and the two are answered in different
|
|
2521
|
+
* places: a rule needs a context and so can only be evaluated while rendering a
|
|
2522
|
+
* particular entity, but a literal is already the answer. Reading it here is
|
|
2523
|
+
* what makes `hidden: true` work without every gate below having to build a
|
|
2524
|
+
* condition context first — and without the caller reaching for
|
|
2525
|
+
* `{ "==": [1, 1] }` to say something it can say with a boolean.
|
|
2526
|
+
*/
|
|
2527
|
+
function literalCondition(condition) {
|
|
2528
|
+
return condition === true;
|
|
2529
|
+
}
|
|
2530
|
+
function isReadOnly(property) {
|
|
2531
|
+
if (property.admin?.readOnly) return true;
|
|
2532
|
+
if (literalCondition(property.conditions?.readOnly)) return true;
|
|
2533
|
+
if (property.type === "date") {
|
|
2534
|
+
if (property.autoValue) return true;
|
|
2535
|
+
}
|
|
2536
|
+
if (property.type === "reference") return !property.path && !("Field" in (property.admin || {}) && property.admin?.Field);
|
|
2537
|
+
return false;
|
|
2538
|
+
}
|
|
2539
|
+
function isHidden(property) {
|
|
2540
|
+
if (literalCondition(property.conditions?.hidden)) return true;
|
|
2541
|
+
return typeof property.admin?.disabled === "object" && Boolean(property.admin?.disabled.hidden);
|
|
2542
|
+
}
|
|
2543
|
+
/**
|
|
2544
|
+
* Whether the field is disabled by its own declaration, ignoring form state.
|
|
2545
|
+
*
|
|
2546
|
+
* The `admin.disabled` block and `conditions.disabled: true` say the same thing
|
|
2547
|
+
* two ways, so every caller that gated on the first now asks here instead of
|
|
2548
|
+
* growing a second check of its own.
|
|
2549
|
+
*/
|
|
2550
|
+
function isDisabled(property) {
|
|
2551
|
+
return Boolean(property.admin?.disabled) || literalCondition(property.conditions?.disabled);
|
|
2552
|
+
}
|
|
2553
|
+
function applyPropertyConditions(property, context) {
|
|
2554
|
+
const { conditions } = property;
|
|
2555
|
+
if (!conditions) return property;
|
|
2556
|
+
const result = { ...property };
|
|
2557
|
+
if (conditions.disabled) {
|
|
2558
|
+
if (evaluateCondition(conditions.disabled, context)) {
|
|
2559
|
+
result.admin = result.admin || {};
|
|
2560
|
+
result.admin.disabled = {
|
|
2561
|
+
clearOnDisabled: conditions.clearOnDisabled ?? false,
|
|
2562
|
+
disabledMessage: conditions.disabledMessage,
|
|
2563
|
+
hidden: false
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
if (conditions.hidden) {
|
|
2568
|
+
if (evaluateCondition(conditions.hidden, context)) {
|
|
2569
|
+
result.admin = result.admin || {};
|
|
2570
|
+
result.admin.disabled = {
|
|
2571
|
+
...typeof result.admin?.disabled === "object" ? result.admin.disabled : {},
|
|
2572
|
+
hidden: true,
|
|
2573
|
+
clearOnDisabled: conditions.clearOnDisabled ?? false
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
if (conditions.readOnly) {
|
|
2578
|
+
if (evaluateCondition(conditions.readOnly, context)) {
|
|
2579
|
+
result.admin = result.admin || {};
|
|
2580
|
+
result.admin.readOnly = true;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
if (conditions.required !== void 0) {
|
|
2584
|
+
const isRequired = evaluateCondition(conditions.required, context);
|
|
2585
|
+
result.validation = {
|
|
2586
|
+
...result.validation,
|
|
2587
|
+
required: isRequired,
|
|
2588
|
+
requiredMessage: conditions.requiredMessage
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
if (context.isNew && conditions.defaultValue !== void 0) result.defaultValue = evaluateCondition(conditions.defaultValue, context);
|
|
2592
|
+
if ("enum" in result && result.enum && (conditions.enumConditions || conditions.allowedEnumValues || conditions.excludedEnumValues)) result.enum = applyEnumConditions(result.enum, conditions, context);
|
|
2593
|
+
if (result.type === "reference") {
|
|
2594
|
+
if (conditions.referencePath) result.path = evaluateCondition(conditions.referencePath, context);
|
|
2595
|
+
if (conditions.referenceFilter) {
|
|
2596
|
+
result.admin = result.admin || {};
|
|
2597
|
+
result.admin.fixedFilter = evaluateCondition(conditions.referenceFilter, context);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
if (result.type === "array") {
|
|
2601
|
+
if (conditions.canAddElements !== void 0) {
|
|
2602
|
+
result.admin = result.admin || {};
|
|
2603
|
+
result.admin.canAddElements = evaluateCondition(conditions.canAddElements, context);
|
|
2604
|
+
}
|
|
2605
|
+
if (conditions.sortable !== void 0) {
|
|
2606
|
+
result.admin = result.admin || {};
|
|
2607
|
+
result.admin.sortable = evaluateCondition(conditions.sortable, context);
|
|
2340
2608
|
}
|
|
2341
2609
|
}
|
|
2610
|
+
return result;
|
|
2611
|
+
}
|
|
2612
|
+
/**
|
|
2613
|
+
* Convert an object with numeric keys back to an array.
|
|
2614
|
+
* Firestore stores arrays as {"0": "a", "1": "b"} to avoid nested arrays.
|
|
2615
|
+
*/
|
|
2616
|
+
function objectToArray(obj) {
|
|
2617
|
+
if (Array.isArray(obj)) return obj.map(String);
|
|
2618
|
+
if (obj && typeof obj === "object") {
|
|
2619
|
+
const keys = Object.keys(obj);
|
|
2620
|
+
if (keys.length > 0 && keys.every((k) => !isNaN(Number(k)))) return keys.sort((a, b) => Number(a) - Number(b)).map((k) => obj[k]).filter((v) => typeof v === "string" || typeof v === "number").map(String);
|
|
2621
|
+
}
|
|
2622
|
+
return [];
|
|
2623
|
+
}
|
|
2624
|
+
/**
|
|
2625
|
+
* Apply enum-specific conditions to filter and modify enum values.
|
|
2626
|
+
*/
|
|
2627
|
+
function applyEnumConditions(enumValues, conditions, context) {
|
|
2628
|
+
let result = [...enumValues];
|
|
2629
|
+
if (conditions.allowedEnumValues) {
|
|
2630
|
+
const allowedArray = objectToArray(evaluateCondition(conditions.allowedEnumValues, context));
|
|
2631
|
+
if (allowedArray.length > 0) result = result.filter((ev) => allowedArray.includes(String(ev.id)));
|
|
2632
|
+
}
|
|
2633
|
+
if (conditions.excludedEnumValues) {
|
|
2634
|
+
const excludedArray = objectToArray(evaluateCondition(conditions.excludedEnumValues, context));
|
|
2635
|
+
if (excludedArray.length > 0) result = result.filter((ev) => !excludedArray.includes(String(ev.id)));
|
|
2636
|
+
}
|
|
2637
|
+
if (conditions.enumConditions) result = result.map((ev) => {
|
|
2638
|
+
const evConditions = conditions.enumConditions?.[ev.id];
|
|
2639
|
+
if (!evConditions) return ev;
|
|
2640
|
+
if (evConditions.hidden && evaluateCondition(evConditions.hidden, context)) return null;
|
|
2641
|
+
if (evConditions.disabled && evaluateCondition(evConditions.disabled, context)) return {
|
|
2642
|
+
...ev,
|
|
2643
|
+
disabled: true
|
|
2644
|
+
};
|
|
2645
|
+
return ev;
|
|
2646
|
+
}).filter((ev) => ev !== null);
|
|
2647
|
+
return result;
|
|
2342
2648
|
}
|
|
2649
|
+
//#endregion
|
|
2650
|
+
//#region src/components/common/useColumnsIds.tsx
|
|
2343
2651
|
function getSubcollectionColumnId(collection) {
|
|
2344
2652
|
return `subcollection:${collection.slug}`;
|
|
2345
2653
|
}
|
|
2654
|
+
/**
|
|
2655
|
+
* The jump-to-tab columns a collection table does not need, because the relation
|
|
2656
|
+
* behind them already has a column of its own.
|
|
2657
|
+
*
|
|
2658
|
+
* Every child view gets a 200px button column that opens its tab. For a relation
|
|
2659
|
+
* declared in `relations` that button is the relation's only presence in the
|
|
2660
|
+
* table. For one declared as a property it is the second: the property's own
|
|
2661
|
+
* column is already there, hydrated by the list fetch's `include: ["*"]`, showing
|
|
2662
|
+
* the child rows themselves — and carrying the *same heading*, because the tab
|
|
2663
|
+
* takes its name from the declaring property. Two columns called "Applications",
|
|
2664
|
+
* one of them a button, and nothing in the header to tell them apart.
|
|
2665
|
+
*
|
|
2666
|
+
* The property column wins: it shows what the children are, and each chip in it
|
|
2667
|
+
* opens one. The tab stays reachable by opening the record, which is what the
|
|
2668
|
+
* rest of the table's rows do anyway.
|
|
2669
|
+
*
|
|
2670
|
+
* Unless the author hid that column — `hideFromCollection` on the property, or a
|
|
2671
|
+
* `propertiesOrder` that omits it, is a statement about the column and not about
|
|
2672
|
+
* the relation, so the button comes back rather than the relation dropping out
|
|
2673
|
+
* of the table altogether.
|
|
2674
|
+
*/
|
|
2675
|
+
function getRedundantChildViewColumnIds(collection) {
|
|
2676
|
+
const redundant = /* @__PURE__ */ new Set();
|
|
2677
|
+
const properties = collection.properties ?? {};
|
|
2678
|
+
const order = collection.propertiesOrder;
|
|
2679
|
+
for (const [viewKey, propertyKey] of getChildViewDeclaringProperties(collection)) {
|
|
2680
|
+
const property = properties[propertyKey];
|
|
2681
|
+
if (!property || property.admin?.hideFromCollection) continue;
|
|
2682
|
+
if (order && !order.includes(propertyKey)) continue;
|
|
2683
|
+
redundant.add(`subcollection:${viewKey}`);
|
|
2684
|
+
}
|
|
2685
|
+
return redundant;
|
|
2686
|
+
}
|
|
2346
2687
|
function useColumnIds(collection, includeSubcollections) {
|
|
2347
2688
|
return useMemo(() => {
|
|
2348
2689
|
if (collection.propertiesOrder) return hideAndExpandKeys(collection, collection.propertiesOrder);
|
|
@@ -2357,8 +2698,10 @@ function hideAndExpandKeys(collection, keys) {
|
|
|
2357
2698
|
if (rootProperty && rootProperty.type === "map" && rootProperty.admin?.spreadChildren && rootProperty.properties) rootsWithExplicitChildren.add(rootKey);
|
|
2358
2699
|
}
|
|
2359
2700
|
const processedPropertyKeys = /* @__PURE__ */ new Set();
|
|
2701
|
+
const redundantChildViewColumns = getRedundantChildViewColumnIds(collection);
|
|
2360
2702
|
const result = keys.flatMap((key) => {
|
|
2361
2703
|
if (processedPropertyKeys.has(key)) return [null];
|
|
2704
|
+
if (redundantChildViewColumns.has(key)) return [null];
|
|
2362
2705
|
const property = collection.properties[key];
|
|
2363
2706
|
if (property) {
|
|
2364
2707
|
processedPropertyKeys.add(key);
|
|
@@ -2372,7 +2715,7 @@ function hideAndExpandKeys(collection, keys) {
|
|
|
2372
2715
|
}
|
|
2373
2716
|
return [{
|
|
2374
2717
|
key,
|
|
2375
|
-
disabled:
|
|
2718
|
+
disabled: isDisabled(property) || Boolean(property.admin?.readOnly)
|
|
2376
2719
|
}];
|
|
2377
2720
|
}
|
|
2378
2721
|
if (key.includes(".")) {
|
|
@@ -2387,7 +2730,7 @@ function hideAndExpandKeys(collection, keys) {
|
|
|
2387
2730
|
if (nestedProperty.admin?.disabled && typeof nestedProperty.admin?.disabled === "object" && nestedProperty.admin?.disabled.hidden) return [null];
|
|
2388
2731
|
return [{
|
|
2389
2732
|
key,
|
|
2390
|
-
disabled:
|
|
2733
|
+
disabled: isDisabled(rootProperty) || Boolean(rootProperty.admin?.readOnly) || isDisabled(nestedProperty) || Boolean(nestedProperty.admin?.readOnly)
|
|
2391
2734
|
}];
|
|
2392
2735
|
}
|
|
2393
2736
|
}
|
|
@@ -2420,7 +2763,7 @@ function hideAndExpandKeys(collection, keys) {
|
|
|
2420
2763
|
} else {
|
|
2421
2764
|
result.push({
|
|
2422
2765
|
key: propKey,
|
|
2423
|
-
disabled:
|
|
2766
|
+
disabled: isDisabled(property) || Boolean(property.admin?.readOnly)
|
|
2424
2767
|
});
|
|
2425
2768
|
processedPropertyKeys.add(propKey);
|
|
2426
2769
|
}
|
|
@@ -2439,14 +2782,20 @@ function getDefaultColumnKeys(collection, includeSubCollections) {
|
|
|
2439
2782
|
return hideAndExpandKeys(collection, columnIds);
|
|
2440
2783
|
}
|
|
2441
2784
|
function getColumnKeysForProperty(property, key, disabled) {
|
|
2442
|
-
if (property
|
|
2785
|
+
if (!property) return [];
|
|
2786
|
+
if (property.type === "map" && property.admin?.spreadChildren && property.properties) return Object.entries(property.properties).flatMap(([childKey, childProperty]) => getColumnKeysForProperty(childProperty, `${key}.${childKey}`, disabled || isDisabled(property) || Boolean(property.admin?.readOnly)));
|
|
2443
2787
|
return [{
|
|
2444
2788
|
key,
|
|
2445
|
-
disabled: disabled ||
|
|
2789
|
+
disabled: disabled || isDisabled(property) || Boolean(property.admin?.readOnly)
|
|
2446
2790
|
}];
|
|
2447
2791
|
}
|
|
2448
2792
|
function getFormFieldKeys(collection) {
|
|
2449
|
-
const
|
|
2793
|
+
const properties = collection.properties ?? {};
|
|
2794
|
+
const consumedByChildView = getChildViewRelationPropertyKeys(collection);
|
|
2795
|
+
const propertyKeys = Object.keys(properties).filter((key) => {
|
|
2796
|
+
if (!consumedByChildView.has(key)) return true;
|
|
2797
|
+
return properties[key].admin?.renderInForm === true;
|
|
2798
|
+
});
|
|
2450
2799
|
const additionalFields = collection.additionalFields ?? [];
|
|
2451
2800
|
const allKeys = [...propertyKeys, ...additionalFields.map((field) => field.key)];
|
|
2452
2801
|
if (collection.propertiesOrder) return collection.propertiesOrder.filter((key) => allKeys.includes(key));
|
|
@@ -2610,21 +2959,21 @@ function useDataTableController({ path, collection, scrollRestoration, entitiesD
|
|
|
2610
2959
|
const orderByParams = sortBy ? [String(sortBy[0]), sortBy[1]] : void 0;
|
|
2611
2960
|
let unsubscribe;
|
|
2612
2961
|
const includeParams = getRelationIncludeParams(collection);
|
|
2613
|
-
if (accessor.listen) unsubscribe = accessor.listen({
|
|
2962
|
+
if (accessor.listen) unsubscribe = accessor.listen(toFindParams({
|
|
2614
2963
|
where: whereParams,
|
|
2615
2964
|
limit: itemCount,
|
|
2616
2965
|
orderBy: orderByParams,
|
|
2617
2966
|
searchString,
|
|
2618
2967
|
include: includeParams
|
|
2619
|
-
}, (res) => onEntitiesUpdate(res.data), onError);
|
|
2968
|
+
}), (res) => onEntitiesUpdate(res.data), onError);
|
|
2620
2969
|
else {
|
|
2621
|
-
accessor.find({
|
|
2970
|
+
accessor.find(toFindParams({
|
|
2622
2971
|
where: whereParams,
|
|
2623
2972
|
limit: itemCount,
|
|
2624
2973
|
orderBy: orderByParams,
|
|
2625
2974
|
searchString,
|
|
2626
2975
|
include: includeParams
|
|
2627
|
-
}).then((res) => onEntitiesUpdate(res.data)).catch(onError);
|
|
2976
|
+
})).then((res) => onEntitiesUpdate(res.data)).catch(onError);
|
|
2628
2977
|
unsubscribe = () => void 0;
|
|
2629
2978
|
}
|
|
2630
2979
|
return unsubscribe;
|
|
@@ -9821,6 +10170,7 @@ var en = {
|
|
|
9821
10170
|
column_cannot_be_edited: "This column can't be edited directly",
|
|
9822
10171
|
close: "Close",
|
|
9823
10172
|
hide_list: "Hide list",
|
|
10173
|
+
show_list: "Show list",
|
|
9824
10174
|
unsaved_local_changes: "Unsaved Local changes",
|
|
9825
10175
|
unsaved_local_changes_description: "This document was edited locally and has unsaved changes. These local changes will be lost if you don't apply them.",
|
|
9826
10176
|
preview_changes: "Preview Changes",
|
|
@@ -10632,6 +10982,7 @@ var en = {
|
|
|
10632
10982
|
inspect_record: "Inspect record",
|
|
10633
10983
|
inspect: "Inspect",
|
|
10634
10984
|
close_inspector: "Close inspector",
|
|
10985
|
+
open_in_live_site: "Open in the live site",
|
|
10635
10986
|
history_revert: "Revert to this version",
|
|
10636
10987
|
history_revert_title: "Revert data to this version?",
|
|
10637
10988
|
history_reverted: "Reverted to selected version",
|
|
@@ -10720,6 +11071,7 @@ var es = {
|
|
|
10720
11071
|
column_cannot_be_edited: "Esta columna no se puede editar directamente",
|
|
10721
11072
|
close: "Cerrar",
|
|
10722
11073
|
hide_list: "Ocultar lista",
|
|
11074
|
+
show_list: "Mostrar lista",
|
|
10723
11075
|
unsaved_local_changes: "Cambios locales sin guardar",
|
|
10724
11076
|
unsaved_local_changes_description: "Este documento ha sido editado localmente y tiene cambios sin guardar. Estos cambios locales se perderán si no los aplicas.",
|
|
10725
11077
|
preview_changes: "Previsualizar cambios",
|
|
@@ -11576,6 +11928,7 @@ var de = {
|
|
|
11576
11928
|
column_cannot_be_edited: "Diese Spalte kann nicht direkt bearbeitet werden",
|
|
11577
11929
|
close: "Schließen",
|
|
11578
11930
|
hide_list: "Liste ausblenden",
|
|
11931
|
+
show_list: "Liste einblenden",
|
|
11579
11932
|
unsaved_local_changes: "Ungespeicherte lokale Änderungen",
|
|
11580
11933
|
unsaved_local_changes_description: "Dieses Dokument wurde lokal bearbeitet und weist ungespeicherte Änderungen auf. Diese lokalen Änderungen gehen verloren, wenn Sie sie nicht anwenden.",
|
|
11581
11934
|
preview_changes: "Änderungen in der Vorschau anzeigen",
|
|
@@ -12422,6 +12775,7 @@ var fr = {
|
|
|
12422
12775
|
column_cannot_be_edited: "Cette colonne ne peut pas être modifiée directement",
|
|
12423
12776
|
close: "Fermer",
|
|
12424
12777
|
hide_list: "Masquer la liste",
|
|
12778
|
+
show_list: "Afficher la liste",
|
|
12425
12779
|
unsaved_local_changes: "Modifications locales non enregistrées",
|
|
12426
12780
|
unsaved_local_changes_description: "Ce document a été modifié localement et contient des modifications non enregistrées. Ces modifications locales seront perdues si vous ne les appliquez pas.",
|
|
12427
12781
|
preview_changes: "Aperçu des modifications",
|
|
@@ -13268,6 +13622,7 @@ var it = {
|
|
|
13268
13622
|
column_cannot_be_edited: "Questa colonna non può essere modificata direttamente",
|
|
13269
13623
|
close: "Chiudi",
|
|
13270
13624
|
hide_list: "Nascondi elenco",
|
|
13625
|
+
show_list: "Mostra elenco",
|
|
13271
13626
|
unsaved_local_changes: "Modifiche locali non salvate",
|
|
13272
13627
|
unsaved_local_changes_description: "Questo documento è stato modificato localmente e ha modifiche non salvate. Queste modifiche locali andranno perse se non le applichi.",
|
|
13273
13628
|
preview_changes: "Anteprima modifiche",
|
|
@@ -14106,7 +14461,7 @@ var hi = {
|
|
|
14106
14461
|
group_by: "इसके द्वारा समूहित करें",
|
|
14107
14462
|
initialize_kanban_order: "कानबन क्रम प्रारंभ करें",
|
|
14108
14463
|
copy_id: "ID कॉपी करें",
|
|
14109
|
-
add_specific: "
|
|
14464
|
+
add_specific: "{{name}} जोड़ें",
|
|
14110
14465
|
select_specific: "विशिष्ट {{name}} चुनें",
|
|
14111
14466
|
select_from: "{{name}} से चुनें",
|
|
14112
14467
|
done: "हो गया",
|
|
@@ -14114,6 +14469,7 @@ var hi = {
|
|
|
14114
14469
|
column_cannot_be_edited: "इस कॉलम को सीधे संपादित नहीं किया जा सकता",
|
|
14115
14470
|
close: "बंद करें",
|
|
14116
14471
|
hide_list: "सूची छिपाएँ",
|
|
14472
|
+
show_list: "सूची दिखाएँ",
|
|
14117
14473
|
unsaved_local_changes: "सहेजे नहीं गए स्थानीय परिवर्तन",
|
|
14118
14474
|
unsaved_local_changes_description: "इस दस्तावेज़ को स्थानीय स्तर पर संपादित किया गया था और इसमें सहेजे नहीं गए परिवर्तन हैं। यदि आप इन्हें लागू नहीं करते हैं तो ये स्थानीय परिवर्तन खो जाएंगे।",
|
|
14119
14475
|
preview_changes: "परिवर्तनों का पूर्वावलोकन करें",
|
|
@@ -14965,6 +15321,7 @@ var pt = {
|
|
|
14965
15321
|
column_cannot_be_edited: "Esta coluna não pode ser editada diretamente",
|
|
14966
15322
|
close: "Fechar",
|
|
14967
15323
|
hide_list: "Ocultar lista",
|
|
15324
|
+
show_list: "Mostrar lista",
|
|
14968
15325
|
unsaved_local_changes: "Alterações locais não guardadas",
|
|
14969
15326
|
unsaved_local_changes_description: "Este documento foi editado localmente e tem alterações não guardadas. Estas alterações locais serão perdidas se não as aplicar.",
|
|
14970
15327
|
preview_changes: "Pré-visualizar alterações",
|
|
@@ -15840,6 +16197,11 @@ function Rebase(props) {
|
|
|
15840
16197
|
if (new Set(keys).size !== keys.length) console.error("Duplicate plugin keys detected:", keys.filter((k, i) => keys.indexOf(k) !== i));
|
|
15841
16198
|
}
|
|
15842
16199
|
const resolvedSlots = useMemo(() => [...directSlots, ...(plugins ?? []).flatMap((p) => p.slots ?? [])], [directSlots, plugins]);
|
|
16200
|
+
useEffect(() => {
|
|
16201
|
+
const dead = new Set(UNRENDERED_SLOTS);
|
|
16202
|
+
const registered = new Set(resolvedSlots.map((s) => s.slot).filter((slot) => dead.has(slot)));
|
|
16203
|
+
for (const slot of registered) console.warn(`[Rebase] A component is registered for the "${slot}" slot, which this build declares but renders nowhere — nothing will appear. This is a gap in Rebase, not in your plugin.`);
|
|
16204
|
+
}, [resolvedSlots]);
|
|
15843
16205
|
const clientAuthController = useAuthSubscription(authControllerProp ? void 0 : client?.auth);
|
|
15844
16206
|
const authController = authControllerProp ?? clientAuthController;
|
|
15845
16207
|
const normalizedDataSources = useMemo(() => {
|
|
@@ -16201,7 +16563,9 @@ function useRebaseAuthController(props = {}) {
|
|
|
16201
16563
|
});
|
|
16202
16564
|
auth.getAuthConfig().then((config) => {
|
|
16203
16565
|
if (isMountedRef.current) setAuthConfig(config);
|
|
16204
|
-
}).catch(() => {
|
|
16566
|
+
}).catch((e) => {
|
|
16567
|
+
console.warn("[Rebase] Could not load the backend auth configuration; the login view will fall back to defaults.", e);
|
|
16568
|
+
});
|
|
16205
16569
|
const unsubscribe = auth.onAuthStateChange(syncState);
|
|
16206
16570
|
return () => {
|
|
16207
16571
|
isMountedRef.current = false;
|
|
@@ -16636,6 +17000,21 @@ function createFormexStub(values) {
|
|
|
16636
17000
|
//#endregion
|
|
16637
17001
|
//#region src/util/entity_cache.ts
|
|
16638
17002
|
var LOCAL_STORAGE_PREFIX = "entity_cache::";
|
|
17003
|
+
/**
|
|
17004
|
+
* The in-session handoff channel: an edit in flight, parked here by the layout
|
|
17005
|
+
* that is being replaced and picked up by the one taking over — the split's
|
|
17006
|
+
* "hide list", full screen's "show list", the side panel's "open full screen".
|
|
17007
|
+
* A record loaded from it opens *dirty*, because that is what it is for.
|
|
17008
|
+
*
|
|
17009
|
+
* Deliberately **not** hydrated from `sessionStorage`, and it is the one thing
|
|
17010
|
+
* here that isn't. The persisted half of this file is the local-changes backup:
|
|
17011
|
+
* a draft that outlives a reload, offered back through the banner or applied on
|
|
17012
|
+
* open, according to the collection's `localChangesBackup`. Loading those
|
|
17013
|
+
* drafts into this map on startup made every one of them look like a handoff —
|
|
17014
|
+
* the first visit to the record after a reload silently opened dirty carrying a
|
|
17015
|
+
* draft the user had walked away from, and the banner that exists to ask about
|
|
17016
|
+
* it never appeared.
|
|
17017
|
+
*/
|
|
16639
17018
|
var entityCache = /* @__PURE__ */ new Map();
|
|
16640
17019
|
var isSessionStorageAvailable = typeof sessionStorage !== "undefined";
|
|
16641
17020
|
function customReplacer(key) {
|
|
@@ -16690,25 +17069,12 @@ function customReviver(_key, value) {
|
|
|
16690
17069
|
}
|
|
16691
17070
|
return value;
|
|
16692
17071
|
}
|
|
16693
|
-
if (isSessionStorageAvailable) try {
|
|
16694
|
-
for (let i = 0; i < sessionStorage.length; i++) {
|
|
16695
|
-
const fullKey = sessionStorage.key(i);
|
|
16696
|
-
if (fullKey && fullKey.startsWith(LOCAL_STORAGE_PREFIX)) {
|
|
16697
|
-
const path = fullKey.substring(14);
|
|
16698
|
-
const entityString = sessionStorage.getItem(fullKey);
|
|
16699
|
-
if (entityString) try {
|
|
16700
|
-
const entity = JSON.parse(entityString, customReviver);
|
|
16701
|
-
entityCache.set(path, entity);
|
|
16702
|
-
} catch (parseError) {
|
|
16703
|
-
console.error(`Failed to parse entity for path "${path}" from sessionStorage:`, parseError);
|
|
16704
|
-
}
|
|
16705
|
-
}
|
|
16706
|
-
}
|
|
16707
|
-
} catch (error) {
|
|
16708
|
-
console.error("Error accessing sessionStorage during initialization:", error);
|
|
16709
|
-
}
|
|
16710
17072
|
/**
|
|
16711
|
-
* Saves data to the
|
|
17073
|
+
* Saves data to the local-changes backup in `sessionStorage`.
|
|
17074
|
+
*
|
|
17075
|
+
* The backup only — a draft that survives a reload is not an edit in flight.
|
|
17076
|
+
* {@link saveEntityToMemoryCache} is the other channel; see {@link entityCache}
|
|
17077
|
+
* for what separates them.
|
|
16712
17078
|
* @param path - The unique path/key for the data.
|
|
16713
17079
|
* @param data - The data to cache and persist.
|
|
16714
17080
|
*/
|
|
@@ -16721,9 +17087,18 @@ function saveEntityToCache(path, data) {
|
|
|
16721
17087
|
console.error(`Failed to save entity for path "${path}" to sessionStorage:`, error);
|
|
16722
17088
|
}
|
|
16723
17089
|
}
|
|
17090
|
+
/**
|
|
17091
|
+
* Consumes a handoff. The channel is one-shot: whoever picks an edit up owns it
|
|
17092
|
+
* from then on, and an edit left behind here is one that reopens a record dirty
|
|
17093
|
+
* long after the user stopped looking at it.
|
|
17094
|
+
*/
|
|
16724
17095
|
function removeEntityFromMemoryCache(path) {
|
|
16725
17096
|
entityCache.delete(path);
|
|
16726
17097
|
}
|
|
17098
|
+
/**
|
|
17099
|
+
* Parks an edit in flight for the layout about to take over. See
|
|
17100
|
+
* {@link entityCache}: this is the handoff, not the backup.
|
|
17101
|
+
*/
|
|
16727
17102
|
function saveEntityToMemoryCache(path, data) {
|
|
16728
17103
|
entityCache.set(path, data);
|
|
16729
17104
|
}
|
|
@@ -16731,8 +17106,11 @@ function getEntityFromMemoryCache(path) {
|
|
|
16731
17106
|
return entityCache.get(path);
|
|
16732
17107
|
}
|
|
16733
17108
|
/**
|
|
16734
|
-
* Retrieves a entity from the
|
|
16735
|
-
*
|
|
17109
|
+
* Retrieves a entity from the local-changes backup in `sessionStorage`.
|
|
17110
|
+
*
|
|
17111
|
+
* The backup only — the handoff channel is read through
|
|
17112
|
+
* {@link getEntityFromMemoryCache}, and the two mean different things to the
|
|
17113
|
+
* form that receives them.
|
|
16736
17114
|
* @param path - The unique path/key for the entity.
|
|
16737
17115
|
* @returns The cached entity or `undefined` if not found.
|
|
16738
17116
|
*/
|
|
@@ -17359,8 +17737,82 @@ function getAdminEntityChildViews(collection) {
|
|
|
17359
17737
|
}));
|
|
17360
17738
|
}
|
|
17361
17739
|
//#endregion
|
|
17740
|
+
//#region src/collections/entity-display.ts
|
|
17741
|
+
/**
|
|
17742
|
+
* Collections that have already warned about a deprecated field, so a list of
|
|
17743
|
+
* fifty rows produces one line in the console rather than fifty.
|
|
17744
|
+
*/
|
|
17745
|
+
var deprecationWarned = /* @__PURE__ */ new Set();
|
|
17746
|
+
function collectionId(collection) {
|
|
17747
|
+
return collection.slug ?? collection.name ?? "collection";
|
|
17748
|
+
}
|
|
17749
|
+
function warnOnce(id, message) {
|
|
17750
|
+
const key = `${id}:${message}`;
|
|
17751
|
+
if (deprecationWarned.has(key)) return;
|
|
17752
|
+
deprecationWarned.add(key);
|
|
17753
|
+
console.warn(message);
|
|
17754
|
+
}
|
|
17755
|
+
/**
|
|
17756
|
+
* What the collection declares for a role, before deciding which form it is.
|
|
17757
|
+
*
|
|
17758
|
+
* `display.title` wins over the deprecated `titleProperty`: a collection setting
|
|
17759
|
+
* both is mid-migration, and the new field is the one it means.
|
|
17760
|
+
*/
|
|
17761
|
+
function getDeclaredSource(collection, role) {
|
|
17762
|
+
const declared = collection.display?.[role];
|
|
17763
|
+
if (declared !== void 0) return declared;
|
|
17764
|
+
if (role === "title" && collection.titleProperty) {
|
|
17765
|
+
const id = collectionId(collection);
|
|
17766
|
+
warnOnce(id, `[rebase] Collection "${id}" uses admin.titleProperty, which is deprecated. Move it to admin.display.title — the same string works there, and display.title also accepts a resolver for a title the record does not carry.`);
|
|
17767
|
+
return collection.titleProperty;
|
|
17768
|
+
}
|
|
17769
|
+
}
|
|
17770
|
+
/**
|
|
17771
|
+
* The property path a role is declared to read, when it is declared as a path.
|
|
17772
|
+
*
|
|
17773
|
+
* Returns `undefined` for a role filled by a resolver — a resolver has no key —
|
|
17774
|
+
* and for a role the collection says nothing about, which is then derived.
|
|
17775
|
+
*
|
|
17776
|
+
* @group Collections
|
|
17777
|
+
*/
|
|
17778
|
+
function getDisplayPropertyKey(collection, role) {
|
|
17779
|
+
const declared = getDeclaredSource(collection, role);
|
|
17780
|
+
return typeof declared === "string" ? declared : void 0;
|
|
17781
|
+
}
|
|
17782
|
+
/**
|
|
17783
|
+
* The resolver a role is declared to use, when it is declared as one.
|
|
17784
|
+
*
|
|
17785
|
+
* @group Collections
|
|
17786
|
+
*/
|
|
17787
|
+
function getDisplayResolver(collection, role) {
|
|
17788
|
+
const declared = getDeclaredSource(collection, role);
|
|
17789
|
+
return typeof declared === "function" ? declared : void 0;
|
|
17790
|
+
}
|
|
17791
|
+
/**
|
|
17792
|
+
* True when the collection states this role at all, in either form.
|
|
17793
|
+
*
|
|
17794
|
+
* The derivation is a guess about what a collection probably means; a statement
|
|
17795
|
+
* outranks it, and the heuristics that look for "the first enum" or "the leading
|
|
17796
|
+
* relation" have to stand down when one exists.
|
|
17797
|
+
*
|
|
17798
|
+
* @group Collections
|
|
17799
|
+
*/
|
|
17800
|
+
function hasDeclaredDisplay(collection, role) {
|
|
17801
|
+
return getDeclaredSource(collection, role) !== void 0;
|
|
17802
|
+
}
|
|
17803
|
+
//#endregion
|
|
17362
17804
|
//#region src/collections/entity_image_preview.ts
|
|
17805
|
+
/**
|
|
17806
|
+
* The property that fills a record's image slot.
|
|
17807
|
+
*
|
|
17808
|
+
* `admin.display.image` first, then six fallbacks in descending confidence —
|
|
17809
|
+
* the first image-typed storage property, an array of them, a URL rendered as an
|
|
17810
|
+
* image, and so on. The ladder stays for collections that say nothing; a
|
|
17811
|
+
* collection that names its picture is not guessed at.
|
|
17812
|
+
*/
|
|
17363
17813
|
function getEntityImagePreviewPropertyKey(collection) {
|
|
17814
|
+
const declared = getDisplayPropertyKey(collection, "image");
|
|
17815
|
+
if (declared) return declared;
|
|
17364
17816
|
for (const key in collection.properties) {
|
|
17365
17817
|
const property = collection.properties[key];
|
|
17366
17818
|
if (property.type === "string" && property.storage?.acceptedFiles?.includes("image/*")) return key;
|
|
@@ -17387,6 +17839,142 @@ function getEntityImagePreviewPropertyKey(collection) {
|
|
|
17387
17839
|
}
|
|
17388
17840
|
}
|
|
17389
17841
|
//#endregion
|
|
17842
|
+
//#region src/collections/entity-display-cache.ts
|
|
17843
|
+
/** The identity of one role of one record, as a cache key. */
|
|
17844
|
+
function entityDisplayKey(path, entityId, role) {
|
|
17845
|
+
return `${role} ${path} ${entityId ?? ""}`;
|
|
17846
|
+
}
|
|
17847
|
+
var EntityDisplayCache = class {
|
|
17848
|
+
entries = /* @__PURE__ */ new Map();
|
|
17849
|
+
listeners = /* @__PURE__ */ new Set();
|
|
17850
|
+
/**
|
|
17851
|
+
* The resolved value, or `undefined` when this pair has not been resolved
|
|
17852
|
+
* yet. `null` is a resolved absence, and the two must stay distinct: a
|
|
17853
|
+
* caller that reads "not yet" as "nothing" flickers its fallback in on every
|
|
17854
|
+
* mount.
|
|
17855
|
+
*/
|
|
17856
|
+
peek(key) {
|
|
17857
|
+
const entry = this.entries.get(key);
|
|
17858
|
+
if (!entry || "promise" in entry) return void 0;
|
|
17859
|
+
return entry.value;
|
|
17860
|
+
}
|
|
17861
|
+
/** True while a resolution for this pair is in flight. */
|
|
17862
|
+
isLoading(key) {
|
|
17863
|
+
const entry = this.entries.get(key);
|
|
17864
|
+
return Boolean(entry && "promise" in entry);
|
|
17865
|
+
}
|
|
17866
|
+
/**
|
|
17867
|
+
* Resolve once per record and role. Concurrent callers share the first
|
|
17868
|
+
* call's promise; later callers get the cached value with no promise at all.
|
|
17869
|
+
*
|
|
17870
|
+
* A resolver that throws is recorded as "nothing" rather than retried: the
|
|
17871
|
+
* alternative is every render re-running a call that just failed. And it is
|
|
17872
|
+
* reported here, which is a correction.
|
|
17873
|
+
*
|
|
17874
|
+
* It used to say "the caller that saw the rejection is the one that logs
|
|
17875
|
+
* it", and `useEntityDisplay` duly attached a `.catch()` that warned. But
|
|
17876
|
+
* both failure paths below swallow and return a *resolved* promise, so that
|
|
17877
|
+
* catch could never run — the two halves each did the reasonable thing and
|
|
17878
|
+
* between them the log was unreachable. A resolver that blew up produced a
|
|
17879
|
+
* blank chip and total silence, which is the failure mode
|
|
17880
|
+
* `EntityDisplayResolver`'s own contract ("treated as `undefined` and logged
|
|
17881
|
+
* once") exists to rule out.
|
|
17882
|
+
*
|
|
17883
|
+
* Reporting belongs here for the reason the caller could not do it: this is
|
|
17884
|
+
* the one place that runs exactly once per key, so "once" is a property of
|
|
17885
|
+
* the code rather than a hope about how many components mount.
|
|
17886
|
+
*/
|
|
17887
|
+
resolve(key, resolver) {
|
|
17888
|
+
const entry = this.entries.get(key);
|
|
17889
|
+
if (entry) return "promise" in entry ? entry.promise : Promise.resolve(entry.value);
|
|
17890
|
+
let produced;
|
|
17891
|
+
try {
|
|
17892
|
+
produced = resolver();
|
|
17893
|
+
} catch (error) {
|
|
17894
|
+
this.fail(key, error);
|
|
17895
|
+
return Promise.resolve(null);
|
|
17896
|
+
}
|
|
17897
|
+
if (!isPromise(produced)) {
|
|
17898
|
+
const value = normalize(produced);
|
|
17899
|
+
this.set(key, value);
|
|
17900
|
+
return Promise.resolve(value);
|
|
17901
|
+
}
|
|
17902
|
+
const promise = produced.then((resolved) => {
|
|
17903
|
+
const value = normalize(resolved);
|
|
17904
|
+
this.set(key, value);
|
|
17905
|
+
return value;
|
|
17906
|
+
}).catch((error) => {
|
|
17907
|
+
this.fail(key, error);
|
|
17908
|
+
return null;
|
|
17909
|
+
});
|
|
17910
|
+
this.entries.set(key, { promise });
|
|
17911
|
+
return promise;
|
|
17912
|
+
}
|
|
17913
|
+
/**
|
|
17914
|
+
* Drop what is known about a record, so the next ask resolves again. Called
|
|
17915
|
+
* after a write: the row that just saved may be called something else now.
|
|
17916
|
+
*/
|
|
17917
|
+
invalidate(path, entityId) {
|
|
17918
|
+
const suffix = ` ${path} ${entityId ?? ""}`;
|
|
17919
|
+
let changed = false;
|
|
17920
|
+
for (const key of [...this.entries.keys()]) if (entityId === void 0 ? key.includes(` ${path} `) || key.endsWith(` ${path} `) : key.endsWith(suffix)) {
|
|
17921
|
+
this.entries.delete(key);
|
|
17922
|
+
changed = true;
|
|
17923
|
+
}
|
|
17924
|
+
if (changed) this.emit();
|
|
17925
|
+
}
|
|
17926
|
+
/** Drop everything. The user signed out, or the app swapped datasource. */
|
|
17927
|
+
clear() {
|
|
17928
|
+
if (this.entries.size === 0) return;
|
|
17929
|
+
this.entries.clear();
|
|
17930
|
+
this.emit();
|
|
17931
|
+
}
|
|
17932
|
+
subscribe(listener) {
|
|
17933
|
+
this.listeners.add(listener);
|
|
17934
|
+
return () => {
|
|
17935
|
+
this.listeners.delete(listener);
|
|
17936
|
+
};
|
|
17937
|
+
}
|
|
17938
|
+
set(key, value) {
|
|
17939
|
+
this.entries.set(key, { value });
|
|
17940
|
+
this.emit();
|
|
17941
|
+
}
|
|
17942
|
+
/**
|
|
17943
|
+
* Record a failed resolution as "nothing", and say so once.
|
|
17944
|
+
*
|
|
17945
|
+
* The key is the message: it is `<role> <path> <id>`, which is exactly what a
|
|
17946
|
+
* reader needs to find the resolver that blew up. A warning with no key would
|
|
17947
|
+
* tell them a display resolver failed somewhere in a list of fifty rows.
|
|
17948
|
+
*
|
|
17949
|
+
* `console.warn` rather than a thrown error, because this runs while a row is
|
|
17950
|
+
* rendering: the contract is that a title which cannot be fetched must not
|
|
17951
|
+
* take down the row that shows it.
|
|
17952
|
+
*/
|
|
17953
|
+
fail(key, error) {
|
|
17954
|
+
console.warn(`[rebase] Could not resolve display value for ${key}:`, error);
|
|
17955
|
+
this.set(key, null);
|
|
17956
|
+
}
|
|
17957
|
+
emit() {
|
|
17958
|
+
for (const listener of [...this.listeners]) listener();
|
|
17959
|
+
}
|
|
17960
|
+
};
|
|
17961
|
+
/**
|
|
17962
|
+
* Empty strings and empty arrays are absences, not values — a title of `" "`
|
|
17963
|
+
* would otherwise beat the derived one and render as a blank heading.
|
|
17964
|
+
*/
|
|
17965
|
+
function normalize(value) {
|
|
17966
|
+
if (value === void 0 || value === null) return null;
|
|
17967
|
+
if (typeof value === "string") {
|
|
17968
|
+
const trimmed = value.trim();
|
|
17969
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
17970
|
+
}
|
|
17971
|
+
if (Array.isArray(value)) return value.length > 0 ? value : null;
|
|
17972
|
+
return value;
|
|
17973
|
+
}
|
|
17974
|
+
function isPromise(value) {
|
|
17975
|
+
return typeof value?.then === "function";
|
|
17976
|
+
}
|
|
17977
|
+
//#endregion
|
|
17390
17978
|
//#region src/collections/filter-operator-resolution.ts
|
|
17391
17979
|
/**
|
|
17392
17980
|
* Default operators offered per property type, before engine capabilities and
|
|
@@ -17497,115 +18085,6 @@ function resolveFilterOperators({ property, isArray, engine }) {
|
|
|
17497
18085
|
return typeDefaults.filter((op) => engineOps.has(op) && (narrowingSet === void 0 || narrowingSet.has(op)));
|
|
17498
18086
|
}
|
|
17499
18087
|
//#endregion
|
|
17500
|
-
//#region src/collections/property_presentation.ts
|
|
17501
|
-
function isReadOnly(property) {
|
|
17502
|
-
if (property.admin?.readOnly) return true;
|
|
17503
|
-
if (property.type === "date") {
|
|
17504
|
-
if (property.autoValue) return true;
|
|
17505
|
-
}
|
|
17506
|
-
if (property.type === "reference") return !property.path && !("Field" in (property.admin || {}) && property.admin?.Field);
|
|
17507
|
-
return false;
|
|
17508
|
-
}
|
|
17509
|
-
function isHidden(property) {
|
|
17510
|
-
return typeof property.admin?.disabled === "object" && Boolean(property.admin?.disabled.hidden);
|
|
17511
|
-
}
|
|
17512
|
-
function applyPropertyConditions(property, context) {
|
|
17513
|
-
const { conditions } = property;
|
|
17514
|
-
if (!conditions) return property;
|
|
17515
|
-
const result = { ...property };
|
|
17516
|
-
if (conditions.disabled) {
|
|
17517
|
-
if (evaluateCondition(conditions.disabled, context)) {
|
|
17518
|
-
result.admin = result.admin || {};
|
|
17519
|
-
result.admin.disabled = {
|
|
17520
|
-
clearOnDisabled: conditions.clearOnDisabled ?? false,
|
|
17521
|
-
disabledMessage: conditions.disabledMessage,
|
|
17522
|
-
hidden: false
|
|
17523
|
-
};
|
|
17524
|
-
}
|
|
17525
|
-
}
|
|
17526
|
-
if (conditions.hidden) {
|
|
17527
|
-
if (evaluateCondition(conditions.hidden, context)) {
|
|
17528
|
-
result.admin = result.admin || {};
|
|
17529
|
-
result.admin.disabled = {
|
|
17530
|
-
...typeof result.admin?.disabled === "object" ? result.admin.disabled : {},
|
|
17531
|
-
hidden: true,
|
|
17532
|
-
clearOnDisabled: conditions.clearOnDisabled ?? false
|
|
17533
|
-
};
|
|
17534
|
-
}
|
|
17535
|
-
}
|
|
17536
|
-
if (conditions.readOnly) {
|
|
17537
|
-
if (evaluateCondition(conditions.readOnly, context)) {
|
|
17538
|
-
result.admin = result.admin || {};
|
|
17539
|
-
result.admin.readOnly = true;
|
|
17540
|
-
}
|
|
17541
|
-
}
|
|
17542
|
-
if (conditions.required !== void 0) {
|
|
17543
|
-
const isRequired = evaluateCondition(conditions.required, context);
|
|
17544
|
-
result.validation = {
|
|
17545
|
-
...result.validation,
|
|
17546
|
-
required: isRequired,
|
|
17547
|
-
requiredMessage: conditions.requiredMessage
|
|
17548
|
-
};
|
|
17549
|
-
}
|
|
17550
|
-
if (context.isNew && conditions.defaultValue !== void 0) result.defaultValue = evaluateCondition(conditions.defaultValue, context);
|
|
17551
|
-
if ("enum" in result && result.enum && (conditions.enumConditions || conditions.allowedEnumValues || conditions.excludedEnumValues)) result.enum = applyEnumConditions(result.enum, conditions, context);
|
|
17552
|
-
if (result.type === "reference") {
|
|
17553
|
-
if (conditions.referencePath) result.path = evaluateCondition(conditions.referencePath, context);
|
|
17554
|
-
if (conditions.referenceFilter) {
|
|
17555
|
-
result.admin = result.admin || {};
|
|
17556
|
-
result.admin.fixedFilter = evaluateCondition(conditions.referenceFilter, context);
|
|
17557
|
-
}
|
|
17558
|
-
}
|
|
17559
|
-
if (result.type === "array") {
|
|
17560
|
-
if (conditions.canAddElements !== void 0) {
|
|
17561
|
-
result.admin = result.admin || {};
|
|
17562
|
-
result.admin.canAddElements = evaluateCondition(conditions.canAddElements, context);
|
|
17563
|
-
}
|
|
17564
|
-
if (conditions.sortable !== void 0) {
|
|
17565
|
-
result.admin = result.admin || {};
|
|
17566
|
-
result.admin.sortable = evaluateCondition(conditions.sortable, context);
|
|
17567
|
-
}
|
|
17568
|
-
}
|
|
17569
|
-
return result;
|
|
17570
|
-
}
|
|
17571
|
-
/**
|
|
17572
|
-
* Convert an object with numeric keys back to an array.
|
|
17573
|
-
* Firestore stores arrays as {"0": "a", "1": "b"} to avoid nested arrays.
|
|
17574
|
-
*/
|
|
17575
|
-
function objectToArray(obj) {
|
|
17576
|
-
if (Array.isArray(obj)) return obj.map(String);
|
|
17577
|
-
if (obj && typeof obj === "object") {
|
|
17578
|
-
const keys = Object.keys(obj);
|
|
17579
|
-
if (keys.length > 0 && keys.every((k) => !isNaN(Number(k)))) return keys.sort((a, b) => Number(a) - Number(b)).map((k) => obj[k]).filter((v) => typeof v === "string" || typeof v === "number").map(String);
|
|
17580
|
-
}
|
|
17581
|
-
return [];
|
|
17582
|
-
}
|
|
17583
|
-
/**
|
|
17584
|
-
* Apply enum-specific conditions to filter and modify enum values.
|
|
17585
|
-
*/
|
|
17586
|
-
function applyEnumConditions(enumValues, conditions, context) {
|
|
17587
|
-
let result = [...enumValues];
|
|
17588
|
-
if (conditions.allowedEnumValues) {
|
|
17589
|
-
const allowedArray = objectToArray(evaluateCondition(conditions.allowedEnumValues, context));
|
|
17590
|
-
if (allowedArray.length > 0) result = result.filter((ev) => allowedArray.includes(String(ev.id)));
|
|
17591
|
-
}
|
|
17592
|
-
if (conditions.excludedEnumValues) {
|
|
17593
|
-
const excludedArray = objectToArray(evaluateCondition(conditions.excludedEnumValues, context));
|
|
17594
|
-
if (excludedArray.length > 0) result = result.filter((ev) => !excludedArray.includes(String(ev.id)));
|
|
17595
|
-
}
|
|
17596
|
-
if (conditions.enumConditions) result = result.map((ev) => {
|
|
17597
|
-
const evConditions = conditions.enumConditions?.[ev.id];
|
|
17598
|
-
if (!evConditions) return ev;
|
|
17599
|
-
if (evConditions.hidden && evaluateCondition(evConditions.hidden, context)) return null;
|
|
17600
|
-
if (evConditions.disabled && evaluateCondition(evConditions.disabled, context)) return {
|
|
17601
|
-
...ev,
|
|
17602
|
-
disabled: true
|
|
17603
|
-
};
|
|
17604
|
-
return ev;
|
|
17605
|
-
}).filter((ev) => ev !== null);
|
|
17606
|
-
return result;
|
|
17607
|
-
}
|
|
17608
|
-
//#endregion
|
|
17609
18088
|
//#region src/collections/form-layout.ts
|
|
17610
18089
|
/**
|
|
17611
18090
|
* Columns in the form grid.
|
|
@@ -17796,7 +18275,8 @@ function resolveFormLayout({ collection, fieldKeys, status }) {
|
|
|
17796
18275
|
title: section.title,
|
|
17797
18276
|
collapsible: section.collapsible ?? titled,
|
|
17798
18277
|
collapsed: titled ? Boolean(section.collapsed) : false,
|
|
17799
|
-
fields
|
|
18278
|
+
fields,
|
|
18279
|
+
readVariant: section.readVariant
|
|
17800
18280
|
});
|
|
17801
18281
|
}
|
|
17802
18282
|
const leftovers = fieldKeys.map((key) => available.get(key)).filter((f) => Boolean(f));
|
|
@@ -18079,18 +18559,6 @@ function isHidden$1(property) {
|
|
|
18079
18559
|
return Boolean(property.admin?.hideFromCollection);
|
|
18080
18560
|
}
|
|
18081
18561
|
/**
|
|
18082
|
-
* File-storage backed content (single image, array of images, generic upload…).
|
|
18083
|
-
* Rendered by the dedicated image slot, so never a title.
|
|
18084
|
-
*/
|
|
18085
|
-
function isStorageProperty(property) {
|
|
18086
|
-
if (property.type === "string" && (property.storage || property.admin?.urlPreview === "image")) return true;
|
|
18087
|
-
if (property.type === "array" && property.of && !Array.isArray(property.of)) {
|
|
18088
|
-
const inner = property.of;
|
|
18089
|
-
if (inner.type === "string" && (inner.storage || inner.admin?.urlPreview === "image")) return true;
|
|
18090
|
-
}
|
|
18091
|
-
return false;
|
|
18092
|
-
}
|
|
18093
|
-
/**
|
|
18094
18562
|
* Every column on this collection that stores a foreign key: the `localKey` of
|
|
18095
18563
|
* each owning relation (declared inline on a property or in `relations[]`), the
|
|
18096
18564
|
* source column of a junction, and the key a many-to-many joins on.
|
|
@@ -18170,7 +18638,8 @@ function scoreTitleCandidate(property, key, idKeys, foreignKeys) {
|
|
|
18170
18638
|
*/
|
|
18171
18639
|
function getTitlePropertyCandidates(collection) {
|
|
18172
18640
|
if (!collection.properties) return [];
|
|
18173
|
-
|
|
18641
|
+
const declared = getDisplayPropertyKey(collection, "title");
|
|
18642
|
+
if (declared && getPropertyInPath(collection.properties, declared)) return [declared];
|
|
18174
18643
|
const idKeys = new Set(getPrimaryKeys(collection));
|
|
18175
18644
|
const foreignKeys = getForeignKeyColumns(collection);
|
|
18176
18645
|
const explicitOrder = collection.propertiesOrder;
|
|
@@ -18213,7 +18682,7 @@ function getTitlePropertyCandidates(collection) {
|
|
|
18213
18682
|
*/
|
|
18214
18683
|
function getLeadingRelationTitleKey(collection) {
|
|
18215
18684
|
if (!collection.properties) return void 0;
|
|
18216
|
-
if (collection
|
|
18685
|
+
if (hasDeclaredDisplay(collection, "title")) return void 0;
|
|
18217
18686
|
const idKeys = new Set(getPrimaryKeys(collection));
|
|
18218
18687
|
const foreignKeys = getForeignKeyColumns(collection);
|
|
18219
18688
|
const order = collection.propertiesOrder ?? Object.keys(collection.properties);
|
|
@@ -18274,6 +18743,6 @@ function getTitlePropertyKeyForValues(collection, values, entityId) {
|
|
|
18274
18743
|
return candidates[0];
|
|
18275
18744
|
}
|
|
18276
18745
|
//#endregion
|
|
18277
|
-
export { AIIcon, AIModifiedIndicator, AdminModeControllerContext, AdminModeControllerProvider, AnalyticsContext, ApiConfigProvider, AuthControllerContext, CONTAINER_FULL_WIDTH, CollectionComponentOverrideProvider, CollectionResolverRegistrationContext, CollectionScopeContext, CollectionScopeProvider, ComponentOverrideContext, ConfirmationDialog, CrmDashboardDemo, CustomizationControllerContext, DEFAULT_API_PATH, DEFAULT_PAGE_SIZE, DataDriverContext, DataSourcesContext, DialogsControllerContext, DialogsProvider, EffectiveRoleControllerContext, EffectiveRoleControllerProvider, ErrorTooltip, ErrorView, FORM_CONTAINER_WIDTH, GlobalComponentOverrideProvider, IconForView, LanguageToggle, LoginView, ModeControllerContext, ModeControllerProvider, NavigationBlockerProvider, NotFoundPage, PluginProviderStack, REBASE_LOCALE_STORAGE_KEY, Rebase, RebaseAuth, RebaseClientInstanceContext, RebaseDataContext, RebaseI18nProvider, RebaseLogo, RebaseRegistryProvider, RebaseRouter, RebaseRoutes, SIDE_PANEL_DEFAULT_WIDTH, STUDIO_NAVIGATION_GROUPS, SchemaDriftBanner, SchemaDriftProvider, SnackbarProvider, StorageSourceContext, StorageSourcesContext, StudioBridgeContext, StudioBridgeProvider, StudioBridgeRegistryContext, StudioBridgeRegistryProvider, UIReferenceView, UIStyleGuide, UnsavedChangesDialog, UserConfigurationPersistenceContext, UserDisplay, UserSelectPopover, UserSettingsView, addInitialSlash, apiBaseOf, applyPropertyConditions, buildCollapsedDefaults, buildEnumLabel, clearAuthConfigCache, clearFetchCache, createAuthConfigCache, createFormexStub, deleteEntityWithCallbacks, deriveSpan, en, es, fetchAuthConfig, fillRows, flattenKeys, getAdminEntityChildViews, getAdminSubcollections, getCollectionBySlugWithin, getCollectionPathsCombinations, getColorScheme, getColumnKeysForProperty, getEntityFromCache, getEntityFromMemoryCache, getEntityImagePreviewPropertyKey, getEntityPreviewKeys, getEntityTitlePropertyKey, getFormFieldKeys, getIcon, getLastSegment, getLeadingRelationTitleKey, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getRelationIncludeParams, getRowHeight, getSubcollectionColumnId, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, iconsSearch, isAuditTimestamp, isEnumValueDisabled, isFilterableRelation, isHidden, isIdPropertyEditable, isReadOnly, isSchemaDriftError, looksLikeIdentifierValue, populateFetchCache, removeEntityFromCache, removeEntityFromMemoryCache, removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, resolveCollectionPathIds, resolveComponentRef, resolveDefaultSelectedView, resolveFilterOperators, resolveFormLayout, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAdminModeController, useAnalyticsController, useApiBase, useApiConfig, useAuthController, useAuthSubscription, useBackendStorageSource, useBridgeRegistration, useBrowserTitleAndIcon, useBuildAdminModeController, useBuildEffectiveRoleController, useBuildLocalConfigurationPersistence, useBuildModeController, useClipboard, useCollapsedGroups, useCollection, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataSources, useDataTableController, useDebouncedData, useDialogsController, useEffectiveRoleController, useFetch, useLargeLayout, useModeController, useNavigationBlocker, usePermissions, useRebaseAuthController, useRebaseClient, useRebaseContext, useRebaseRegistry, useRebaseRegistryDispatch, useRelationSelector, useResolvedComponent, useRestoreScroll, useSchemaDriftContext, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useStudioBreadcrumbs, useStudioCapabilities, useStudioCollectionRegistry, useStudioNavigationState, useStudioSidePanelController, useStudioUrlController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence };
|
|
18746
|
+
export { AIIcon, AIModifiedIndicator, AdminModeControllerContext, AdminModeControllerProvider, AnalyticsContext, ApiConfigProvider, AuthControllerContext, CONTAINER_FULL_WIDTH, CollectionComponentOverrideProvider, CollectionResolverRegistrationContext, CollectionScopeContext, CollectionScopeProvider, ComponentOverrideContext, ConfirmationDialog, CrmDashboardDemo, CustomizationControllerContext, DEFAULT_API_PATH, DEFAULT_PAGE_SIZE, DataDriverContext, DataSourcesContext, DialogsControllerContext, DialogsProvider, EffectiveRoleControllerContext, EffectiveRoleControllerProvider, EntityDisplayCache, ErrorTooltip, ErrorView, FORM_CONTAINER_WIDTH, GlobalComponentOverrideProvider, IconForView, LanguageToggle, LoginView, ModeControllerContext, ModeControllerProvider, NavigationBlockerProvider, NotFoundPage, PluginProviderStack, REBASE_LOCALE_STORAGE_KEY, Rebase, RebaseAuth, RebaseClientInstanceContext, RebaseDataContext, RebaseI18nProvider, RebaseLogo, RebaseRegistryProvider, RebaseRouter, RebaseRoutes, SIDE_PANEL_DEFAULT_WIDTH, STUDIO_NAVIGATION_GROUPS, SUMMARY_RANK, SchemaDriftBanner, SchemaDriftProvider, SnackbarProvider, StorageSourceContext, StorageSourcesContext, StudioBridgeContext, StudioBridgeProvider, StudioBridgeRegistryContext, StudioBridgeRegistryProvider, UIReferenceView, UIStyleGuide, UnsavedChangesDialog, UserConfigurationPersistenceContext, UserDisplay, UserSelectPopover, UserSettingsView, addInitialSlash, apiBaseOf, applyPropertyConditions, buildCollapsedDefaults, buildEnumLabel, canSummariseProperty, clearAuthConfigCache, clearFetchCache, createAuthConfigCache, createFormexStub, deleteEntityWithCallbacks, deriveSpan, en, entityDisplayKey, es, fetchAuthConfig, fillRows, flattenKeys, getAdminEntityChildViews, getAdminSubcollections, getCollectionBySlugWithin, getCollectionPathsCombinations, getColorScheme, getColumnKeysForProperty, getDisplayPropertyKey, getDisplayResolver, getEntityFromCache, getEntityFromMemoryCache, getEntityImagePreviewPropertyKey, getEntityPreviewKeys, getEntityTitlePropertyKey, getFormFieldKeys, getIcon, getLastSegment, getLeadingRelationTitleKey, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getPropertyInPath, getRedundantChildViewColumnIds, getRelationIncludeParams, getRowHeight, getSubcollectionColumnId, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, hasDeclaredDisplay, iconsSearch, isAuditTimestamp, isDisabled, isEnumValueDisabled, isFilterableRelation, isHidden, isIdPropertyEditable, isLongTextProperty, isReadOnly, isSchemaDriftError, isStorageProperty, looksLikeIdentifierValue, populateFetchCache, rankSummaryProperty, removeEntityFromCache, removeEntityFromMemoryCache, removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, resolveCollectionPathIds, resolveComponentRef, resolveDefaultSelectedView, resolveFilterOperators, resolveFormLayout, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAdminModeController, useAnalyticsController, useApiBase, useApiConfig, useAuthController, useAuthSubscription, useBackendStorageSource, useBridgeRegistration, useBrowserTitleAndIcon, useBuildAdminModeController, useBuildEffectiveRoleController, useBuildLocalConfigurationPersistence, useBuildModeController, useClipboard, useCollapsedGroups, useCollection, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataSources, useDataTableController, useDebouncedData, useDialogsController, useEffectiveRoleController, useFetch, useLargeLayout, useModeController, useNavigationBlocker, usePermissions, useRebaseAuthController, useRebaseClient, useRebaseContext, useRebaseRegistry, useRebaseRegistryDispatch, useRelationSelector, useResolvedComponent, useRestoreScroll, useSchemaDriftContext, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useStudioBreadcrumbs, useStudioCapabilities, useStudioCollectionRegistry, useStudioNavigationState, useStudioSidePanelController, useStudioUrlController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence };
|
|
18278
18747
|
|
|
18279
18748
|
//# sourceMappingURL=index.es.js.map
|