@firecms/core 3.0.0-canary.50 → 3.0.0-canary.51

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.
@@ -134,559 +134,560 @@ export const EntityCollectionView = React.memo(
134
134
  }: EntityCollectionViewProps<M>
135
135
  ) {
136
136
 
137
- const context = useFireCMSContext();
138
- const fullPath = fullPathProp ?? collectionProp.path;
139
- const dataSource = useDataSource(collectionProp);
140
- const navigation = useNavigationController();
141
- const sideEntityController = useSideEntityController();
142
- const authController = useAuthController();
143
- const userConfigPersistence = useUserConfigurationPersistence();
144
- const analyticsController = useAnalyticsController();
145
- const customizationController = useCustomizationController();
146
-
147
- const containerRef = React.useRef<HTMLDivElement>(null);
148
-
149
- const collection = useMemo(() => {
150
- const userOverride = userConfigPersistence?.getCollectionConfig<M>(fullPath);
151
- return (userOverride ? mergeDeep(collectionProp, userOverride) : collectionProp) as EntityCollection<M>;
152
- }, [collectionProp, fullPath, userConfigPersistence?.getCollectionConfig]);
153
-
154
- const collectionRef = React.useRef(collection);
155
- useEffect(() => {
156
- collectionRef.current = collection;
157
- }, [collection]);
158
-
159
- const canCreateEntities = canCreateEntity(collection, authController, fullPath, null);
160
- const [selectedNavigationEntity, setSelectedNavigationEntity] = useState<Entity<M> | undefined>(undefined);
161
- const [deleteEntityClicked, setDeleteEntityClicked] = React.useState<Entity<M> | Entity<M>[] | undefined>(undefined);
162
-
163
- const [lastDeleteTimestamp, setLastDeleteTimestamp] = React.useState<number>(0);
164
-
165
- // number of entities in the collection
166
- const [docsCount, setDocsCount] = useState<number>(0);
167
-
168
- const unselectNavigatedEntity = useCallback(() => {
169
- const currentSelection = selectedNavigationEntity;
170
- setTimeout(() => {
171
- if (currentSelection === selectedNavigationEntity)
172
- setSelectedNavigationEntity(undefined);
173
- }, 2400);
174
- }, [selectedNavigationEntity]);
175
-
176
- const checkInlineEditing = useCallback((entity?: Entity<any>): boolean => {
177
- const collection = collectionRef.current;
178
- if (!canEditEntity(collection, authController, fullPath, entity ?? null)) {
179
- return false;
180
- }
181
- return collection.inlineEditing === undefined || collection.inlineEditing;
182
- }, [authController, fullPath]);
137
+ const context = useFireCMSContext();
138
+ console.debug("EntityCollectionView context", context.customizationController.propertyConfigs);
139
+ const fullPath = fullPathProp ?? collectionProp.path;
140
+ const dataSource = useDataSource(collectionProp);
141
+ const navigation = useNavigationController();
142
+ const sideEntityController = useSideEntityController();
143
+ const authController = useAuthController();
144
+ const userConfigPersistence = useUserConfigurationPersistence();
145
+ const analyticsController = useAnalyticsController();
146
+ const customizationController = useCustomizationController();
183
147
 
184
- const selectionEnabled = collection.selectionEnabled === undefined || collection.selectionEnabled;
185
- const hoverRow = !checkInlineEditing();
148
+ const containerRef = React.useRef<HTMLDivElement>(null);
186
149
 
187
- const [popOverOpen, setPopOverOpen] = useState(false);
150
+ const collection = useMemo(() => {
151
+ const userOverride = userConfigPersistence?.getCollectionConfig<M>(fullPath);
152
+ return (userOverride ? mergeDeep(collectionProp, userOverride) : collectionProp) as EntityCollection<M>;
153
+ }, [collectionProp, fullPath, userConfigPersistence?.getCollectionConfig]);
188
154
 
189
- const selectionController = useSelectionController<M>();
190
- const usedSelectionController = collection.selectionController ?? selectionController;
191
- const {
192
- selectedEntities,
193
- isEntitySelected,
194
- setSelectedEntities
195
- } = usedSelectionController;
155
+ const collectionRef = React.useRef(collection);
156
+ useEffect(() => {
157
+ collectionRef.current = collection;
158
+ }, [collection]);
159
+
160
+ const canCreateEntities = canCreateEntity(collection, authController, fullPath, null);
161
+ const [selectedNavigationEntity, setSelectedNavigationEntity] = useState<Entity<M> | undefined>(undefined);
162
+ const [deleteEntityClicked, setDeleteEntityClicked] = React.useState<Entity<M> | Entity<M>[] | undefined>(undefined);
163
+
164
+ const [lastDeleteTimestamp, setLastDeleteTimestamp] = React.useState<number>(0);
165
+
166
+ // number of entities in the collection
167
+ const [docsCount, setDocsCount] = useState<number>(0);
168
+
169
+ const unselectNavigatedEntity = useCallback(() => {
170
+ const currentSelection = selectedNavigationEntity;
171
+ setTimeout(() => {
172
+ if (currentSelection === selectedNavigationEntity)
173
+ setSelectedNavigationEntity(undefined);
174
+ }, 2400);
175
+ }, [selectedNavigationEntity]);
176
+
177
+ const checkInlineEditing = useCallback((entity?: Entity<any>): boolean => {
178
+ const collection = collectionRef.current;
179
+ if (!canEditEntity(collection, authController, fullPath, entity ?? null)) {
180
+ return false;
181
+ }
182
+ return collection.inlineEditing === undefined || collection.inlineEditing;
183
+ }, [authController, fullPath]);
196
184
 
197
- useEffect(() => {
198
- setDeleteEntityClicked(undefined);
199
- }, [selectedEntities]);
185
+ const selectionEnabled = collection.selectionEnabled === undefined || collection.selectionEnabled;
186
+ const hoverRow = !checkInlineEditing();
200
187
 
201
- const tableController = useDataSourceEntityCollectionTableController<M>({
202
- fullPath,
188
+ const [popOverOpen, setPopOverOpen] = useState(false);
189
+
190
+ const selectionController = useSelectionController<M>();
191
+ const usedSelectionController = collection.selectionController ?? selectionController;
192
+ const {
193
+ selectedEntities,
194
+ isEntitySelected,
195
+ setSelectedEntities
196
+ } = usedSelectionController;
197
+
198
+ useEffect(() => {
199
+ setDeleteEntityClicked(undefined);
200
+ }, [selectedEntities]);
201
+
202
+ const tableController = useDataSourceEntityCollectionTableController<M>({
203
+ fullPath,
204
+ collection,
205
+ lastDeleteTimestamp
206
+ });
207
+
208
+ const tableKey = React.useRef<string>(Math.random().toString(36));
209
+ const popupCell = tableController.popupCell;
210
+
211
+ const onPopupClose = useCallback(() => {
212
+ tableController.setPopupCell?.(undefined);
213
+ }, [tableController.setPopupCell]);
214
+
215
+ const onEntityClick = useCallback((clickedEntity: Entity<M>) => {
216
+ console.log("Entity clicked", clickedEntity)
217
+ const collection = collectionRef.current;
218
+ setSelectedNavigationEntity(clickedEntity);
219
+ analyticsController.onAnalyticsEvent?.("edit_entity_clicked", {
220
+ path: clickedEntity.path,
221
+ entityId: clickedEntity.id
222
+ });
223
+ return sideEntityController.open({
224
+ entityId: clickedEntity.id,
225
+ path: clickedEntity.path,
203
226
  collection,
204
- lastDeleteTimestamp
227
+ updateUrl: true,
228
+ onClose: unselectNavigatedEntity,
205
229
  });
230
+ }, [unselectNavigatedEntity, sideEntityController]);
206
231
 
207
- const tableKey = React.useRef<string>(Math.random().toString(36));
208
- const popupCell = tableController.popupCell;
209
-
210
- const onPopupClose = useCallback(() => {
211
- tableController.setPopupCell?.(undefined);
212
- }, [tableController.setPopupCell]);
213
-
214
- const onEntityClick = useCallback((clickedEntity: Entity<M>) => {
215
- console.log("Entity clicked", clickedEntity)
216
- const collection = collectionRef.current;
217
- setSelectedNavigationEntity(clickedEntity);
218
- analyticsController.onAnalyticsEvent?.("edit_entity_clicked", {
219
- path: clickedEntity.path,
220
- entityId: clickedEntity.id
221
- });
222
- return sideEntityController.open({
223
- entityId: clickedEntity.id,
224
- path: clickedEntity.path,
225
- collection,
226
- updateUrl: true,
227
- onClose: unselectNavigatedEntity,
228
- });
229
- }, [unselectNavigatedEntity, sideEntityController]);
230
-
231
- const onNewClick = useCallback(() => {
232
-
233
- const collection = collectionRef.current;
234
- analyticsController.onAnalyticsEvent?.("new_entity_click", {
235
- path: fullPath
236
- });
237
- sideEntityController.open({
238
- path: fullPath,
239
- collection,
240
- updateUrl: true,
241
- onClose: unselectNavigatedEntity,
242
- });
243
- }, [fullPath, sideEntityController]);
244
-
245
- const onMultipleDeleteClick = () => {
246
- analyticsController.onAnalyticsEvent?.("multiple_delete_dialog_open", {
247
- path: fullPath
248
- });
249
- setDeleteEntityClicked(selectedEntities);
250
- };
251
-
252
- const internalOnEntityDelete = (_path: string, entity: Entity<M>) => {
253
- analyticsController.onAnalyticsEvent?.("single_entity_deleted", {
254
- path: fullPath
255
- });
256
- setSelectedEntities((selectedEntities) => selectedEntities.filter((e) => e.id !== entity.id));
257
- setLastDeleteTimestamp(Date.now());
258
- };
232
+ const onNewClick = useCallback(() => {
259
233
 
260
- const internalOnMultipleEntitiesDelete = (_path: string, entities: Entity<M>[]) => {
261
- analyticsController.onAnalyticsEvent?.("multiple_entities_deleted", {
262
- path: fullPath
263
- });
264
- setSelectedEntities([]);
265
- setDeleteEntityClicked(undefined);
266
- setLastDeleteTimestamp(Date.now());
267
- };
234
+ const collection = collectionRef.current;
235
+ analyticsController.onAnalyticsEvent?.("new_entity_click", {
236
+ path: fullPath
237
+ });
238
+ sideEntityController.open({
239
+ path: fullPath,
240
+ collection,
241
+ updateUrl: true,
242
+ onClose: unselectNavigatedEntity,
243
+ });
244
+ }, [fullPath, sideEntityController]);
268
245
 
269
- let AddColumnComponent: React.ComponentType<{
270
- fullPath: string,
271
- parentCollectionIds: string[],
272
- collection: EntityCollection;
273
- }> | undefined
246
+ const onMultipleDeleteClick = () => {
247
+ analyticsController.onAnalyticsEvent?.("multiple_delete_dialog_open", {
248
+ path: fullPath
249
+ });
250
+ setDeleteEntityClicked(selectedEntities);
251
+ };
274
252
 
275
- // we are only using the first plugin that implements this
276
- if (customizationController?.plugins) {
277
- AddColumnComponent = customizationController.plugins.find(plugin => plugin.collectionView?.AddColumnComponent)?.collectionView?.AddColumnComponent;
278
- }
253
+ const internalOnEntityDelete = (_path: string, entity: Entity<M>) => {
254
+ analyticsController.onAnalyticsEvent?.("single_entity_deleted", {
255
+ path: fullPath
256
+ });
257
+ setSelectedEntities((selectedEntities) => selectedEntities.filter((e) => e.id !== entity.id));
258
+ setLastDeleteTimestamp(Date.now());
259
+ };
279
260
 
280
- const onCollectionModifiedForUser = useCallback((path: string, partialCollection: PartialEntityCollection<M>) => {
281
- if (userConfigPersistence) {
282
- const currentStoredConfig = userConfigPersistence.getCollectionConfig(path);
283
- const updatedConfig = mergeDeep(currentStoredConfig, partialCollection);
284
- userConfigPersistence.onCollectionModified(path, updatedConfig);
285
- }
286
- }, [userConfigPersistence]);
287
-
288
- const onColumnResize = useCallback(({
289
- width,
290
- key
291
- }: OnColumnResizeParams) => {
292
-
293
- const collection = collectionRef.current;
294
- // Only for property columns
295
- if (!getPropertyInPath(collection.properties, key)) return;
296
- const localCollection = buildPropertyWidthOverwrite(key, width);
297
- onCollectionModifiedForUser(fullPath, localCollection);
298
- }, [onCollectionModifiedForUser, fullPath]);
299
-
300
- const onSizeChanged = useCallback((size: CollectionSize) => {
301
- if (userConfigPersistence)
302
- onCollectionModifiedForUser(fullPath, { defaultSize: size })
303
- }, [onCollectionModifiedForUser, fullPath, userConfigPersistence]);
304
-
305
- const createEnabled = canCreateEntity(collection, authController, fullPath, null);
306
-
307
- const uniqueFieldValidator: UniqueFieldValidator = useCallback(
308
- ({
309
- name,
310
- value,
311
- property,
312
- entityId
313
- }) => dataSource.checkUniqueField(fullPath, name, value, entityId),
314
- [fullPath]);
315
-
316
- const onValueChange: OnCellValueChange<any, any> = ({
317
- value,
318
- propertyKey,
319
- onValueUpdated,
320
- setError,
321
- data: entity,
322
- }) => {
323
-
324
- const updatedValues = setIn({ ...entity.values }, propertyKey, value);
325
-
326
- const saveProps: SaveEntityProps = {
327
- path: fullPath,
328
- entityId: entity.id,
329
- values: updatedValues,
330
- previousValues: entity.values,
331
- collection,
332
- status: "existing"
333
- };
261
+ const internalOnMultipleEntitiesDelete = (_path: string, entities: Entity<M>[]) => {
262
+ analyticsController.onAnalyticsEvent?.("multiple_entities_deleted", {
263
+ path: fullPath
264
+ });
265
+ setSelectedEntities([]);
266
+ setDeleteEntityClicked(undefined);
267
+ setLastDeleteTimestamp(Date.now());
268
+ };
269
+
270
+ let AddColumnComponent: React.ComponentType<{
271
+ fullPath: string,
272
+ parentCollectionIds: string[],
273
+ collection: EntityCollection;
274
+ }> | undefined
275
+
276
+ // we are only using the first plugin that implements this
277
+ if (customizationController?.plugins) {
278
+ AddColumnComponent = customizationController.plugins.find(plugin => plugin.collectionView?.AddColumnComponent)?.collectionView?.AddColumnComponent;
279
+ }
334
280
 
335
- return saveEntityWithCallbacks({
336
- ...saveProps,
337
- collection,
338
- dataSource,
339
- context,
340
- onSaveSuccess: () => {
341
- setError(undefined);
342
- onValueUpdated();
343
- },
344
- onSaveFailure: (e: Error) => {
345
- console.error("Save failure");
346
- console.error(e);
347
- setError(e);
348
- }
349
- });
281
+ const onCollectionModifiedForUser = useCallback((path: string, partialCollection: PartialEntityCollection<M>) => {
282
+ if (userConfigPersistence) {
283
+ const currentStoredConfig = userConfigPersistence.getCollectionConfig(path);
284
+ const updatedConfig = mergeDeep(currentStoredConfig, partialCollection);
285
+ userConfigPersistence.onCollectionModified(path, updatedConfig);
286
+ }
287
+ }, [userConfigPersistence]);
350
288
 
289
+ const onColumnResize = useCallback(({
290
+ width,
291
+ key
292
+ }: OnColumnResizeParams) => {
293
+
294
+ const collection = collectionRef.current;
295
+ // Only for property columns
296
+ if (!getPropertyInPath(collection.properties, key)) return;
297
+ const localCollection = buildPropertyWidthOverwrite(key, width);
298
+ onCollectionModifiedForUser(fullPath, localCollection);
299
+ }, [onCollectionModifiedForUser, fullPath]);
300
+
301
+ const onSizeChanged = useCallback((size: CollectionSize) => {
302
+ if (userConfigPersistence)
303
+ onCollectionModifiedForUser(fullPath, { defaultSize: size })
304
+ }, [onCollectionModifiedForUser, fullPath, userConfigPersistence]);
305
+
306
+ const createEnabled = canCreateEntity(collection, authController, fullPath, null);
307
+
308
+ const uniqueFieldValidator: UniqueFieldValidator = useCallback(
309
+ ({
310
+ name,
311
+ value,
312
+ property,
313
+ entityId
314
+ }) => dataSource.checkUniqueField(fullPath, name, value, entityId),
315
+ [fullPath]);
316
+
317
+ const onValueChange: OnCellValueChange<any, any> = ({
318
+ value,
319
+ propertyKey,
320
+ onValueUpdated,
321
+ setError,
322
+ data: entity,
323
+ }) => {
324
+
325
+ const updatedValues = setIn({ ...entity.values }, propertyKey, value);
326
+
327
+ const saveProps: SaveEntityProps = {
328
+ path: fullPath,
329
+ entityId: entity.id,
330
+ values: updatedValues,
331
+ previousValues: entity.values,
332
+ collection,
333
+ status: "existing"
351
334
  };
352
335
 
353
- const resolvedFullPath = navigation.resolveAliasesFrom(fullPath);
354
- const resolvedCollection = useMemo(() => resolveCollection<M>({
336
+ return saveEntityWithCallbacks({
337
+ ...saveProps,
355
338
  collection,
339
+ dataSource,
340
+ context,
341
+ onSaveSuccess: () => {
342
+ setError(undefined);
343
+ onValueUpdated();
344
+ },
345
+ onSaveFailure: (e: Error) => {
346
+ console.error("Save failure");
347
+ console.error(e);
348
+ setError(e);
349
+ }
350
+ });
351
+
352
+ };
353
+
354
+ const resolvedFullPath = navigation.resolveAliasesFrom(fullPath);
355
+ const resolvedCollection = useMemo(() => resolveCollection<M>({
356
+ collection,
357
+ path: fullPath,
358
+ fields: customizationController.propertyConfigs
359
+ }), [collection, fullPath]);
360
+
361
+ const getPropertyFor = useCallback(({
362
+ propertyKey,
363
+ entity
364
+ }: GetPropertyForProps<M>) => {
365
+ let propertyOrBuilder: PropertyOrBuilder<any, M> | undefined = getPropertyInPath<M>(collection.properties, propertyKey);
366
+
367
+ // we might not find the property in the collection if combining property builders and map spread
368
+ if (!propertyOrBuilder) {
369
+ // these 2 properties are coming from the resolved collection with default values
370
+ propertyOrBuilder = getPropertyInPath<M>(resolvedCollection.properties, propertyKey);
371
+ }
372
+
373
+ return resolveProperty({
374
+ propertyKey,
375
+ propertyOrBuilder,
356
376
  path: fullPath,
377
+ values: entity.values,
378
+ entityId: entity.id,
357
379
  fields: customizationController.propertyConfigs
358
- }), [collection, fullPath]);
359
-
360
- const getPropertyFor = useCallback(({
361
- propertyKey,
362
- entity
363
- }: GetPropertyForProps<M>) => {
364
- let propertyOrBuilder: PropertyOrBuilder<any, M> | undefined = getPropertyInPath<M>(collection.properties, propertyKey);
365
-
366
- // we might not find the property in the collection if combining property builders and map spread
367
- if (!propertyOrBuilder) {
368
- // these 2 properties are coming from the resolved collection with default values
369
- propertyOrBuilder = getPropertyInPath<M>(resolvedCollection.properties, propertyKey);
370
- }
380
+ });
381
+ }, [collection.properties, customizationController.propertyConfigs, fullPath, resolvedCollection.properties]);
382
+
383
+ const displayedColumnIds = useColumnIds(resolvedCollection, true);
384
+
385
+ const additionalFields = useMemo(() => {
386
+ const subcollectionColumns: AdditionalFieldDelegate<M, any>[] = collection.subcollections?.map((subcollection) => {
387
+ return {
388
+ key: getSubcollectionColumnId(subcollection),
389
+ name: subcollection.name,
390
+ width: 200,
391
+ dependencies: [],
392
+ Builder: ({ entity }) => (
393
+ <Button color={"primary"}
394
+ variant={"outlined"}
395
+ startIcon={<KeyboardTabIcon size={"small"}/>}
396
+ onClick={(event: any) => {
397
+ event.stopPropagation();
398
+ sideEntityController.open({
399
+ path: fullPath,
400
+ entityId: entity.id,
401
+ selectedSubPath: subcollection.id ?? subcollection.path,
402
+ collection,
403
+ updateUrl: true,
404
+ });
405
+ }}>
406
+ {subcollection.name}
407
+ </Button>
408
+ )
409
+ };
410
+ }) ?? [];
411
+
412
+ const collectionGroupParentCollections: AdditionalFieldDelegate<M, any>[] = collection.collectionGroup
413
+ ? [{
414
+ key: COLLECTION_GROUP_PARENT_ID,
415
+ name: "Parent entities",
416
+ width: 260,
417
+ dependencies: [],
418
+ Builder: ({ entity }) => {
419
+ const collectionsWithPath = navigation.getParentReferencesFromPath(entity.path);
420
+ return (
421
+ <>
422
+ {collectionsWithPath.map((reference) => {
423
+ return (
424
+ <ReferencePreview
425
+ key={reference.path + "/" + reference.id}
426
+ reference={reference}
427
+ size={"tiny"}/>
428
+ );
429
+ })}
430
+ </>
431
+ );
432
+ }
433
+ }]
434
+ : [];
435
+
436
+ return [
437
+ ...(collection.additionalFields ?? []),
438
+ ...subcollectionColumns,
439
+ ...collectionGroupParentCollections
440
+ ];
441
+ }, [collection, fullPath, sideEntityController]);
442
+
443
+ const updateLastDeleteTimestamp = useCallback(() => {
444
+ setLastDeleteTimestamp(Date.now());
445
+ }, []);
446
+
447
+ const largeLayout = useLargeLayout();
448
+
449
+ const getActionsForEntity = ({
450
+ entity,
451
+ customEntityActions
452
+ }: {
453
+ entity?: Entity<M>,
454
+ customEntityActions?: EntityAction[]
455
+ }): EntityAction[] => {
456
+ const deleteEnabled = entity ? canDeleteEntity(collection, authController, fullPath, entity) : true;
457
+ const actions: EntityAction[] = [editEntityAction];
458
+ if (createEnabled)
459
+ actions.push(copyEntityAction);
460
+ if (deleteEnabled)
461
+ actions.push(deleteEntityAction);
462
+ if (customEntityActions)
463
+ actions.push(...customEntityActions);
464
+ return actions;
465
+ };
466
+
467
+ const getIdColumnWidth = () => {
468
+ const entityActions = getActionsForEntity({});
469
+ const collapsedActions = entityActions.filter(a => a.collapsed !== false);
470
+ const uncollapsedActions = entityActions.filter(a => a.collapsed === false);
471
+ const actionsWidth = uncollapsedActions.length * (largeLayout ? 40 : 30);
472
+ return (largeLayout ? (80 + actionsWidth) : (70 + actionsWidth)) + (collapsedActions.length > 0 ? (largeLayout ? 40 : 30) : 0);
473
+ };
474
+
475
+ const tableRowActionsBuilder = ({
476
+ entity,
477
+ size,
478
+ width,
479
+ frozen
480
+ }: {
481
+ entity: Entity<any>,
482
+ size: CollectionSize,
483
+ width: number,
484
+ frozen?: boolean
485
+ }) => {
486
+
487
+ const isSelected = isEntitySelected(entity);
488
+
489
+ const actions = getActionsForEntity({
490
+ entity,
491
+ customEntityActions: collection.entityActions
492
+ });
371
493
 
372
- return resolveProperty({
373
- propertyKey,
374
- propertyOrBuilder,
375
- path: fullPath,
376
- values: entity.values,
377
- entityId: entity.id,
378
- fields: customizationController.propertyConfigs
379
- });
380
- }, [collection.properties, customizationController.propertyConfigs, fullPath, resolvedCollection.properties]);
381
-
382
- const displayedColumnIds = useColumnIds(resolvedCollection, true);
383
-
384
- const additionalFields = useMemo(() => {
385
- const subcollectionColumns: AdditionalFieldDelegate<M, any>[] = collection.subcollections?.map((subcollection) => {
386
- return {
387
- key: getSubcollectionColumnId(subcollection),
388
- name: subcollection.name,
389
- width: 200,
390
- dependencies: [],
391
- Builder: ({ entity }) => (
392
- <Button color={"primary"}
393
- variant={"outlined"}
394
- startIcon={<KeyboardTabIcon size={"small"}/>}
395
- onClick={(event: any) => {
396
- event.stopPropagation();
397
- sideEntityController.open({
398
- path: fullPath,
399
- entityId: entity.id,
400
- selectedSubPath: subcollection.id ?? subcollection.path,
401
- collection,
402
- updateUrl: true,
403
- });
404
- }}>
405
- {subcollection.name}
406
- </Button>
407
- )
408
- };
409
- }) ?? [];
410
-
411
- const collectionGroupParentCollections: AdditionalFieldDelegate<M, any>[] = collection.collectionGroup
412
- ? [{
413
- key: COLLECTION_GROUP_PARENT_ID,
414
- name: "Parent entities",
415
- width: 260,
416
- dependencies: [],
417
- Builder: ({ entity }) => {
418
- const collectionsWithPath = navigation.getParentReferencesFromPath(entity.path);
419
- return (
420
- <>
421
- {collectionsWithPath.map((reference) => {
422
- return (
423
- <ReferencePreview
424
- key={reference.path + "/" + reference.id}
425
- reference={reference}
426
- size={"tiny"}/>
427
- );
428
- })}
429
- </>
430
- );
431
- }
432
- }]
433
- : [];
434
-
435
- return [
436
- ...(collection.additionalFields ?? []),
437
- ...subcollectionColumns,
438
- ...collectionGroupParentCollections
439
- ];
440
- }, [collection, fullPath, sideEntityController]);
441
-
442
- const updateLastDeleteTimestamp = useCallback(() => {
443
- setLastDeleteTimestamp(Date.now());
444
- }, []);
445
-
446
- const largeLayout = useLargeLayout();
447
-
448
- const getActionsForEntity = ({
449
- entity,
450
- customEntityActions
451
- }: {
452
- entity?: Entity<M>,
453
- customEntityActions?: EntityAction[]
454
- }): EntityAction[] => {
455
- const deleteEnabled = entity ? canDeleteEntity(collection, authController, fullPath, entity) : true;
456
- const actions: EntityAction[] = [editEntityAction];
457
- if (createEnabled)
458
- actions.push(copyEntityAction);
459
- if (deleteEnabled)
460
- actions.push(deleteEntityAction);
461
- if (customEntityActions)
462
- actions.push(...customEntityActions);
463
- return actions;
464
- };
494
+ return (
495
+ <EntityCollectionRowActions
496
+ entity={entity}
497
+ width={width}
498
+ frozen={frozen}
499
+ isSelected={isSelected}
500
+ selectionEnabled={selectionEnabled}
501
+ size={size}
502
+ highlightEntity={setSelectedNavigationEntity}
503
+ unhighlightEntity={unselectNavigatedEntity}
504
+ collection={collection}
505
+ fullPath={fullPath}
506
+ actions={actions}
507
+ hideId={collection?.hideIdFromCollection}
508
+ onCollectionChange={updateLastDeleteTimestamp}
509
+ selectionController={usedSelectionController}
510
+ />
511
+ );
465
512
 
466
- const getIdColumnWidth = () => {
467
- const entityActions = getActionsForEntity({});
468
- const collapsedActions = entityActions.filter(a => a.collapsed !== false);
469
- const uncollapsedActions = entityActions.filter(a => a.collapsed === false);
470
- const actionsWidth = uncollapsedActions.length * (largeLayout ? 40 : 30);
471
- return (largeLayout ? (80 + actionsWidth) : (70 + actionsWidth)) + (collapsedActions.length > 0 ? (largeLayout ? 40 : 30) : 0);
472
- };
513
+ };
514
+
515
+ const title = <Popover
516
+ open={popOverOpen}
517
+ onOpenChange={setPopOverOpen}
518
+ enabled={Boolean(collection.description)}
519
+ trigger={<div className="flex flex-col items-start">
520
+ <Typography
521
+ variant={"subtitle1"}
522
+ className={`leading-none truncate max-w-[160px] lg:max-w-[240px] ${collection.description ? "cursor-pointer" : "cursor-auto"}`}
523
+ onClick={collection.description
524
+ ? (e) => {
525
+ setPopOverOpen(true);
526
+ e.stopPropagation();
527
+ }
528
+ : undefined}>
529
+ {`${collection.name}`}
530
+ </Typography>
531
+
532
+ <EntitiesCount
533
+ fullPath={fullPath}
534
+ collection={collection}
535
+ filter={tableController.filterValues}
536
+ sortBy={tableController.sortBy}
537
+ onCountChange={setDocsCount}
538
+ />
539
+
540
+ </div>}
541
+ >
542
+
543
+ {collection.description && <div className="m-4 text-gray-900 dark:text-white">
544
+ <Markdown source={collection.description}/>
545
+ </div>}
546
+
547
+ </Popover>;
548
+
549
+ const buildAdditionalHeaderWidget = useCallback(({
550
+ property,
551
+ propertyKey,
552
+ onHover
553
+ }: {
554
+ property: ResolvedProperty,
555
+ propertyKey: string,
556
+ onHover: boolean
557
+ }) => {
558
+ const collection = collectionRef.current;
559
+ if (!customizationController.plugins)
560
+ return null;
561
+ return <>
562
+ {customizationController.plugins.filter(plugin => plugin.collectionView?.HeaderAction)
563
+ .map((plugin, i) => {
564
+ const HeaderAction = plugin.collectionView!.HeaderAction!;
565
+ return <HeaderAction
566
+ onHover={onHover}
567
+ key={`plugin_header_action_${i}`}
568
+ propertyKey={propertyKey}
569
+ property={property}
570
+ fullPath={fullPath}
571
+ collection={collection}
572
+ parentCollectionIds={parentCollectionIds ?? []}/>;
573
+ })}
574
+ </>;
575
+ }, [customizationController.plugins, fullPath, parentCollectionIds]);
576
+
577
+ const addColumnComponentInternal = AddColumnComponent
578
+ ? function () {
579
+ if (typeof AddColumnComponent === "function")
580
+ return <AddColumnComponent fullPath={fullPath}
581
+ parentCollectionIds={parentCollectionIds ?? []}
582
+ collection={collection}/>;
583
+ return null;
584
+ }
585
+ : undefined;
586
+
587
+ const {
588
+ textSearchLoading,
589
+ textSearchInitialised,
590
+ onTextSearchClick,
591
+ textSearchEnabled
592
+ } = useTableSearchHelper({
593
+ collection,
594
+ fullPath: resolvedFullPath,
595
+ parentCollectionIds
596
+ });
473
597
 
474
- const tableRowActionsBuilder = ({
475
- entity,
476
- size,
477
- width,
478
- frozen
479
- }: {
480
- entity: Entity<any>,
481
- size: CollectionSize,
482
- width: number,
483
- frozen?: boolean
484
- }) => {
485
-
486
- const isSelected = isEntitySelected(entity);
487
-
488
- const actions = getActionsForEntity({
489
- entity,
490
- customEntityActions: collection.entityActions
491
- });
492
-
493
- return (
494
- <EntityCollectionRowActions
495
- entity={entity}
496
- width={width}
497
- frozen={frozen}
498
- isSelected={isSelected}
499
- selectionEnabled={selectionEnabled}
500
- size={size}
501
- highlightEntity={setSelectedNavigationEntity}
502
- unhighlightEntity={unselectNavigatedEntity}
598
+ return (
599
+ <div className={cn("overflow-hidden h-full w-full rounded-md", className)}
600
+ ref={containerRef}>
601
+ <EntityCollectionTable
602
+ key={`collection_table_${fullPath}`}
603
+ additionalFields={additionalFields}
604
+ tableController={tableController}
605
+ enablePopupIcon={true}
606
+ displayedColumnIds={displayedColumnIds}
607
+ onSizeChanged={onSizeChanged}
608
+ onEntityClick={onEntityClick}
609
+ onColumnResize={onColumnResize}
610
+ onValueChange={onValueChange}
611
+ tableRowActionsBuilder={tableRowActionsBuilder}
612
+ uniqueFieldValidator={uniqueFieldValidator}
613
+ title={title}
614
+ selectionController={usedSelectionController}
615
+ highlightedEntities={selectedNavigationEntity ? [selectedNavigationEntity] : []}
616
+ defaultSize={collection.defaultSize}
617
+ properties={resolvedCollection.properties}
618
+ getPropertyFor={getPropertyFor}
619
+ onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
620
+ textSearchLoading={textSearchLoading}
621
+ textSearchEnabled={textSearchEnabled}
622
+ actionsStart={<EntityCollectionViewStartActions
623
+ parentCollectionIds={parentCollectionIds ?? []}
503
624
  collection={collection}
504
- fullPath={fullPath}
505
- actions={actions}
506
- hideId={collection?.hideIdFromCollection}
507
- onCollectionChange={updateLastDeleteTimestamp}
625
+ tableController={tableController}
626
+ path={fullPath}
627
+ relativePath={collection.path}
508
628
  selectionController={usedSelectionController}
509
- />
510
- );
511
-
512
- };
513
-
514
- const title = <Popover
515
- open={popOverOpen}
516
- onOpenChange={setPopOverOpen}
517
- enabled={Boolean(collection.description)}
518
- trigger={<div className="flex flex-col items-start">
519
- <Typography
520
- variant={"subtitle1"}
521
- className={`leading-none truncate max-w-[160px] lg:max-w-[240px] ${collection.description ? "cursor-pointer" : "cursor-auto"}`}
522
- onClick={collection.description
523
- ? (e) => {
524
- setPopOverOpen(true);
525
- e.stopPropagation();
526
- }
527
- : undefined}>
528
- {`${collection.name}`}
529
- </Typography>
530
-
531
- <EntitiesCount
532
- fullPath={fullPath}
629
+ collectionEntitiesCount={docsCount}/>}
630
+ actions={<EntityCollectionViewActions
631
+ parentCollectionIds={parentCollectionIds ?? []}
533
632
  collection={collection}
534
- filter={tableController.filterValues}
535
- sortBy={tableController.sortBy}
536
- onCountChange={setDocsCount}
537
- />
538
-
539
- </div>}
540
- >
541
-
542
- {collection.description && <div className="m-4 text-gray-900 dark:text-white">
543
- <Markdown source={collection.description}/>
544
- </div>}
545
-
546
- </Popover>;
547
-
548
- const buildAdditionalHeaderWidget = useCallback(({
549
- property,
550
- propertyKey,
551
- onHover
552
- }: {
553
- property: ResolvedProperty,
554
- propertyKey: string,
555
- onHover: boolean
556
- }) => {
557
- const collection = collectionRef.current;
558
- if (!customizationController.plugins)
559
- return null;
560
- return <>
561
- {customizationController.plugins.filter(plugin => plugin.collectionView?.HeaderAction)
562
- .map((plugin, i) => {
563
- const HeaderAction = plugin.collectionView!.HeaderAction!;
564
- return <HeaderAction
565
- onHover={onHover}
566
- key={`plugin_header_action_${i}`}
567
- propertyKey={propertyKey}
568
- property={property}
569
- fullPath={fullPath}
570
- collection={collection}
571
- parentCollectionIds={parentCollectionIds ?? []}/>;
572
- })}
573
- </>;
574
- }, [customizationController.plugins, fullPath, parentCollectionIds]);
575
-
576
- const addColumnComponentInternal = AddColumnComponent
577
- ? function () {
578
- if (typeof AddColumnComponent === "function")
579
- return <AddColumnComponent fullPath={fullPath}
580
- parentCollectionIds={parentCollectionIds ?? []}
581
- collection={collection}/>;
582
- return null;
583
- }
584
- : undefined;
585
-
586
- const {
587
- textSearchLoading,
588
- textSearchInitialised,
589
- onTextSearchClick,
590
- textSearchEnabled
591
- } = useTableSearchHelper({
592
- collection,
593
- fullPath: resolvedFullPath,
594
- parentCollectionIds
595
- });
596
-
597
- return (
598
- <div className={cn("overflow-hidden h-full w-full rounded-md", className)}
599
- ref={containerRef}>
600
- <EntityCollectionTable
601
- key={`collection_table_${fullPath}`}
602
- additionalFields={additionalFields}
603
633
  tableController={tableController}
604
- enablePopupIcon={true}
605
- displayedColumnIds={displayedColumnIds}
606
- onSizeChanged={onSizeChanged}
607
- onEntityClick={onEntityClick}
608
- onColumnResize={onColumnResize}
609
- onValueChange={onValueChange}
610
- tableRowActionsBuilder={tableRowActionsBuilder}
611
- uniqueFieldValidator={uniqueFieldValidator}
612
- title={title}
634
+ onMultipleDeleteClick={onMultipleDeleteClick}
635
+ onNewClick={onNewClick}
636
+ path={fullPath}
637
+ relativePath={collection.path}
613
638
  selectionController={usedSelectionController}
614
- highlightedEntities={selectedNavigationEntity ? [selectedNavigationEntity] : []}
615
- defaultSize={collection.defaultSize}
616
- properties={resolvedCollection.properties}
617
- getPropertyFor={getPropertyFor}
618
- onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
619
- textSearchLoading={textSearchLoading}
620
- textSearchEnabled={textSearchEnabled}
621
- actionsStart={<EntityCollectionViewStartActions
622
- parentCollectionIds={parentCollectionIds ?? []}
623
- collection={collection}
624
- tableController={tableController}
625
- path={fullPath}
626
- relativePath={collection.path}
627
- selectionController={usedSelectionController}
628
- collectionEntitiesCount={docsCount}/>}
629
- actions={<EntityCollectionViewActions
630
- parentCollectionIds={parentCollectionIds ?? []}
631
- collection={collection}
632
- tableController={tableController}
633
- onMultipleDeleteClick={onMultipleDeleteClick}
634
- onNewClick={onNewClick}
635
- path={fullPath}
636
- relativePath={collection.path}
637
- selectionController={usedSelectionController}
638
- selectionEnabled={selectionEnabled}
639
- collectionEntitiesCount={docsCount}
640
- />}
641
- emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
642
- ? <div className="flex flex-col items-center justify-center">
643
- <Typography variant={"subtitle2"}>So empty...</Typography>
644
- <Button
645
- color={"primary"}
646
- variant={"outlined"}
647
- onClick={onNewClick}
648
- className="mt-4"
649
- >
650
- <AddIcon/>
651
- Create your first entity
652
- </Button>
653
- </div>
654
- : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
655
- }
656
- hoverRow={hoverRow}
657
- inlineEditing={checkInlineEditing()}
658
- AdditionalHeaderWidget={buildAdditionalHeaderWidget}
659
- AddColumnComponent={addColumnComponentInternal}
660
- getIdColumnWidth={getIdColumnWidth}
661
- additionalIDHeaderWidget={<EntityIdHeaderWidget
662
- path={fullPath}
663
- collection={collection}/>}
664
- />
665
-
666
- <PopupFormField
667
- key={`popup_form_${popupCell?.propertyKey}_${popupCell?.entity?.id}`}
668
- open={Boolean(popupCell)}
669
- onClose={onPopupClose}
670
- cellRect={popupCell?.cellRect}
671
- propertyKey={popupCell?.propertyKey}
639
+ selectionEnabled={selectionEnabled}
640
+ collectionEntitiesCount={docsCount}
641
+ />}
642
+ emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
643
+ ? <div className="flex flex-col items-center justify-center">
644
+ <Typography variant={"subtitle2"}>So empty...</Typography>
645
+ <Button
646
+ color={"primary"}
647
+ variant={"outlined"}
648
+ onClick={onNewClick}
649
+ className="mt-4"
650
+ >
651
+ <AddIcon/>
652
+ Create your first entity
653
+ </Button>
654
+ </div>
655
+ : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
656
+ }
657
+ hoverRow={hoverRow}
658
+ inlineEditing={checkInlineEditing()}
659
+ AdditionalHeaderWidget={buildAdditionalHeaderWidget}
660
+ AddColumnComponent={addColumnComponentInternal}
661
+ getIdColumnWidth={getIdColumnWidth}
662
+ additionalIDHeaderWidget={<EntityIdHeaderWidget
663
+ path={fullPath}
664
+ collection={collection}/>}
665
+ />
666
+
667
+ <PopupFormField
668
+ key={`popup_form_${popupCell?.propertyKey}_${popupCell?.entity?.id}`}
669
+ open={Boolean(popupCell)}
670
+ onClose={onPopupClose}
671
+ cellRect={popupCell?.cellRect}
672
+ propertyKey={popupCell?.propertyKey}
673
+ collection={collection}
674
+ entity={popupCell?.entity}
675
+ tableKey={tableKey.current}
676
+ customFieldValidator={uniqueFieldValidator}
677
+ path={resolvedFullPath}
678
+ onCellValueChange={onValueChange}
679
+ container={containerRef.current}/>
680
+
681
+ {deleteEntityClicked &&
682
+ <DeleteEntityDialog
683
+ entityOrEntitiesToDelete={deleteEntityClicked}
684
+ path={fullPath}
672
685
  collection={collection}
673
- entity={popupCell?.entity}
674
- tableKey={tableKey.current}
675
- customFieldValidator={uniqueFieldValidator}
676
- path={resolvedFullPath}
677
- onCellValueChange={onValueChange}
678
- container={containerRef.current}/>
679
-
680
- {deleteEntityClicked &&
681
- <DeleteEntityDialog
682
- entityOrEntitiesToDelete={deleteEntityClicked}
683
- path={fullPath}
684
- collection={collection}
685
- callbacks={collection.callbacks}
686
- open={Boolean(deleteEntityClicked)}
687
- onEntityDelete={internalOnEntityDelete}
688
- onMultipleEntitiesDelete={internalOnMultipleEntitiesDelete}
689
- onClose={() => setDeleteEntityClicked(undefined)}/>}
686
+ callbacks={collection.callbacks}
687
+ open={Boolean(deleteEntityClicked)}
688
+ onEntityDelete={internalOnEntityDelete}
689
+ onMultipleEntitiesDelete={internalOnMultipleEntitiesDelete}
690
+ onClose={() => setDeleteEntityClicked(undefined)}/>}
690
691
 
691
692
  </div>
692
693
  );