@firecms/core 3.0.1 → 3.1.0-canary.1df3b2c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/components/AIIcon.d.ts +16 -0
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +7 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +14 -0
- package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +6 -0
- package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -4
- package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +6 -0
- package/dist/components/EntityCollectionView/Board.d.ts +2 -0
- package/dist/components/EntityCollectionView/BoardColumn.d.ts +42 -0
- package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +9 -0
- package/dist/components/EntityCollectionView/BoardSortableList.d.ts +14 -0
- package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +26 -0
- package/dist/components/EntityCollectionView/EntityCard.d.ts +19 -0
- package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +20 -0
- package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +31 -0
- package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -2
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +7 -3
- package/dist/components/EntityCollectionView/FiltersDialog.d.ts +14 -0
- package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +49 -0
- package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
- package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
- package/dist/components/SelectableTable/SelectableTable.d.ts +5 -1
- package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
- package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
- package/dist/components/VirtualTable/VirtualTableHeader.d.ts +2 -0
- package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -0
- package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
- package/dist/components/VirtualTable/types.d.ts +2 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/contexts/index.d.ts +10 -0
- package/dist/core/DrawerNavigationGroup.d.ts +45 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/form/validation.d.ts +3 -2
- package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
- package/dist/hooks/useCollapsedGroups.d.ts +4 -1
- package/dist/index.es.js +5239 -1590
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +5233 -1585
- package/dist/index.umd.js.map +1 -1
- package/dist/preview/PropertyPreviewProps.d.ts +5 -0
- package/dist/preview/components/DatePreview.d.ts +13 -3
- package/dist/preview/components/ImagePreview.d.ts +5 -1
- package/dist/preview/components/StorageThumbnail.d.ts +2 -1
- package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
- package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
- package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
- package/dist/types/collections.d.ts +42 -2
- package/dist/types/datasource.d.ts +0 -1
- package/dist/types/plugins.d.ts +46 -1
- package/dist/types/properties.d.ts +259 -4
- package/dist/util/__tests__/conditions.test.d.ts +1 -0
- package/dist/util/__tests__/objects.test.d.ts +1 -0
- package/dist/util/conditions.d.ts +26 -0
- package/dist/util/entities.d.ts +1 -2
- package/dist/util/index.d.ts +2 -1
- package/dist/util/property_utils.d.ts +2 -1
- package/dist/util/resolutions.d.ts +1 -1
- package/package.json +10 -7
- package/src/app/Scaffold.tsx +14 -15
- package/src/components/AIIcon.tsx +39 -0
- package/src/components/ArrayContainer.tsx +1 -4
- package/src/components/ClearFilterSortButton.tsx +19 -16
- package/src/components/ConfirmationDialog.tsx +0 -2
- package/src/components/DeleteEntityDialog.tsx +2 -4
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +74 -41
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +20 -42
- package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
- package/src/components/EntityCollectionView/Board.tsx +324 -0
- package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
- package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
- package/src/components/EntityCollectionView/BoardSortableList.tsx +172 -0
- package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
- package/src/components/EntityCollectionView/EntityCard.tsx +231 -0
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +713 -0
- package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +244 -0
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +485 -203
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +31 -19
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +84 -15
- package/src/components/EntityCollectionView/FiltersDialog.tsx +249 -0
- package/src/components/EntityCollectionView/ViewModeToggle.tsx +202 -0
- package/src/components/EntityCollectionView/board_types.ts +113 -0
- package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
- package/src/components/ErrorTooltip.tsx +2 -1
- package/src/components/HomePage/DefaultHomePage.tsx +47 -10
- package/src/components/HomePage/HomePageDnD.tsx +56 -41
- package/src/components/HomePage/NavigationCard.tsx +20 -18
- package/src/components/HomePage/NavigationGroup.tsx +17 -16
- package/src/components/HomePage/RenameGroupDialog.tsx +0 -2
- package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -10
- package/src/components/ReferenceWidget.tsx +2 -4
- package/src/components/SelectableTable/SelectableTable.tsx +75 -67
- package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +39 -40
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +38 -38
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +49 -58
- package/src/components/UnsavedChangesDialog.tsx +0 -2
- package/src/components/UserDisplay.tsx +4 -4
- package/src/components/VirtualTable/VirtualTable.tsx +170 -19
- package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
- package/src/components/VirtualTable/VirtualTableHeader.tsx +20 -11
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +158 -42
- package/src/components/VirtualTable/VirtualTableProps.tsx +14 -1
- package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +17 -4
- package/src/components/VirtualTable/types.tsx +2 -0
- package/src/components/common/useColumnsIds.tsx +95 -3
- package/src/components/index.tsx +4 -0
- package/src/contexts/BreacrumbsContext.tsx +15 -8
- package/src/contexts/index.ts +10 -0
- package/src/core/DefaultAppBar.tsx +39 -26
- package/src/core/DefaultDrawer.tsx +42 -56
- package/src/core/DrawerNavigationGroup.tsx +118 -0
- package/src/core/DrawerNavigationItem.tsx +4 -3
- package/src/core/EntityEditView.tsx +41 -43
- package/src/core/SideDialogs.tsx +4 -2
- package/src/core/index.tsx +1 -0
- package/src/form/PropertyFieldBinding.tsx +58 -43
- package/src/form/components/StorageItemPreview.tsx +2 -1
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -1
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -16
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -1
- package/src/form/field_bindings/MapFieldBinding.tsx +69 -67
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +21 -17
- package/src/form/field_bindings/TextFieldBinding.tsx +71 -35
- package/src/form/validation.ts +245 -160
- package/src/hooks/useBreadcrumbsController.tsx +18 -0
- package/src/hooks/useBuildNavigationController.tsx +42 -19
- package/src/hooks/useCollapsedGroups.ts +12 -4
- package/src/internal/useBuildDataSource.ts +69 -34
- package/src/internal/useBuildSideDialogsController.tsx +11 -8
- package/src/internal/useBuildSideEntityController.tsx +2 -4
- package/src/internal/useRestoreScroll.tsx +26 -14
- package/src/preview/PropertyPreview.tsx +40 -32
- package/src/preview/PropertyPreviewProps.tsx +6 -0
- package/src/preview/components/DatePreview.tsx +72 -4
- package/src/preview/components/EmptyValue.tsx +1 -1
- package/src/preview/components/ImagePreview.tsx +37 -21
- package/src/preview/components/StorageThumbnail.tsx +16 -12
- package/src/preview/components/UrlComponentPreview.tsx +28 -25
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
- package/src/routes/CustomCMSRoute.tsx +1 -0
- package/src/routes/FireCMSRoute.tsx +26 -13
- package/src/types/collections.ts +48 -3
- package/src/types/datasource.ts +54 -56
- package/src/types/plugins.tsx +51 -1
- package/src/types/properties.ts +347 -27
- package/src/util/__tests__/conditions.test.ts +506 -0
- package/src/util/__tests__/objects.test.ts +196 -0
- package/src/util/callbacks.ts +6 -3
- package/src/util/collections.ts +51 -6
- package/src/util/conditions.ts +339 -0
- package/src/util/entities.ts +28 -29
- package/src/util/entity_cache.ts +2 -1
- package/src/util/index.ts +2 -1
- package/src/util/objects.ts +31 -13
- package/src/util/{references.ts → previews.ts} +14 -0
- package/src/util/property_utils.tsx +36 -10
- package/src/util/resolutions.ts +57 -55
- /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
|
@@ -62,9 +62,19 @@ export function VirtualTableSelect(props: {
|
|
|
62
62
|
key={`${enumKey}`}
|
|
63
63
|
enumKey={String(enumKey)}
|
|
64
64
|
enumValues={enumValues}
|
|
65
|
-
size={small ? "small" : "medium"}/>;
|
|
65
|
+
size={small ? "small" : "medium"} />;
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
+
// When the dropdown closes (including on escape), restore focus to the trigger
|
|
69
|
+
const handleOpenChange = useCallback((open: boolean) => {
|
|
70
|
+
if (!open && ref.current) {
|
|
71
|
+
// Use setTimeout to ensure focus is restored after Radix finishes its cleanup
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
ref.current?.focus({ preventScroll: true });
|
|
74
|
+
}, 0);
|
|
75
|
+
}
|
|
76
|
+
}, []);
|
|
77
|
+
|
|
68
78
|
return (
|
|
69
79
|
multiple
|
|
70
80
|
? <MultiSelect
|
|
@@ -77,7 +87,8 @@ export function VirtualTableSelect(props: {
|
|
|
77
87
|
value={validValue
|
|
78
88
|
? ((internalValue as any[]).map(v => v.toString()))
|
|
79
89
|
: ([])}
|
|
80
|
-
onValueChange={onChange}
|
|
90
|
+
onValueChange={onChange}
|
|
91
|
+
onOpenChange={handleOpenChange}>
|
|
81
92
|
{enumValues?.map((enumConfig) => (
|
|
82
93
|
<MultiSelectItem
|
|
83
94
|
key={enumConfig.id}
|
|
@@ -85,7 +96,7 @@ export function VirtualTableSelect(props: {
|
|
|
85
96
|
<EnumValuesChip
|
|
86
97
|
enumKey={enumConfig.id}
|
|
87
98
|
enumValues={enumValues}
|
|
88
|
-
size={small ? "small" : "medium"}/>
|
|
99
|
+
size={small ? "small" : "medium"} />
|
|
89
100
|
</MultiSelectItem>
|
|
90
101
|
))}
|
|
91
102
|
</MultiSelect>
|
|
@@ -94,6 +105,7 @@ export function VirtualTableSelect(props: {
|
|
|
94
105
|
size={"large"}
|
|
95
106
|
fullWidth={true}
|
|
96
107
|
className="w-full h-full p-0 bg-transparent"
|
|
108
|
+
inputClassName="focus:ring-0 focus-visible:ring-0 outline-none focus:outline-none focus-visible:outline-none"
|
|
97
109
|
position={"item-aligned"}
|
|
98
110
|
disabled={disabled}
|
|
99
111
|
padding={false}
|
|
@@ -101,6 +113,7 @@ export function VirtualTableSelect(props: {
|
|
|
101
113
|
? internalValue?.toString()
|
|
102
114
|
: ""}
|
|
103
115
|
onValueChange={onChange}
|
|
116
|
+
onOpenChange={handleOpenChange}
|
|
104
117
|
renderValue={renderValue}>
|
|
105
118
|
{enumValues?.map((enumConfig) => (
|
|
106
119
|
<SelectItem
|
|
@@ -109,7 +122,7 @@ export function VirtualTableSelect(props: {
|
|
|
109
122
|
<EnumValuesChip
|
|
110
123
|
enumKey={enumConfig.id}
|
|
111
124
|
enumValues={enumValues}
|
|
112
|
-
size={small ? "small" : "medium"}/>
|
|
125
|
+
size={small ? "small" : "medium"} />
|
|
113
126
|
</SelectItem>
|
|
114
127
|
))}
|
|
115
128
|
</Select>
|
|
@@ -40,4 +40,6 @@ export type VirtualTableContextProps<T extends any> = {
|
|
|
40
40
|
rowClassName?: (rowData: T) => string | undefined;
|
|
41
41
|
endAdornment?: React.ReactNode;
|
|
42
42
|
AddColumnComponent?: React.ComponentType;
|
|
43
|
+
onColumnsOrderChange?: (columns: VirtualTableColumn[]) => void;
|
|
44
|
+
draggingColumnId?: string | null;
|
|
43
45
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import { EntityCollection, ResolvedEntityCollection, ResolvedProperty } from "../../types";
|
|
3
|
+
import { getPropertyInPath } from "../../util";
|
|
3
4
|
import { getSubcollectionColumnId } from "../EntityCollectionTable/internal/common";
|
|
4
5
|
import { PropertyColumnConfig } from "../EntityCollectionTable/EntityCollectionTableProps";
|
|
5
6
|
|
|
@@ -16,15 +17,45 @@ export function useColumnIds<M extends Record<string, any>>(collection: Resolved
|
|
|
16
17
|
|
|
17
18
|
function hideAndExpandKeys<M extends Record<string, any>>(collection: ResolvedEntityCollection<M>, keys: string[]): PropertyColumnConfig[] {
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
// First, figure out which spread map roots have individual child keys in the order
|
|
21
|
+
// If so, we should NOT auto-expand them - just use the explicit child keys
|
|
22
|
+
const rootsWithExplicitChildren = new Set<string>();
|
|
23
|
+
for (const key of keys) {
|
|
24
|
+
if (key.includes(".")) {
|
|
25
|
+
const rootKey = key.split(".")[0];
|
|
26
|
+
const rootProperty = collection.properties[rootKey];
|
|
27
|
+
if (rootProperty && rootProperty.dataType === "map" && rootProperty.spreadChildren && rootProperty.properties) {
|
|
28
|
+
rootsWithExplicitChildren.add(rootKey);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Track processed keys to avoid duplicates
|
|
34
|
+
const processedPropertyKeys = new Set<string>();
|
|
35
|
+
|
|
36
|
+
const result = keys.flatMap((key) => {
|
|
37
|
+
// Skip if already processed (handles duplicates in propertiesOrder)
|
|
38
|
+
if (processedPropertyKeys.has(key)) return [null];
|
|
39
|
+
|
|
40
|
+
// Check if it's a top-level property
|
|
20
41
|
const property = collection.properties[key];
|
|
21
42
|
if (property) {
|
|
43
|
+
processedPropertyKeys.add(key);
|
|
22
44
|
if (property.hideFromCollection)
|
|
23
45
|
return [null];
|
|
24
46
|
if (property.disabled && typeof property.disabled === "object" && property.disabled.hidden)
|
|
25
47
|
return [null];
|
|
48
|
+
|
|
26
49
|
if (property.dataType === "map" && property.spreadChildren && property.properties) {
|
|
27
|
-
|
|
50
|
+
// Check if this spread map has explicit child keys in propertiesOrder
|
|
51
|
+
if (rootsWithExplicitChildren.has(key)) {
|
|
52
|
+
// DON'T auto-expand - the children are explicitly listed elsewhere
|
|
53
|
+
return [null];
|
|
54
|
+
}
|
|
55
|
+
// Auto-expand all children
|
|
56
|
+
const childConfigs = getColumnKeysForProperty(property, key);
|
|
57
|
+
childConfigs.forEach(c => processedPropertyKeys.add(c.key));
|
|
58
|
+
return childConfigs;
|
|
28
59
|
}
|
|
29
60
|
return [{
|
|
30
61
|
key,
|
|
@@ -32,6 +63,33 @@ function hideAndExpandKeys<M extends Record<string, any>>(collection: ResolvedEn
|
|
|
32
63
|
}];
|
|
33
64
|
}
|
|
34
65
|
|
|
66
|
+
// Check if it's a nested key like "data.mode" (for spread map properties)
|
|
67
|
+
if (key.includes(".")) {
|
|
68
|
+
const rootKey = key.split(".")[0];
|
|
69
|
+
const rootProperty = collection.properties[rootKey];
|
|
70
|
+
|
|
71
|
+
if (rootProperty && rootProperty.dataType === "map" && rootProperty.properties) {
|
|
72
|
+
const nestedProperty = getPropertyInPath(collection.properties, key) as ResolvedProperty | undefined;
|
|
73
|
+
if (nestedProperty) {
|
|
74
|
+
processedPropertyKeys.add(key);
|
|
75
|
+
// Mark root as seen
|
|
76
|
+
processedPropertyKeys.add(rootKey);
|
|
77
|
+
|
|
78
|
+
if (nestedProperty.hideFromCollection)
|
|
79
|
+
return [null];
|
|
80
|
+
if (nestedProperty.disabled && typeof nestedProperty.disabled === "object" && nestedProperty.disabled.hidden)
|
|
81
|
+
return [null];
|
|
82
|
+
|
|
83
|
+
return [{
|
|
84
|
+
key,
|
|
85
|
+
disabled: Boolean(rootProperty.disabled) || Boolean(rootProperty.readOnly) ||
|
|
86
|
+
Boolean(nestedProperty.disabled) || Boolean(nestedProperty.readOnly)
|
|
87
|
+
}];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check additional fields
|
|
35
93
|
const additionalField = collection.additionalFields?.find(field => field.key === key);
|
|
36
94
|
if (additionalField) {
|
|
37
95
|
return [{
|
|
@@ -40,6 +98,7 @@ function hideAndExpandKeys<M extends Record<string, any>>(collection: ResolvedEn
|
|
|
40
98
|
}];
|
|
41
99
|
}
|
|
42
100
|
|
|
101
|
+
// Check subcollections
|
|
43
102
|
if (collection.subcollections) {
|
|
44
103
|
const subCollection = collection.subcollections.find(subCol => getSubcollectionColumnId(subCol) === key);
|
|
45
104
|
if (subCollection) {
|
|
@@ -50,6 +109,7 @@ function hideAndExpandKeys<M extends Record<string, any>>(collection: ResolvedEn
|
|
|
50
109
|
}
|
|
51
110
|
}
|
|
52
111
|
|
|
112
|
+
// Check collection group parent
|
|
53
113
|
if (collection.collectionGroup && key === COLLECTION_GROUP_PARENT_ID) {
|
|
54
114
|
return [{
|
|
55
115
|
key,
|
|
@@ -59,6 +119,37 @@ function hideAndExpandKeys<M extends Record<string, any>>(collection: ResolvedEn
|
|
|
59
119
|
|
|
60
120
|
return [null];
|
|
61
121
|
}).filter(Boolean) as PropertyColumnConfig[];
|
|
122
|
+
|
|
123
|
+
// Add any missing properties that weren't in propertiesOrder
|
|
124
|
+
// This ensures properties NEVER disappear
|
|
125
|
+
for (const propKey of Object.keys(collection.properties)) {
|
|
126
|
+
// Skip if already processed
|
|
127
|
+
if (processedPropertyKeys.has(propKey)) continue;
|
|
128
|
+
|
|
129
|
+
const property = collection.properties[propKey];
|
|
130
|
+
if (!property) continue;
|
|
131
|
+
if (property.hideFromCollection) continue;
|
|
132
|
+
if (property.disabled && typeof property.disabled === "object" && property.disabled.hidden) continue;
|
|
133
|
+
|
|
134
|
+
if (property.dataType === "map" && property.spreadChildren && property.properties) {
|
|
135
|
+
// For spread maps, add all children that weren't already added
|
|
136
|
+
const allChildConfigs = getColumnKeysForProperty(property, propKey);
|
|
137
|
+
for (const childConfig of allChildConfigs) {
|
|
138
|
+
if (!processedPropertyKeys.has(childConfig.key)) {
|
|
139
|
+
result.push(childConfig);
|
|
140
|
+
processedPropertyKeys.add(childConfig.key);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
result.push({
|
|
145
|
+
key: propKey,
|
|
146
|
+
disabled: Boolean(property.disabled) || Boolean(property.readOnly)
|
|
147
|
+
});
|
|
148
|
+
processedPropertyKeys.add(propKey);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return result;
|
|
62
153
|
}
|
|
63
154
|
|
|
64
155
|
function getDefaultColumnKeys<M extends Record<string, any> = any>(collection: ResolvedEntityCollection<M>, includeSubCollections: boolean) {
|
|
@@ -69,7 +160,8 @@ function getDefaultColumnKeys<M extends Record<string, any> = any>(collection: R
|
|
|
69
160
|
|
|
70
161
|
const columnIds: string[] = [
|
|
71
162
|
...propertyKeys,
|
|
72
|
-
|
|
163
|
+
// Filter out additional fields whose key already exists in propertyKeys to avoid duplicate column keys
|
|
164
|
+
...additionalFields.filter((field) => !propertyKeys.includes(field.key)).map((field) => field.key)
|
|
73
165
|
];
|
|
74
166
|
|
|
75
167
|
if (includeSubCollections) {
|
package/src/components/index.tsx
CHANGED
|
@@ -16,6 +16,8 @@ export * from "./SelectableTable/SelectableTable";
|
|
|
16
16
|
export * from "./SelectableTable/SelectableTableContext";
|
|
17
17
|
export * from "./EntityCollectionView/EntityCollectionView";
|
|
18
18
|
export * from "./EntityCollectionView/EntityCollectionViewActions";
|
|
19
|
+
export * from "./EntityCollectionView/EntityCollectionCardView";
|
|
20
|
+
export * from "./EntityCollectionView/EntityCard";
|
|
19
21
|
export * from "./EntityCollectionView/useSelectionController";
|
|
20
22
|
|
|
21
23
|
export * from "./PropertyConfigBadge";
|
|
@@ -38,3 +40,5 @@ export * from "./SearchIconsView";
|
|
|
38
40
|
export * from "./FieldCaption";
|
|
39
41
|
|
|
40
42
|
export * from "./EntityPreview";
|
|
43
|
+
|
|
44
|
+
export * from "./AIIcon";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
1
|
+
import React, { useCallback, useState } from "react";
|
|
2
2
|
import { BreadcrumbEntry, BreadcrumbsController } from "../hooks/useBreadcrumbsController";
|
|
3
3
|
|
|
4
|
-
const DEFAULT_BREADCRUMBS_CONTROLLER = {
|
|
4
|
+
const DEFAULT_BREADCRUMBS_CONTROLLER: BreadcrumbsController = {
|
|
5
5
|
breadcrumbs: [],
|
|
6
|
-
set: (
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
set: () => {
|
|
7
|
+
},
|
|
8
|
+
updateCount: () => {
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
11
|
|
|
@@ -19,17 +19,24 @@ export const BreadcrumbsProvider: React.FC<BreadcrumbsProviderProps> = ({ childr
|
|
|
19
19
|
|
|
20
20
|
const [breadcrumbs, setBreadcrumbs] = useState<BreadcrumbEntry[]>([]);
|
|
21
21
|
|
|
22
|
-
const set = (props: {
|
|
22
|
+
const set = useCallback((props: {
|
|
23
23
|
breadcrumbs: BreadcrumbEntry[];
|
|
24
24
|
}) => {
|
|
25
25
|
setBreadcrumbs(props.breadcrumbs);
|
|
26
|
-
};
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
const updateCount = useCallback((id: string, count: number | null | undefined) => {
|
|
29
|
+
setBreadcrumbs(prev => prev.map(entry =>
|
|
30
|
+
entry.id === id ? { ...entry, count } : entry
|
|
31
|
+
));
|
|
32
|
+
}, []);
|
|
27
33
|
|
|
28
34
|
return (
|
|
29
35
|
<BreadcrumbContext.Provider
|
|
30
36
|
value={{
|
|
31
37
|
breadcrumbs,
|
|
32
|
-
set
|
|
38
|
+
set,
|
|
39
|
+
updateCount
|
|
33
40
|
}}
|
|
34
41
|
>
|
|
35
42
|
{children}
|
package/src/contexts/index.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
export * from "./SnackbarProvider";
|
|
2
2
|
export * from "./ModeController";
|
|
3
3
|
export * from "./AuthControllerContext";
|
|
4
|
+
export * from "./DataSourceContext";
|
|
5
|
+
export * from "./NavigationContext";
|
|
6
|
+
export * from "./CustomizationControllerContext";
|
|
7
|
+
export * from "./SideEntityControllerContext";
|
|
8
|
+
export * from "./SideDialogsControllerContext";
|
|
9
|
+
export * from "./AnalyticsContext";
|
|
10
|
+
export * from "./StorageSourceContext";
|
|
11
|
+
export * from "./UserConfigurationPersistenceContext";
|
|
12
|
+
export * from "./DialogsProvider";
|
|
13
|
+
export * from "./InternalUserManagementContext";
|
|
@@ -57,16 +57,16 @@ export type DefaultAppBarProps<ADDITIONAL_PROPS = object> = {
|
|
|
57
57
|
|
|
58
58
|
*/
|
|
59
59
|
export const DefaultAppBar = function DefaultAppBar({
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
title,
|
|
61
|
+
endAdornment,
|
|
62
|
+
startAdornment,
|
|
63
|
+
dropDownActions,
|
|
64
|
+
includeModeToggle = true,
|
|
65
|
+
className,
|
|
66
|
+
style,
|
|
67
|
+
user: userProp,
|
|
68
|
+
logo: logoProp,
|
|
69
|
+
}: DefaultAppBarProps) {
|
|
70
70
|
|
|
71
71
|
const {
|
|
72
72
|
hasDrawer,
|
|
@@ -103,7 +103,7 @@ export const DefaultAppBar = function DefaultAppBar({
|
|
|
103
103
|
</Avatar>;
|
|
104
104
|
} else if (user === undefined || authController.initialLoading) {
|
|
105
105
|
avatarComponent = <div className={"p-1 flex justify-center"}>
|
|
106
|
-
<Skeleton className={"w-10 h-10 rounded-full"}/>
|
|
106
|
+
<Skeleton className={"w-10 h-10 rounded-full"} />
|
|
107
107
|
</div>;
|
|
108
108
|
} else {
|
|
109
109
|
avatarComponent = null;
|
|
@@ -131,13 +131,13 @@ export const DefaultAppBar = function DefaultAppBar({
|
|
|
131
131
|
<div className={"flex flex-row gap-4"}>
|
|
132
132
|
{!hasDrawer && (logo
|
|
133
133
|
? <img src={logo}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
: <FireCMSLogo width={"32px"} height={"32px"}/>)}
|
|
134
|
+
alt="Logo"
|
|
135
|
+
className={cls("w-[32px] h-[32px] object-contain")} />
|
|
136
|
+
: <FireCMSLogo width={"32px"} height={"32px"} />)}
|
|
137
137
|
|
|
138
138
|
{typeof title === "string"
|
|
139
139
|
? <Typography variant="subtitle1"
|
|
140
|
-
|
|
140
|
+
noWrap>
|
|
141
141
|
{title}
|
|
142
142
|
</Typography>
|
|
143
143
|
: title}
|
|
@@ -146,7 +146,7 @@ export const DefaultAppBar = function DefaultAppBar({
|
|
|
146
146
|
</div>}
|
|
147
147
|
|
|
148
148
|
{(breadcrumbs.breadcrumbs ?? []).length > 0 && <div className="mr-8 hidden lg:block">
|
|
149
|
-
<div className={"flex flex-row gap-2"}>
|
|
149
|
+
<div className={"flex flex-row gap-2 items-center"}>
|
|
150
150
|
{breadcrumbs.breadcrumbs.map((breadcrumb, index) => {
|
|
151
151
|
return <React.Fragment key={breadcrumb.url + "_" + index}>
|
|
152
152
|
<Typography variant={"caption"} color={"secondary"}>
|
|
@@ -157,18 +157,31 @@ export const DefaultAppBar = function DefaultAppBar({
|
|
|
157
157
|
className="visited:text-inherit visited:dark:text-inherit block"
|
|
158
158
|
to={breadcrumb.url}
|
|
159
159
|
>
|
|
160
|
-
<
|
|
161
|
-
{
|
|
162
|
-
|
|
160
|
+
<div className="flex flex-row items-center gap-2 whitespace-nowrap">
|
|
161
|
+
<Typography variant={"body2"}>
|
|
162
|
+
{breadcrumb.title}
|
|
163
|
+
</Typography>
|
|
164
|
+
{/* Show count badge for collection breadcrumbs: undefined = not applicable, null = loading, number = count */}
|
|
165
|
+
{breadcrumb.count !== undefined && (
|
|
166
|
+
breadcrumb.count !== null ? (
|
|
167
|
+
<span className="text-xs text-surface-accent-500 dark:text-surface-accent-400 bg-surface-100 dark:bg-surface-700 px-1 py-0 rounded">
|
|
168
|
+
{breadcrumb.count}
|
|
169
|
+
</span>
|
|
170
|
+
) : (
|
|
171
|
+
<Skeleton className="w-8 h-4 rounded-md" />
|
|
172
|
+
)
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
163
175
|
</Link>
|
|
164
176
|
</React.Fragment>;
|
|
165
177
|
})}
|
|
166
178
|
</div>
|
|
167
179
|
</div>}
|
|
168
180
|
|
|
181
|
+
|
|
169
182
|
{startAdornment}
|
|
170
183
|
|
|
171
|
-
<div className={"flex-grow"}/>
|
|
184
|
+
<div className={"flex-grow"} />
|
|
172
185
|
|
|
173
186
|
{endAdornment &&
|
|
174
187
|
<ErrorBoundary>
|
|
@@ -182,13 +195,13 @@ export const DefaultAppBar = function DefaultAppBar({
|
|
|
182
195
|
aria-label="Open drawer"
|
|
183
196
|
size="large">
|
|
184
197
|
{mode === "dark"
|
|
185
|
-
? <DarkModeIcon/>
|
|
186
|
-
: <LightModeIcon/>}
|
|
198
|
+
? <DarkModeIcon />
|
|
199
|
+
: <LightModeIcon />}
|
|
187
200
|
</IconButton>}>
|
|
188
|
-
<MenuItem onClick={() => setMode("dark")}><DarkModeIcon size={"smallest"}/> Dark</MenuItem>
|
|
189
|
-
<MenuItem onClick={() => setMode("light")}><LightModeIcon size={"smallest"}/> Light </MenuItem>
|
|
201
|
+
<MenuItem onClick={() => setMode("dark")}><DarkModeIcon size={"smallest"} /> Dark</MenuItem>
|
|
202
|
+
<MenuItem onClick={() => setMode("light")}><LightModeIcon size={"smallest"} /> Light </MenuItem>
|
|
190
203
|
<MenuItem onClick={() => setMode("system")}> <BrightnessMediumIcon
|
|
191
|
-
size={"smallest"}/>System</MenuItem>
|
|
204
|
+
size={"smallest"} />System</MenuItem>
|
|
192
205
|
</Menu>}
|
|
193
206
|
|
|
194
207
|
<Menu trigger={avatarComponent}>
|
|
@@ -208,7 +221,7 @@ export const DefaultAppBar = function DefaultAppBar({
|
|
|
208
221
|
// replace current route with home
|
|
209
222
|
navigate("/");
|
|
210
223
|
}}>
|
|
211
|
-
<LogoutIcon/>
|
|
224
|
+
<LogoutIcon />
|
|
212
225
|
Log Out
|
|
213
226
|
</MenuItem>}
|
|
214
227
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
|
|
3
|
-
import { useLargeLayout, useNavigationController } from "../hooks";
|
|
3
|
+
import { useCollapsedGroups, useLargeLayout, useNavigationController } from "../hooks";
|
|
4
4
|
|
|
5
5
|
import { Link, useNavigate } from "react-router-dom";
|
|
6
6
|
import { CMSAnalyticsEvent, NavigationEntry, NavigationResult } from "../types";
|
|
7
7
|
import { IconForView } from "../util";
|
|
8
|
-
import { cls, IconButton, Menu, MenuItem, MoreVertIcon, Tooltip
|
|
8
|
+
import { cls, IconButton, Menu, MenuItem, MoreVertIcon, Tooltip } from "@firecms/ui";
|
|
9
9
|
import { useAnalyticsController } from "../hooks/useAnalyticsController";
|
|
10
|
-
import {
|
|
10
|
+
import { DrawerNavigationGroup } from "./DrawerNavigationGroup";
|
|
11
11
|
import { FireCMSLogo } from "../components";
|
|
12
12
|
import { useApp } from "../app/useApp";
|
|
13
13
|
|
|
@@ -16,9 +16,9 @@ import { useApp } from "../app/useApp";
|
|
|
16
16
|
* @group Core
|
|
17
17
|
*/
|
|
18
18
|
export function DefaultDrawer({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
className,
|
|
20
|
+
style,
|
|
21
|
+
}: {
|
|
22
22
|
className?: string
|
|
23
23
|
style?: React.CSSProperties,
|
|
24
24
|
}) {
|
|
@@ -50,20 +50,10 @@ export function DefaultDrawer({
|
|
|
50
50
|
const adminViews = navigationEntries.filter(e => e.type === "admin") ?? [];
|
|
51
51
|
const groupsWithoutAdmin = groups.filter(g => g !== "Admin");
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return <div
|
|
56
|
-
className="pl-6 pr-8 py-4 flex flex-row items-center">
|
|
57
|
-
<Typography variant={"caption"}
|
|
58
|
-
color={"secondary"}
|
|
59
|
-
className="font-medium flex-grow line-clamp-1">
|
|
60
|
-
{group ? group.toUpperCase() : "Views".toUpperCase()}
|
|
61
|
-
</Typography>
|
|
53
|
+
// Collapsible groups state - using "drawer" namespace for independent state from home page
|
|
54
|
+
const { isGroupCollapsed, toggleGroupCollapsed } = useCollapsedGroups(groupsWithoutAdmin, "drawer");
|
|
62
55
|
|
|
63
|
-
|
|
64
|
-
}, [drawerOpen]);
|
|
65
|
-
|
|
66
|
-
const onClick = (view: NavigationEntry) => {
|
|
56
|
+
const onItemClick = (view: NavigationEntry) => {
|
|
67
57
|
const eventName: CMSAnalyticsEvent = view.type === "collection"
|
|
68
58
|
? "drawer_navigate_to_collection"
|
|
69
59
|
: (view.type === "view" ? "drawer_navigate_to_view" : "unmapped_event");
|
|
@@ -76,33 +66,29 @@ export function DefaultDrawer({
|
|
|
76
66
|
<>
|
|
77
67
|
<div className={cls("flex flex-col h-full relative flex-grow w-full", className)} style={style}>
|
|
78
68
|
|
|
79
|
-
<DrawerLogo logo={logo}/>
|
|
69
|
+
<DrawerLogo logo={logo} />
|
|
80
70
|
|
|
81
71
|
<div className={"mt-4 flex-grow overflow-scroll no-scrollbar"}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
{groupsWithoutAdmin.map((group) =>
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
url={view.url}
|
|
103
|
-
name={view.name}/>)}
|
|
104
|
-
</div>
|
|
105
|
-
))}
|
|
72
|
+
style={{
|
|
73
|
+
maskImage: "linear-gradient(to bottom, transparent 0, black 20px, black calc(100% - 20px), transparent 100%)",
|
|
74
|
+
}}>
|
|
75
|
+
|
|
76
|
+
{groupsWithoutAdmin.map((group) => {
|
|
77
|
+
const entriesInGroup = Object.values(navigationEntries).filter(e => e.group === group);
|
|
78
|
+
return (
|
|
79
|
+
<DrawerNavigationGroup
|
|
80
|
+
key={`drawer_group_${group}`}
|
|
81
|
+
group={group}
|
|
82
|
+
entries={entriesInGroup}
|
|
83
|
+
collapsed={isGroupCollapsed(group)}
|
|
84
|
+
onToggleCollapsed={() => toggleGroupCollapsed(group)}
|
|
85
|
+
drawerOpen={drawerOpen}
|
|
86
|
+
tooltipsOpen={tooltipsOpen}
|
|
87
|
+
adminMenuOpen={adminMenuOpen}
|
|
88
|
+
onItemClick={onItemClick}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
})}
|
|
106
92
|
|
|
107
93
|
</div>
|
|
108
94
|
|
|
@@ -115,9 +101,9 @@ export function DefaultDrawer({
|
|
|
115
101
|
shape={"square"}
|
|
116
102
|
className={"m-4 text-surface-900 dark:text-white w-fit"}>
|
|
117
103
|
<Tooltip title={"Admin"}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<MoreVertIcon/>
|
|
104
|
+
open={tooltipsOpen}
|
|
105
|
+
side={"right"} sideOffset={28}>
|
|
106
|
+
<MoreVertIcon />
|
|
121
107
|
</Tooltip>
|
|
122
108
|
{drawerOpen && <div
|
|
123
109
|
className={cls(
|
|
@@ -132,10 +118,10 @@ export function DefaultDrawer({
|
|
|
132
118
|
<MenuItem
|
|
133
119
|
onClick={(event) => {
|
|
134
120
|
event.preventDefault();
|
|
135
|
-
navigate(entry.url);
|
|
121
|
+
navigate(entry.url);
|
|
136
122
|
}}
|
|
137
123
|
key={entry.id}>
|
|
138
|
-
{<IconForView collectionOrView={entry.view}/>}
|
|
124
|
+
{<IconForView collectionOrView={entry.view} />}
|
|
139
125
|
{entry.name}
|
|
140
126
|
</MenuItem>)}
|
|
141
127
|
|
|
@@ -167,17 +153,17 @@ export function DrawerLogo({ logo }: {
|
|
|
167
153
|
className={cls("cursor-pointer rounded ml-3 mr-1")}>
|
|
168
154
|
|
|
169
155
|
<Tooltip title={"Home"}
|
|
170
|
-
|
|
171
|
-
|
|
156
|
+
sideOffset={20}
|
|
157
|
+
side="right">
|
|
172
158
|
<Link
|
|
173
159
|
className={"block"}
|
|
174
160
|
to={navigation.basePath}>
|
|
175
161
|
{logo
|
|
176
162
|
? <img src={logo}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
: <FireCMSLogo/>}
|
|
163
|
+
alt="Logo"
|
|
164
|
+
className={cls("max-w-full max-h-full transition-all object-contain",
|
|
165
|
+
drawerOpen ? "w-[96px] h-[96px]" : "w-[32px] h-[32px]")} />
|
|
166
|
+
: <FireCMSLogo />}
|
|
181
167
|
|
|
182
168
|
</Link>
|
|
183
169
|
</Tooltip>
|