@firecms/collection_editor 3.0.0-beta.13 → 3.0.0-beta.15

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.
Files changed (44) hide show
  1. package/README.md +165 -1
  2. package/dist/ConfigControllerProvider.d.ts +0 -1
  3. package/dist/index.es.js +1620 -948
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/index.umd.js +1616 -947
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/types/collection_editor_controller.d.ts +0 -1
  8. package/dist/types/collection_inference.d.ts +3 -0
  9. package/dist/types/config_controller.d.ts +3 -1
  10. package/dist/ui/EditorEntityAction.d.ts +2 -0
  11. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +1 -1
  12. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
  13. package/dist/ui/collection_editor/EntityActionsEditTab.d.ts +4 -0
  14. package/dist/ui/collection_editor/EntityActionsSelectDialog.d.ts +4 -0
  15. package/dist/ui/collection_editor/PropertyTree.d.ts +2 -3
  16. package/dist/ui/collection_editor/properties/ReferencePropertyField.d.ts +2 -1
  17. package/dist/useCollectionEditorPlugin.d.ts +3 -3
  18. package/package.json +8 -9
  19. package/src/ConfigControllerProvider.tsx +0 -5
  20. package/src/types/collection_editor_controller.tsx +0 -2
  21. package/src/types/collection_inference.ts +3 -0
  22. package/src/types/config_controller.tsx +4 -1
  23. package/src/ui/EditorCollectionAction.tsx +2 -7
  24. package/src/ui/EditorEntityAction.tsx +51 -0
  25. package/src/ui/HomePageEditorCollectionAction.tsx +2 -1
  26. package/src/ui/collection_editor/CollectionDetailsForm.tsx +69 -37
  27. package/src/ui/collection_editor/CollectionEditorDialog.tsx +18 -5
  28. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +15 -25
  29. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +8 -6
  30. package/src/ui/collection_editor/EntityActionsEditTab.tsx +163 -0
  31. package/src/ui/collection_editor/EntityActionsSelectDialog.tsx +41 -0
  32. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +5 -2
  33. package/src/ui/collection_editor/GetCodeDialog.tsx +5 -3
  34. package/src/ui/collection_editor/PropertyEditView.tsx +11 -3
  35. package/src/ui/collection_editor/PropertyFieldPreview.tsx +1 -0
  36. package/src/ui/collection_editor/PropertyTree.tsx +183 -139
  37. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +6 -2
  38. package/src/ui/collection_editor/properties/MapPropertyField.tsx +1 -1
  39. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +5 -3
  40. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +2 -0
  41. package/src/ui/collection_editor/utils/supported_fields.tsx +1 -0
  42. package/src/ui/collection_editor/utils/update_property_for_widget.ts +9 -0
  43. package/src/useCollectionEditorPlugin.tsx +12 -7
  44. package/src/utils/collections.ts +15 -5
@@ -4,14 +4,30 @@ import equal from "react-fast-compare"
4
4
  import {
5
5
  AdditionalFieldDelegate,
6
6
  CMSType,
7
- ErrorBoundary,
8
7
  isPropertyBuilder,
9
8
  PropertiesOrBuilders,
10
9
  PropertyOrBuilder
11
10
  } from "@firecms/core";
12
11
  import { AutorenewIcon, defaultBorderMixin, DragHandleIcon, IconButton, RemoveIcon, Tooltip } from "@firecms/ui";
13
12
  import { NonEditablePropertyPreview, PropertyFieldPreview } from "./PropertyFieldPreview";
14
- import { DragDropContext, Draggable, DraggableProvided, Droppable } from "@hello-pangea/dnd";
13
+ import {
14
+ closestCenter,
15
+ DndContext,
16
+ DragEndEvent,
17
+ KeyboardSensor,
18
+ PointerSensor,
19
+ useSensor,
20
+ useSensors
21
+ } from "@dnd-kit/core";
22
+ import {
23
+ SortableContext,
24
+ sortableKeyboardCoordinates,
25
+ useSortable,
26
+ verticalListSortingStrategy
27
+ } from "@dnd-kit/sortable";
28
+ import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
29
+
30
+ import { CSS } from "@dnd-kit/utilities";
15
31
  import { getFullId, getFullIdPath } from "./util";
16
32
  import { editableProperty } from "../../utils/entities";
17
33
 
@@ -48,103 +64,109 @@ export const PropertyTree = React.memo(
48
64
 
49
65
  const propertiesOrder = propertiesOrderProp ?? Object.keys(properties);
50
66
 
51
- const onDragEnd = (result: any) => {
52
- // dropped outside the list
53
- if (!result.destination) {
67
+ const sensors = useSensors(
68
+ useSensor(PointerSensor, {
69
+ activationConstraint: {
70
+ distance: 5,
71
+ }
72
+ }),
73
+ useSensor(KeyboardSensor, {
74
+ coordinateGetter: sortableKeyboardCoordinates,
75
+ })
76
+ );
77
+
78
+ const handleDragEnd = (event: DragEndEvent) => {
79
+ const {
80
+ active,
81
+ over
82
+ } = event;
83
+
84
+ if (!over || active.id === over.id) {
54
85
  return;
55
86
  }
56
- const startIndex = result.source.index;
57
- const endIndex = result.destination.index;
58
-
59
- const newPropertiesOrder = Array.from(propertiesOrder);
60
- const [removed] = newPropertiesOrder.splice(startIndex, 1);
61
- newPropertiesOrder.splice(endIndex, 0, removed);
62
- if (onPropertyMove)
63
- onPropertyMove(newPropertiesOrder, namespace);
64
- }
87
+
88
+ const activeId = String(active.id);
89
+ const overId = String(over.id);
90
+
91
+ // Extract property keys from the full IDs
92
+ const activeKey = activeId.includes(".") ? activeId.split(".").pop() : activeId;
93
+ const overKey = overId.includes(".") ? overId.split(".").pop() : overId;
94
+
95
+ if (!activeKey || !overKey) return;
96
+
97
+ const oldIndex = propertiesOrder.indexOf(activeKey);
98
+ const newIndex = propertiesOrder.indexOf(overKey);
99
+
100
+ if (oldIndex !== -1 && newIndex !== -1) {
101
+ const newPropertiesOrder = [...propertiesOrder];
102
+ const [removed] = newPropertiesOrder.splice(oldIndex, 1);
103
+ newPropertiesOrder.splice(newIndex, 0, removed);
104
+
105
+ if (onPropertyMove) {
106
+ onPropertyMove(newPropertiesOrder, namespace);
107
+ }
108
+ }
109
+ };
110
+
111
+ const items = propertiesOrder.map(key => getFullId(key, namespace));
65
112
 
66
113
  return (
67
- <>
68
-
69
- <DragDropContext onDragEnd={onDragEnd}>
70
- <Droppable droppableId={`droppable_${namespace}`}>
71
- {(droppableProvided, droppableSnapshot) => (
72
- <div
73
- {...droppableProvided.droppableProps}
74
- ref={droppableProvided.innerRef}
75
- className={className}>
76
- {propertiesOrder && propertiesOrder
77
- .map((propertyKey: string, index: number) => {
78
- const property = properties[propertyKey] as PropertyOrBuilder;
79
- const additionalField = additionalFields?.find(field => field.key === propertyKey);
80
-
81
- if (!property && !additionalField) {
82
- console.warn(`Property ${propertyKey} not found in properties or additionalFields`);
83
- return null;
84
- }
85
- return (
86
- <Draggable
87
- key={`array_field_${namespace}_${propertyKey}}`}
88
- draggableId={`array_field_${namespace}_${propertyKey}}`}
89
- index={index}>
90
- {(provided, snapshot) => {
91
- return (
92
- <ErrorBoundary>
93
- <PropertyTreeEntry
94
- propertyKey={propertyKey as string}
95
- propertyOrBuilder={property}
96
- additionalField={additionalField}
97
- provided={provided}
98
- errors={errors}
99
- namespace={namespace}
100
- inferredPropertyKeys={inferredPropertyKeys}
101
- onPropertyMove={onPropertyMove}
102
- onPropertyRemove={onPropertyRemove}
103
- onPropertyClick={snapshot.isDragging ? undefined : onPropertyClick}
104
- selectedPropertyKey={selectedPropertyKey}
105
- collectionEditable={collectionEditable}
106
- />
107
- </ErrorBoundary>
108
- );
109
- }}
110
- </Draggable>);
111
- }).filter(Boolean)}
112
-
113
- {droppableProvided.placeholder}
114
-
115
- </div>
116
- )}
117
- </Droppable>
118
- </DragDropContext>
119
-
120
- </>
114
+ <DndContext
115
+ sensors={sensors}
116
+ collisionDetection={closestCenter}
117
+ onDragEnd={handleDragEnd}
118
+ modifiers={[restrictToVerticalAxis]}
119
+ >
120
+ <SortableContext
121
+ items={items}
122
+ strategy={verticalListSortingStrategy}
123
+ >
124
+ <div className={className}>
125
+
126
+ {propertiesOrder && propertiesOrder
127
+ .map((propertyKey: string, index: number) => {
128
+ const property = properties[propertyKey] as PropertyOrBuilder;
129
+ const additionalField = additionalFields?.find(field => field.key === propertyKey);
130
+
131
+ if (!property && !additionalField) {
132
+ console.warn(`Property ${propertyKey} not found in properties or additionalFields`);
133
+ return null;
134
+ }
135
+
136
+ const id = getFullId(propertyKey, namespace);
137
+
138
+ return (
139
+ <PropertyTreeEntry
140
+ key={id}
141
+ id={id}
142
+ propertyKey={propertyKey}
143
+ propertyOrBuilder={property}
144
+ additionalField={additionalField}
145
+ errors={errors}
146
+ namespace={namespace}
147
+ inferredPropertyKeys={inferredPropertyKeys}
148
+ onPropertyMove={onPropertyMove}
149
+ onPropertyRemove={onPropertyRemove}
150
+ onPropertyClick={onPropertyClick}
151
+ selectedPropertyKey={selectedPropertyKey}
152
+ collectionEditable={collectionEditable}
153
+ />
154
+ );
155
+ }).filter(Boolean)}
156
+ </div>
157
+ </SortableContext>
158
+ </DndContext>
121
159
  );
122
160
  },
123
- (prevProps, nextProps) => {
124
-
125
- const isSelected = nextProps.selectedPropertyKey?.startsWith(nextProps.namespace ?? "");
126
- const wasSelected = prevProps.selectedPropertyKey?.startsWith(prevProps.namespace ?? "");
127
- if (isSelected || wasSelected)
128
- return false;
129
-
130
- return equal(prevProps.properties, nextProps.properties) &&
131
- prevProps.propertiesOrder === nextProps.propertiesOrder &&
132
- equal(prevProps.additionalFields, nextProps.additionalFields) &&
133
- equal(prevProps.errors, nextProps.errors) &&
134
- equal(prevProps.onPropertyClick, nextProps.onPropertyClick) &&
135
- // equal(prevProps.onPropertyMove, nextProps.onPropertyMove) &&
136
- // equal(prevProps.onPropertyRemove, nextProps.onPropertyRemove) &&
137
- prevProps.namespace === nextProps.namespace &&
138
- prevProps.collectionEditable === nextProps.collectionEditable;
139
- }
161
+ equal
140
162
  );
141
163
 
142
164
  export function PropertyTreeEntry({
165
+ id,
143
166
  propertyKey,
144
167
  namespace,
145
168
  propertyOrBuilder,
146
169
  additionalField,
147
- provided,
148
170
  selectedPropertyKey,
149
171
  errors,
150
172
  onPropertyClick,
@@ -153,12 +175,12 @@ export function PropertyTreeEntry({
153
175
  inferredPropertyKeys,
154
176
  collectionEditable
155
177
  }: {
178
+ id: string;
156
179
  propertyKey: string;
157
180
  namespace?: string;
158
181
  propertyOrBuilder: PropertyOrBuilder;
159
182
  additionalField?: AdditionalFieldDelegate<any>;
160
183
  selectedPropertyKey?: string;
161
- provided: DraggableProvided;
162
184
  errors: Record<string, any>;
163
185
  onPropertyClick?: (propertyKey: string, namespace?: string) => void;
164
186
  onPropertyMove?: (propertiesOrder: string[], namespace?: string) => void;
@@ -167,8 +189,27 @@ export function PropertyTreeEntry({
167
189
  collectionEditable: boolean;
168
190
  }) {
169
191
 
192
+ const {
193
+ attributes,
194
+ listeners,
195
+ setNodeRef,
196
+ transform,
197
+ transition,
198
+ isDragging
199
+ } = useSortable({
200
+ id
201
+ });
202
+
203
+ const style = {
204
+ // Key change: use Translate instead of Transform to prevent stretching
205
+ transform: CSS.Translate.toString(transform),
206
+ transition,
207
+ zIndex: isDragging ? 10 : undefined,
208
+ position: "relative" as const,
209
+ };
210
+
170
211
  const isPropertyInferred = inferredPropertyKeys?.includes(namespace ? `${namespace}.${propertyKey}` : propertyKey);
171
- const fullId = getFullId(propertyKey, namespace);
212
+ const fullId = id;
172
213
  const fullIdPath = getFullIdPath(propertyKey, namespace);
173
214
  const hasError = fullIdPath in errors;
174
215
 
@@ -190,65 +231,68 @@ export function PropertyTreeEntry({
190
231
  }
191
232
  }
192
233
 
193
- // const hasError = fullId ? getIn(errors, idToPropertiesPath(fullId)) : false;
194
234
  const selected = selectedPropertyKey === fullId;
195
235
  const editable = propertyOrBuilder && ((collectionEditable && !isPropertyBuilder(propertyOrBuilder)) || editableProperty(propertyOrBuilder));
196
236
 
197
237
  return (
198
238
  <div
199
- ref={provided.innerRef}
200
- {...provided.draggableProps}
201
- {...provided.dragHandleProps}
239
+ ref={setNodeRef}
240
+ style={style}
202
241
  className="relative -ml-8"
203
242
  >
204
- {subtree && <div
205
- className={"absolute border-l " + defaultBorderMixin}
206
- style={{
207
- left: "32px",
208
- top: "64px",
209
- bottom: "16px"
210
- }}/>}
211
-
212
- {!isPropertyBuilder(propertyOrBuilder) && !additionalField && editable
213
- ? <PropertyFieldPreview
214
- property={propertyOrBuilder}
215
- onClick={onPropertyClick ? () => onPropertyClick(propertyKey, namespace) : undefined}
216
- includeName={true}
217
- selected={selected}
218
- hasError={hasError}/>
219
- : <NonEditablePropertyPreview name={propertyKey}
220
- property={propertyOrBuilder}
221
- onClick={onPropertyClick ? () => onPropertyClick(propertyKey, namespace) : undefined}
222
- selected={selected}/>}
223
-
224
- <div className="absolute top-2 right-2 flex flex-row ">
225
-
226
- {isPropertyInferred && <Tooltip title={"Inferred property"}>
227
- <AutorenewIcon size="small" className={"p-2"}/>
228
- </Tooltip>}
229
-
230
- {onPropertyRemove && <Tooltip title={"Remove"}
231
- asChild={true}>
232
- <IconButton size="small"
233
- color="inherit"
234
- onClick={() => onPropertyRemove(propertyKey, namespace)}>
235
- <RemoveIcon size={"small"}/>
236
- </IconButton>
237
- </Tooltip>}
238
-
239
- {onPropertyMove && <Tooltip title={"Move"}
240
- asChild={true}>
241
- <IconButton
242
- component={"span"}
243
- size="small"
244
- >
245
- <DragHandleIcon size={"small"}/>
246
- </IconButton>
247
- </Tooltip>}
248
- </div>
243
+ <div className="relative">
244
+ {subtree && <div
245
+ className={"absolute border-l " + defaultBorderMixin}
246
+ style={{
247
+ left: "32px",
248
+ top: "64px",
249
+ bottom: "16px"
250
+ }}/>}
249
251
 
250
- {subtree && <div className={"ml-16"}>{subtree}</div>}
252
+ <div>
253
+ {!isPropertyBuilder(propertyOrBuilder) && !additionalField && editable
254
+ ? <PropertyFieldPreview
255
+ property={propertyOrBuilder}
256
+ onClick={onPropertyClick ? () => onPropertyClick(propertyKey, namespace) : undefined}
257
+ includeName={true}
258
+ selected={selected}
259
+ hasError={hasError}/>
260
+ : <NonEditablePropertyPreview name={propertyKey}
261
+ property={propertyOrBuilder}
262
+ onClick={onPropertyClick ? () => onPropertyClick(propertyKey, namespace) : undefined}
263
+ selected={selected}/>}
264
+ </div>
265
+
266
+ <div className="absolute top-2 right-2 flex flex-row">
267
+ {isPropertyInferred && <Tooltip title={"Inferred property"}>
268
+ <AutorenewIcon size="small" className={"p-2"}/>
269
+ </Tooltip>}
270
+
271
+ {onPropertyRemove && !isPropertyInferred && <Tooltip title={"Remove"}
272
+ asChild={true}>
273
+ <IconButton size="small"
274
+ color="inherit"
275
+ onClick={() => onPropertyRemove(propertyKey, namespace)}>
276
+ <RemoveIcon size={"small"}/>
277
+ </IconButton>
278
+ </Tooltip>}
279
+
280
+ {onPropertyMove && <Tooltip title={"Move"}
281
+ asChild={true}>
282
+ <IconButton
283
+ component={"span"}
284
+ size="small"
285
+ {...attributes}
286
+ {...listeners}
287
+ >
288
+ <DragHandleIcon size={"small"}/>
289
+ </IconButton>
290
+ </Tooltip>}
291
+
292
+ </div>
293
+
294
+ {subtree && <div className={"ml-16"}>{subtree}</div>}
295
+ </div>
251
296
  </div>
252
297
  );
253
-
254
298
  }
@@ -37,8 +37,12 @@ export function UnsavedChangesDialog({
37
37
  </DialogContent>
38
38
 
39
39
  <DialogActions>
40
- <Button variant="text" onClick={handleCancel} autoFocus> Cancel </Button>
41
- <Button onClick={handleOk}> Ok </Button>
40
+ <Button variant="text"
41
+ color={"primary"}
42
+ onClick={handleCancel} autoFocus> Cancel </Button>
43
+ <Button
44
+ color={"primary"}
45
+ onClick={handleOk}> Ok </Button>
42
46
  </DialogActions>
43
47
  </Dialog>
44
48
  );
@@ -107,7 +107,7 @@ export function MapPropertyField({ disabled, getData, allowDataInference, proper
107
107
  <div className={"col-span-12"}>
108
108
  <BooleanSwitchWithLabel
109
109
  position={"start"}
110
- size={"small"}
110
+ size={"medium"}
111
111
  label="Spread children as columns"
112
112
  onValueChange={(v) => setFieldValue("spreadChildren", v)}
113
113
  value={values.spreadChildren ?? false}
@@ -7,12 +7,14 @@ export function ReferencePropertyField({
7
7
  existing,
8
8
  multiple,
9
9
  disabled,
10
- showErrors
10
+ showErrors,
11
+ asString
11
12
  }: {
12
13
  existing: boolean,
13
14
  multiple: boolean,
14
15
  disabled: boolean,
15
- showErrors: boolean
16
+ showErrors: boolean,
17
+ asString?: boolean
16
18
  }) {
17
19
 
18
20
  const {
@@ -28,7 +30,7 @@ export function ReferencePropertyField({
28
30
  <CircularProgress/>
29
31
  </div>;
30
32
 
31
- const pathPath = multiple ? "of.path" : "path";
33
+ const pathPath = asString ? "reference.path" : (multiple ? "of.path" : "path") ;
32
34
  const pathValue: string | undefined = getIn(values, pathPath);
33
35
  const pathError: string | undefined = showErrors && getIn(errors, pathPath);
34
36
 
@@ -19,6 +19,7 @@ export function AdvancedPropertyValidation({ disabled }: {
19
19
  {({ field, form }: FormexFieldProps) => {
20
20
  return <SwitchControl
21
21
  label={"Hide from collection"}
22
+ size={"medium"}
22
23
  disabled={disabled}
23
24
  form={form}
24
25
  tooltip={"Hide this field from the collection view. It will still be visible in the form view"}
@@ -33,6 +34,7 @@ export function AdvancedPropertyValidation({ disabled }: {
33
34
  {({ field, form }: FormexFieldProps) => {
34
35
  return <SwitchControl
35
36
  label={"Read only"}
37
+ size={"medium"}
36
38
  disabled={disabled}
37
39
  tooltip={"Is this a read only field. Display only as a preview"}
38
40
  form={form}
@@ -14,6 +14,7 @@ export const supportedFieldsIds: PropertyConfigId[] = [
14
14
  "file_upload",
15
15
  "multi_file_upload",
16
16
  "reference",
17
+ "reference_as_string",
17
18
  "multi_references",
18
19
  "switch",
19
20
  "date_time",
@@ -208,6 +208,15 @@ export function updatePropertyFromWidget(propertyData: any,
208
208
  editable: propertyData.editable !== undefined ? propertyData.editable : true
209
209
  } satisfies Property
210
210
  );
211
+ } else if (selectedWidgetId === "reference_as_string") {
212
+ updatedProperty = mergeDeep(
213
+ propertyData,
214
+ {
215
+ dataType: "string",
216
+ propertyConfig: "reference_as_string",
217
+ editable: propertyData.editable !== undefined ? propertyData.editable : true
218
+ } satisfies Property
219
+ );
211
220
  } else if (selectedWidgetId === "multi_references") {
212
221
  updatedProperty = mergeDeep(
213
222
  propertyData,
@@ -14,6 +14,7 @@ import { AddIcon, Button, Paper, Typography } from "@firecms/ui";
14
14
  import { useCollectionEditorController } from "./useCollectionEditorController";
15
15
  import { EditorCollectionActionStart } from "./ui/EditorCollectionActionStart";
16
16
  import { NewCollectionCard } from "./ui/NewCollectionCard";
17
+ import { EditorEntityAction } from "./ui/EditorEntityAction";
17
18
 
18
19
  export interface CollectionConfigControllerProps<EC extends PersistedCollection = PersistedCollection, USER extends User = User> {
19
20
 
@@ -41,8 +42,6 @@ export interface CollectionConfigControllerProps<EC extends PersistedCollection
41
42
  icon: React.ReactNode
42
43
  };
43
44
 
44
- getPathSuggestions?: (path?: string) => Promise<string[]>;
45
-
46
45
  collectionInference?: CollectionInference;
47
46
 
48
47
  getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
@@ -51,6 +50,8 @@ export interface CollectionConfigControllerProps<EC extends PersistedCollection
51
50
 
52
51
  onAnalyticsEvent?: (event: string, params?: object) => void;
53
52
 
53
+ includeIntroView?: boolean;
54
+
54
55
  }
55
56
 
56
57
  /**
@@ -70,11 +71,11 @@ export function useCollectionEditorPlugin<EC extends PersistedCollection = Persi
70
71
  configPermissions,
71
72
  reservedGroups,
72
73
  extraView,
73
- getPathSuggestions,
74
74
  getUser,
75
75
  collectionInference,
76
76
  getData,
77
77
  onAnalyticsEvent,
78
+ includeIntroView = true
78
79
  }: CollectionConfigControllerProps<EC, USER>): FireCMSPlugin<any, any, PersistedCollection> {
79
80
 
80
81
  return {
@@ -88,7 +89,6 @@ export function useCollectionEditorPlugin<EC extends PersistedCollection = Persi
88
89
  collectionInference,
89
90
  reservedGroups,
90
91
  extraView,
91
- getPathSuggestions,
92
92
  getUser,
93
93
  getData,
94
94
  onAnalyticsEvent,
@@ -96,21 +96,26 @@ export function useCollectionEditorPlugin<EC extends PersistedCollection = Persi
96
96
  },
97
97
  homePage: {
98
98
  additionalActions: <NewCollectionButton/>,
99
- additionalChildrenStart: <IntroWidget/>,
100
- // additionalChildrenEnd: <RootCollectionSuggestions introMode={introMode}/>,
99
+ additionalChildrenStart: includeIntroView ? <IntroWidget/> : undefined,
101
100
  CollectionActions: HomePageEditorCollectionAction,
102
101
  AdditionalCards: NewCollectionCard,
102
+ allowDragAndDrop: true,
103
+ navigationEntries: collectionConfigController.navigationEntries,
104
+ onNavigationEntriesUpdate: collectionConfigController.saveNavigationEntries,
103
105
  },
104
106
  collectionView: {
105
107
  CollectionActionsStart: EditorCollectionActionStart,
106
108
  CollectionActions: EditorCollectionAction,
107
109
  HeaderAction: CollectionViewHeaderAction,
108
110
  AddColumnComponent: PropertyAddColumnComponent
111
+ },
112
+ form: {
113
+ ActionsTop: EditorEntityAction,
109
114
  }
110
115
  };
111
116
  }
112
117
 
113
- export function IntroWidget({}: {}) {
118
+ export function IntroWidget() {
114
119
 
115
120
  const navigation = useNavigationController();
116
121
  if (!navigation.topLevelNavigation)
@@ -1,4 +1,5 @@
1
1
  import {
2
+ EntityCallbacks,
2
3
  EntityCollection,
3
4
  joinCollectionLists,
4
5
  makePropertiesEditable,
@@ -25,11 +26,20 @@ export const mergeCollections = (baseCollections: EntityCollection[],
25
26
  const result = joinCollectionLists(baseCollections, backendCollections, [], modifyCollection);
26
27
 
27
28
  // sort the collections so they are in the same order as the base collections
28
- result.sort((a, b) => baseCollections.findIndex(c => c.id === a.id) - baseCollections.findIndex(c => c.id === b.id));
29
- console.debug("Collections result", {
30
- baseCollections,
31
- backendCollections,
32
- result
29
+ result.sort((a, b) => {
30
+ const indexA = baseCollections.findIndex(c => c.id === a.id);
31
+ const indexB = baseCollections.findIndex(c => c.id === b.id);
32
+
33
+ if (indexA === -1 && indexB === -1) {
34
+ return 0; // Keep original order for items not in baseCollections
35
+ }
36
+ if (indexA === -1) {
37
+ return 1; // a is not in base, so it goes to the end
38
+ }
39
+ if (indexB === -1) {
40
+ return -1; // b is not in base, so it goes to the end
41
+ }
42
+ return indexA - indexB;
33
43
  });
34
44
 
35
45
  return result;