@firecms/core 3.0.0-canary.258 → 3.0.0-canary.259

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 (39) hide show
  1. package/dist/core/EntityEditViewFormActions.d.ts +1 -1
  2. package/dist/form/EntityFormActions.d.ts +4 -2
  3. package/dist/index.es.js +339 -155
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/index.umd.js +339 -155
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/types/collections.d.ts +4 -1
  8. package/dist/types/customization_controller.d.ts +8 -0
  9. package/dist/types/entity_actions.d.ts +45 -6
  10. package/dist/types/firecms.d.ts +8 -0
  11. package/dist/types/plugins.d.ts +8 -1
  12. package/dist/util/resolutions.d.ts +2 -1
  13. package/package.json +5 -5
  14. package/src/components/ConfirmationDialog.tsx +1 -0
  15. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -0
  16. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +2 -2
  17. package/src/components/EntityCollectionView/EntityCollectionView.tsx +5 -2
  18. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +3 -2
  19. package/src/components/HomePage/DefaultHomePage.tsx +48 -33
  20. package/src/components/HomePage/HomePageDnD.tsx +22 -36
  21. package/src/components/HomePage/RenameGroupDialog.tsx +6 -2
  22. package/src/components/UnsavedChangesDialog.tsx +6 -2
  23. package/src/components/common/default_entity_actions.tsx +18 -5
  24. package/src/components/common/useDataSourceTableController.tsx +1 -1
  25. package/src/core/EntityEditView.tsx +35 -12
  26. package/src/core/EntityEditViewFormActions.tsx +154 -29
  27. package/src/core/EntitySidePanel.tsx +1 -1
  28. package/src/core/FireCMS.tsx +2 -0
  29. package/src/form/EntityForm.tsx +31 -5
  30. package/src/form/EntityFormActions.tsx +37 -8
  31. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +4 -2
  32. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +1 -1
  33. package/src/types/collections.ts +4 -1
  34. package/src/types/customization_controller.tsx +9 -0
  35. package/src/types/entity_actions.tsx +56 -6
  36. package/src/types/firecms.tsx +9 -0
  37. package/src/types/plugins.tsx +9 -1
  38. package/src/util/join_collections.ts +3 -1
  39. package/src/util/resolutions.ts +13 -1
package/dist/index.es.js CHANGED
@@ -908,6 +908,13 @@ function resolveEntityView(entityView, contextEntityViews) {
908
908
  return entityView;
909
909
  }
910
910
  }
911
+ function resolveEntityAction(entityAction, contextEntityActions) {
912
+ if (typeof entityAction === "string") {
913
+ return contextEntityActions?.find((entry) => entry.key === entityAction);
914
+ } else {
915
+ return entityAction;
916
+ }
917
+ }
911
918
  function resolvedSelectedEntityView(customViews, customizationController, selectedTab, canEdit) {
912
919
  const resolvedEntityViews = customViews ? customViews.map((e) => resolveEntityView(e, customizationController.entityViews)).filter((e) => Boolean(e)) : [];
913
920
  const selectedEntityView = resolvedEntityViews.find((e) => e.key === selectedTab);
@@ -3573,12 +3580,14 @@ function mergeCollection(target, source, parentPaths = [], modifyCollection) {
3573
3580
  const sourcePropertiesOrder = getCollectionKeys(source);
3574
3581
  const mergedPropertiesOrder = [.../* @__PURE__ */ new Set([...sourcePropertiesOrder, ...targetPropertiesOrder])];
3575
3582
  const mergedEntityViews = [.../* @__PURE__ */ new Set([...target.entityViews ?? [], ...source.entityViews ?? []])];
3583
+ const mergedEntityActions = [.../* @__PURE__ */ new Set([...target.entityActions ?? [], ...source.entityActions ?? []])];
3576
3584
  let resultCollection = {
3577
3585
  ...mergedCollection,
3578
3586
  subcollections: subcollectionsMerged,
3579
3587
  properties: sortProperties(propertiesMerged, mergedPropertiesOrder),
3580
3588
  propertiesOrder: mergedPropertiesOrder,
3581
- entityViews: mergedEntityViews
3589
+ entityViews: mergedEntityViews,
3590
+ entityActions: mergedEntityActions
3582
3591
  };
3583
3592
  if (modifyCollection) {
3584
3593
  const modifiedCollection = modifyCollection({
@@ -9572,6 +9581,7 @@ const EntityCollectionRowActions = function EntityCollectionRowActions2({
9572
9581
  uncollapsedActions.map((action, index) => /* @__PURE__ */ jsx(Tooltip, { title: action.name, asChild: true, children: /* @__PURE__ */ jsx(IconButton, { onClick: (event_0) => {
9573
9582
  event_0.stopPropagation();
9574
9583
  action.onClick({
9584
+ view: "collection",
9575
9585
  entity,
9576
9586
  fullPath,
9577
9587
  fullIdPath,
@@ -9587,6 +9597,7 @@ const EntityCollectionRowActions = function EntityCollectionRowActions2({
9587
9597
  hasCollapsedActions && /* @__PURE__ */ jsx(Menu, { trigger: /* @__PURE__ */ jsx(IconButton, { size: largeLayout ? "medium" : "small", children: /* @__PURE__ */ jsx(MoreVertIcon, {}) }), children: collapsedActions.map((action_0, index_0) => /* @__PURE__ */ jsxs(MenuItem, { onClick: (e) => {
9588
9598
  e.stopPropagation();
9589
9599
  action_0.onClick({
9600
+ view: "collection",
9590
9601
  entity,
9591
9602
  fullPath,
9592
9603
  fullIdPath,
@@ -9689,7 +9700,7 @@ function CollectionTableToolbar(t0) {
9689
9700
  }
9690
9701
  let t9;
9691
9702
  if ($[12] !== actionsStart || $[13] !== sizeSelect || $[14] !== t8) {
9692
- t9 = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 md:mr-4 mr-2", children: [
9703
+ t9 = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 md:mr-4 mr-2", children: [
9693
9704
  t8,
9694
9705
  sizeSelect,
9695
9706
  actionsStart
@@ -9722,7 +9733,7 @@ function CollectionTableToolbar(t0) {
9722
9733
  }
9723
9734
  let t12;
9724
9735
  if ($[23] !== actions || $[24] !== t10 || $[25] !== t11) {
9725
- t12 = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
9736
+ t12 = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
9726
9737
  t10,
9727
9738
  t11,
9728
9739
  actions
@@ -12011,7 +12022,7 @@ function encodeFilterAndSort(filterValues, sortBy) {
12011
12022
  }
12012
12023
  if (encodedValue !== void 0) {
12013
12024
  entries[encodeURIComponent(`${key}_op`)] = encodeURIComponent(op);
12014
- entries[encodeURIComponent(`${key}_value`)] = encodeURIComponent(encodedValue.toString());
12025
+ entries[encodeURIComponent(`${key}_value`)] = encodedValue ? encodeURIComponent(encodedValue.toString()) : "null";
12015
12026
  }
12016
12027
  }
12017
12028
  });
@@ -12391,10 +12402,13 @@ function getRecentIds(collectionId) {
12391
12402
  return JSON.parse(stored);
12392
12403
  }
12393
12404
  const editEntityAction = {
12394
- icon: /* @__PURE__ */ jsx(EditIcon, {}),
12405
+ icon: /* @__PURE__ */ jsx(EditIcon, { size: "small" }),
12395
12406
  key: "edit",
12396
12407
  name: "Edit",
12397
12408
  collapsed: false,
12409
+ isEnabled: ({
12410
+ entity
12411
+ }) => Boolean(entity),
12398
12412
  onClick({
12399
12413
  entity,
12400
12414
  collection,
@@ -12405,6 +12419,9 @@ const editEntityAction = {
12405
12419
  unhighlightEntity,
12406
12420
  openEntityMode
12407
12421
  }) {
12422
+ if (!entity) {
12423
+ throw new Error("INTERNAL: editEntityAction: Entity is undefined");
12424
+ }
12408
12425
  highlightEntity?.(entity);
12409
12426
  context.analyticsController?.onAnalyticsEvent?.("entity_click", {
12410
12427
  path: entity.path,
@@ -12434,9 +12451,12 @@ const editEntityAction = {
12434
12451
  }
12435
12452
  };
12436
12453
  const copyEntityAction = {
12437
- icon: /* @__PURE__ */ jsx(FileCopyIcon, {}),
12454
+ icon: /* @__PURE__ */ jsx(FileCopyIcon, { size: "small" }),
12438
12455
  name: "Copy",
12439
12456
  key: "copy",
12457
+ isEnabled: ({
12458
+ entity
12459
+ }) => Boolean(entity),
12440
12460
  onClick({
12441
12461
  entity,
12442
12462
  collection,
@@ -12446,6 +12466,9 @@ const copyEntityAction = {
12446
12466
  unhighlightEntity,
12447
12467
  openEntityMode
12448
12468
  }) {
12469
+ if (!entity) {
12470
+ throw new Error("INTERNAL: copyEntityAction: Entity is undefined");
12471
+ }
12449
12472
  highlightEntity?.(entity);
12450
12473
  context.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
12451
12474
  path: entity.path,
@@ -12468,9 +12491,12 @@ const copyEntityAction = {
12468
12491
  }
12469
12492
  };
12470
12493
  const deleteEntityAction = {
12471
- icon: /* @__PURE__ */ jsx(DeleteIcon, {}),
12494
+ icon: /* @__PURE__ */ jsx(DeleteIcon, { size: "small" }),
12472
12495
  name: "Delete",
12473
12496
  key: "delete",
12497
+ isEnabled: ({
12498
+ entity
12499
+ }) => Boolean(entity),
12474
12500
  onClick({
12475
12501
  entity,
12476
12502
  fullPath,
@@ -12478,8 +12504,11 @@ const deleteEntityAction = {
12478
12504
  context,
12479
12505
  selectionController,
12480
12506
  onCollectionChange,
12481
- sideEntityController
12507
+ navigateBack
12482
12508
  }) {
12509
+ if (!entity) {
12510
+ throw new Error("INTERNAL: deleteEntityAction: Entity is undefined");
12511
+ }
12483
12512
  const {
12484
12513
  closeDialog
12485
12514
  } = context.dialogsController.open({
@@ -12494,7 +12523,7 @@ const deleteEntityAction = {
12494
12523
  });
12495
12524
  selectionController?.setSelectedEntities(selectionController.selectedEntities.filter((e) => e.id !== entity.id));
12496
12525
  onCollectionChange?.();
12497
- sideEntityController?.close();
12526
+ navigateBack?.();
12498
12527
  }, onClose: closeDialog });
12499
12528
  }
12500
12529
  });
@@ -13361,53 +13390,51 @@ function SortableNavigationCard(t0) {
13361
13390
  isDragging
13362
13391
  } = useSortable(t1);
13363
13392
  let t2;
13364
- let t3;
13365
13393
  if ($[2] !== transform) {
13366
- t3 = transform ? CSS.Transform.toString(transform) : void 0;
13394
+ t2 = transform ? CSS.Transform.toString(transform) : void 0;
13367
13395
  $[2] = transform;
13368
- $[3] = t3;
13396
+ $[3] = t2;
13369
13397
  } else {
13370
- t3 = $[3];
13398
+ t2 = $[3];
13371
13399
  }
13372
- const t4 = isDragging ? 0 : 1;
13373
- let t5;
13374
- if ($[4] !== t3 || $[5] !== t4 || $[6] !== transition) {
13375
- t5 = {
13376
- transform: t3,
13400
+ const t3 = isDragging ? 0 : 1;
13401
+ let t4;
13402
+ if ($[4] !== t2 || $[5] !== t3 || $[6] !== transition) {
13403
+ t4 = {
13404
+ transform: t2,
13377
13405
  transition,
13378
- opacity: t4
13406
+ opacity: t3
13379
13407
  };
13380
- $[4] = t3;
13381
- $[5] = t4;
13408
+ $[4] = t2;
13409
+ $[5] = t3;
13382
13410
  $[6] = transition;
13383
- $[7] = t5;
13411
+ $[7] = t4;
13384
13412
  } else {
13385
- t5 = $[7];
13413
+ t4 = $[7];
13386
13414
  }
13387
- t2 = t5;
13388
- const style = t2;
13389
- let t6;
13415
+ const style = t4;
13416
+ let t5;
13390
13417
  if ($[8] !== entry || $[9] !== onClick) {
13391
- t6 = /* @__PURE__ */ jsx(NavigationCardBinding, { ...entry, onClick });
13418
+ t5 = /* @__PURE__ */ jsx(NavigationCardBinding, { ...entry, onClick });
13392
13419
  $[8] = entry;
13393
13420
  $[9] = onClick;
13394
- $[10] = t6;
13421
+ $[10] = t5;
13395
13422
  } else {
13396
- t6 = $[10];
13423
+ t5 = $[10];
13397
13424
  }
13398
- let t7;
13399
- if ($[11] !== attributes || $[12] !== listeners2 || $[13] !== setNodeRef || $[14] !== style || $[15] !== t6) {
13400
- t7 = /* @__PURE__ */ jsx("div", { ref: setNodeRef, style, ...attributes, ...listeners2, children: t6 });
13425
+ let t6;
13426
+ if ($[11] !== attributes || $[12] !== listeners2 || $[13] !== setNodeRef || $[14] !== style || $[15] !== t5) {
13427
+ t6 = /* @__PURE__ */ jsx("div", { ref: setNodeRef, style, ...attributes, ...listeners2, children: t5 });
13401
13428
  $[11] = attributes;
13402
13429
  $[12] = listeners2;
13403
13430
  $[13] = setNodeRef;
13404
13431
  $[14] = style;
13405
- $[15] = t6;
13406
- $[16] = t7;
13432
+ $[15] = t5;
13433
+ $[16] = t6;
13407
13434
  } else {
13408
- t7 = $[16];
13435
+ t6 = $[16];
13409
13436
  }
13410
- return t7;
13437
+ return t6;
13411
13438
  }
13412
13439
  function NavigationGroupDroppable(t0) {
13413
13440
  const $ = c(11);
@@ -13490,44 +13517,42 @@ function SortableNavigationGroup(t0) {
13490
13517
  isDragging
13491
13518
  } = useSortable(t1);
13492
13519
  let t2;
13493
- let t3;
13494
13520
  if ($[3] !== transform) {
13495
- t3 = transform ? CSS.Transform.toString(transform) : void 0;
13521
+ t2 = transform ? CSS.Transform.toString(transform) : void 0;
13496
13522
  $[3] = transform;
13497
- $[4] = t3;
13523
+ $[4] = t2;
13498
13524
  } else {
13499
- t3 = $[4];
13525
+ t2 = $[4];
13500
13526
  }
13501
- const t4 = isDragging ? 0 : 1;
13502
- let t5;
13503
- if ($[5] !== t3 || $[6] !== t4 || $[7] !== transition) {
13504
- t5 = {
13505
- transform: t3,
13527
+ const t3 = isDragging ? 0 : 1;
13528
+ let t4;
13529
+ if ($[5] !== t2 || $[6] !== t3 || $[7] !== transition) {
13530
+ t4 = {
13531
+ transform: t2,
13506
13532
  transition,
13507
- opacity: t4
13533
+ opacity: t3
13508
13534
  };
13509
- $[5] = t3;
13510
- $[6] = t4;
13535
+ $[5] = t2;
13536
+ $[6] = t3;
13511
13537
  $[7] = transition;
13512
- $[8] = t5;
13538
+ $[8] = t4;
13513
13539
  } else {
13514
- t5 = $[8];
13540
+ t4 = $[8];
13515
13541
  }
13516
- t2 = t5;
13517
- const style = t2;
13518
- let t6;
13542
+ const style = t4;
13543
+ let t5;
13519
13544
  if ($[9] !== attributes || $[10] !== children || $[11] !== listeners2 || $[12] !== setNodeRef || $[13] !== style) {
13520
- t6 = /* @__PURE__ */ jsx("div", { ref: setNodeRef, style, ...attributes, ...listeners2, children });
13545
+ t5 = /* @__PURE__ */ jsx("div", { ref: setNodeRef, style, ...attributes, ...listeners2, children });
13521
13546
  $[9] = attributes;
13522
13547
  $[10] = children;
13523
13548
  $[11] = listeners2;
13524
13549
  $[12] = setNodeRef;
13525
13550
  $[13] = style;
13526
- $[14] = t6;
13551
+ $[14] = t5;
13527
13552
  } else {
13528
- t6 = $[14];
13553
+ t5 = $[14];
13529
13554
  }
13530
- return t6;
13555
+ return t5;
13531
13556
  }
13532
13557
  function useHomePageDnd({
13533
13558
  items: dndItems,
@@ -13561,8 +13586,8 @@ function useHomePageDnd({
13561
13586
  }
13562
13587
  });
13563
13588
  const keyboardSensor = useSensor(KeyboardSensor);
13564
- const sensors = useSensors(...disabled ? [] : [mouseSensor, touchSensor, keyboardSensor]);
13565
- const dndContainers = useMemo(() => dndItems.map((g) => g.name), [dndItems]);
13589
+ const sensors = useSensors(mouseSensor, touchSensor, keyboardSensor);
13590
+ const dndContainers = dndItems.map((g) => g.name);
13566
13591
  const findDndContainer = useCallback((id) => {
13567
13592
  if (!id) return void 0;
13568
13593
  const group = dndItems.find((g_0) => g_0.name === id);
@@ -13773,14 +13798,8 @@ function useHomePageDnd({
13773
13798
  return updated;
13774
13799
  });
13775
13800
  };
13776
- const activeItemForOverlay = useMemo(() => {
13777
- if (disabled || !activeId || activeIsGroup) return null;
13778
- return dndItems.flatMap((g_19) => g_19.entries).find((e_7) => e_7.url === activeId) || null;
13779
- }, [activeId, dndItems, disabled, activeIsGroup]);
13780
- const activeGroupData = useMemo(() => {
13781
- if (disabled || !activeId || !activeIsGroup) return null;
13782
- return dndItems.find((g_20) => g_20.name === activeId) || null;
13783
- }, [activeId, dndItems, disabled, activeIsGroup]);
13801
+ const activeItemForOverlay = disabled || !activeId || activeIsGroup ? null : dndItems.flatMap((g_19) => g_19.entries).find((e_7) => e_7.url === activeId) || null;
13802
+ const activeGroupData = disabled || !activeId || !activeIsGroup ? null : dndItems.find((g_20) => g_20.name === activeId) || null;
13784
13803
  return {
13785
13804
  sensors,
13786
13805
  collisionDetection,
@@ -14061,7 +14080,7 @@ function RenameGroupDialog(t0) {
14061
14080
  }
14062
14081
  let t12;
14063
14082
  if ($[28] !== onClose) {
14064
- t12 = /* @__PURE__ */ jsx(Button, { onClick: onClose, variant: "text", children: "Cancel" });
14083
+ t12 = /* @__PURE__ */ jsx(Button, { onClick: onClose, color: "primary", variant: "text", children: "Cancel" });
14065
14084
  $[28] = onClose;
14066
14085
  $[29] = t12;
14067
14086
  } else {
@@ -14070,7 +14089,7 @@ function RenameGroupDialog(t0) {
14070
14089
  const t13 = !!error || !name.trim();
14071
14090
  let t14;
14072
14091
  if ($[30] !== handleSave || $[31] !== t13) {
14073
- t14 = /* @__PURE__ */ jsx(Button, { onClick: handleSave, disabled: t13, children: "Save" });
14092
+ t14 = /* @__PURE__ */ jsx(Button, { onClick: handleSave, color: "primary", disabled: t13, children: "Save" });
14074
14093
  $[30] = handleSave;
14075
14094
  $[31] = t13;
14076
14095
  $[32] = t14;
@@ -14126,26 +14145,32 @@ function DefaultHomePage({
14126
14145
  const fuse = useRef(null);
14127
14146
  const [filteredUrls, setFilteredUrls] = useState(null);
14128
14147
  const performingSearch = Boolean(filteredUrls);
14129
- const filteredNavigationEntries = filteredUrls ? rawNavigationEntries.filter((e) => filteredUrls.includes(e.url)) : rawNavigationEntries;
14148
+ const filteredNavigationEntries = useMemo(() => {
14149
+ return filteredUrls ? rawNavigationEntries.filter((e) => filteredUrls.includes(e.url)) : rawNavigationEntries;
14150
+ }, [filteredUrls, rawNavigationEntries]);
14130
14151
  useEffect(() => {
14131
14152
  fuse.current = new Fuse(rawNavigationEntries, {
14132
14153
  keys: ["name", "description", "group", "path"]
14133
14154
  });
14134
14155
  }, [rawNavigationEntries]);
14135
14156
  const updateSearch = useCallback((v) => {
14136
- if (!v?.trim()) return setFilteredUrls(null);
14137
- const r = fuse.current?.search(v.trim());
14138
- setFilteredUrls(r ? r.map((x) => x.item.url) : []);
14157
+ if (!v?.trim()) {
14158
+ setFilteredUrls(null);
14159
+ return;
14160
+ }
14161
+ const results = fuse.current?.search(v.trim());
14162
+ setFilteredUrls(results ? results.map((x) => x.item.url) : []);
14139
14163
  }, []);
14140
14164
  const [items, setItems] = useState([]);
14141
14165
  const [adminGroupData, setAdminGroupData] = useState(null);
14142
- useEffect(() => {
14143
- const src = performingSearch ? filteredNavigationEntries : rawNavigationEntries;
14166
+ const processedGroups = useMemo(() => {
14167
+ const src = filteredNavigationEntries;
14144
14168
  const entriesByGroup = {};
14145
14169
  src.forEach((e_0) => {
14146
14170
  const g = e_0.type === "admin" ? ADMIN_GROUP_NAME : e_0.group ?? DEFAULT_GROUP_NAME;
14147
14171
  (entriesByGroup[g] ??= []).push(e_0);
14148
14172
  });
14173
+ const hasPluginAdditionalCards = customizationController.plugins?.some((p) => p.homePage?.AdditionalCards);
14149
14174
  let allProcessed;
14150
14175
  if (performingSearch) {
14151
14176
  const ordered = [...new Set(src.map((e_1) => e_1.group ?? DEFAULT_GROUP_NAME))];
@@ -14164,24 +14189,31 @@ function DefaultHomePage({
14164
14189
  entries: entriesByGroup[g_2]
14165
14190
  });
14166
14191
  });
14167
- allProcessed = allProcessed.filter((g_3) => g_3.entries.length || groupOrderFromNavController.includes(g_3.name));
14168
- }
14169
- const admin = allProcessed.find((g_4) => g_4.name === ADMIN_GROUP_NAME);
14170
- if (admin) {
14171
- setAdminGroupData(admin);
14172
- setItems(allProcessed.filter((g_5) => g_5.name !== ADMIN_GROUP_NAME));
14173
- } else {
14174
- setAdminGroupData(null);
14175
- setItems(allProcessed);
14192
+ if (hasPluginAdditionalCards && !allProcessed.some((g_3) => g_3.name === DEFAULT_GROUP_NAME)) {
14193
+ allProcessed.push({
14194
+ name: DEFAULT_GROUP_NAME,
14195
+ entries: []
14196
+ });
14197
+ }
14198
+ allProcessed = allProcessed.filter((g_4) => g_4.entries.length || groupOrderFromNavController.includes(g_4.name) || g_4.name === DEFAULT_GROUP_NAME && hasPluginAdditionalCards);
14176
14199
  }
14177
- }, [performingSearch, filteredNavigationEntries, rawNavigationEntries, groupOrderFromNavController]);
14200
+ const admin = allProcessed.find((g_5) => g_5.name === ADMIN_GROUP_NAME);
14201
+ return {
14202
+ adminGroupData: admin || null,
14203
+ items: allProcessed.filter((g_6) => g_6.name !== ADMIN_GROUP_NAME)
14204
+ };
14205
+ }, [filteredNavigationEntries, performingSearch, groupOrderFromNavController, customizationController.plugins]);
14206
+ useEffect(() => {
14207
+ setAdminGroupData(processedGroups.adminGroupData);
14208
+ setItems(processedGroups.items);
14209
+ }, [processedGroups]);
14178
14210
  const updateItems = (updater) => {
14179
14211
  setItems(updater);
14180
14212
  };
14181
14213
  const persistNavigationGroups = (latest) => {
14182
- const draggable = latest.map((g_6) => ({
14183
- name: g_6.name,
14184
- entries: g_6.entries.map((e_2) => e_2.path)
14214
+ const draggable = latest.map((g_7) => ({
14215
+ name: g_7.name,
14216
+ entries: g_7.entries.map((e_2) => e_2.path)
14185
14217
  }));
14186
14218
  const all = adminGroupData ? [...draggable, {
14187
14219
  name: adminGroupData.name,
@@ -14214,8 +14246,8 @@ function DefaultHomePage({
14214
14246
  disabled: !allowDragAndDrop || performingSearch,
14215
14247
  onPersist: persistNavigationGroups,
14216
14248
  // ——► persistence here
14217
- onGroupMoved: (g_7) => context.analyticsController?.onAnalyticsEvent?.("home_move_group", {
14218
- name: g_7
14249
+ onGroupMoved: (g_8) => context.analyticsController?.onAnalyticsEvent?.("home_move_group", {
14250
+ name: g_8
14219
14251
  }),
14220
14252
  onCardMovedBetweenGroups: (card) => context.analyticsController?.onAnalyticsEvent?.("home_move_card", {
14221
14253
  id: card.id
@@ -14227,10 +14259,7 @@ function DefaultHomePage({
14227
14259
  direction
14228
14260
  } = useRestoreScroll();
14229
14261
  const dndDisabled = !allowDragAndDrop || performingSearch;
14230
- const dndModifiers = useMemo(() => {
14231
- if (dndKitActiveNode?.data.current?.type === "group") return [restrictToVerticalAxis, restrictToWindowEdges];
14232
- return [restrictToWindowEdges];
14233
- }, [dndKitActiveNode]);
14262
+ const dndModifiers = dndKitActiveNode?.data.current?.type === "group" ? [restrictToVerticalAxis, restrictToWindowEdges] : [restrictToWindowEdges];
14234
14263
  let additionalPluginChildrenStart;
14235
14264
  let additionalPluginChildrenEnd;
14236
14265
  let additionalPluginSections;
@@ -14238,12 +14267,12 @@ function DefaultHomePage({
14238
14267
  const sectionProps = {
14239
14268
  context
14240
14269
  };
14241
- additionalPluginSections = /* @__PURE__ */ jsx(Fragment, { children: customizationController.plugins.filter((p) => p.homePage?.includeSection).map((plugin) => {
14270
+ additionalPluginSections = /* @__PURE__ */ jsx(Fragment, { children: customizationController.plugins.filter((p_0) => p_0.homePage?.includeSection).map((plugin) => {
14242
14271
  const section = plugin.homePage.includeSection(sectionProps);
14243
14272
  return /* @__PURE__ */ jsx(NavigationGroup, { group: section.title, children: section.children }, `plugin_section_${plugin.key}`);
14244
14273
  }) });
14245
- additionalPluginChildrenStart = /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: customizationController.plugins.filter((p_0) => p_0.homePage?.additionalChildrenStart).map((plugin_0, i) => /* @__PURE__ */ jsx("div", { children: plugin_0.homePage.additionalChildrenStart }, `plugin_children_start_${i}`)) });
14246
- additionalPluginChildrenEnd = /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: customizationController.plugins.filter((p_1) => p_1.homePage?.additionalChildrenEnd).map((plugin_1, i_0) => /* @__PURE__ */ jsx("div", { children: plugin_1.homePage.additionalChildrenEnd }, `plugin_children_end_${i_0}`)) });
14274
+ additionalPluginChildrenStart = /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: customizationController.plugins.filter((p_1) => p_1.homePage?.additionalChildrenStart).map((plugin_0, i) => /* @__PURE__ */ jsx("div", { children: plugin_0.homePage.additionalChildrenStart }, `plugin_children_start_${i}`)) });
14275
+ additionalPluginChildrenEnd = /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: customizationController.plugins.filter((p_2) => p_2.homePage?.additionalChildrenEnd).map((plugin_1, i_0) => /* @__PURE__ */ jsx("div", { children: plugin_1.homePage.additionalChildrenEnd }, `plugin_children_end_${i_0}`)) });
14247
14276
  }
14248
14277
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "py-2 overflow-auto h-full w-full", children: [
14249
14278
  /* @__PURE__ */ jsxs(Container, { maxWidth: "6xl", children: [
@@ -14266,8 +14295,8 @@ function DefaultHomePage({
14266
14295
  const groupKey = groupData.name;
14267
14296
  const entriesInGroup = groupData.entries;
14268
14297
  const AdditionalCards = [];
14269
- customizationController.plugins?.forEach((p_2) => {
14270
- if (p_2.homePage?.AdditionalCards) AdditionalCards.push(...toArray(p_2.homePage.AdditionalCards));
14298
+ customizationController.plugins?.forEach((p_3) => {
14299
+ if (p_3.homePage?.AdditionalCards) AdditionalCards.push(...toArray(p_3.homePage.AdditionalCards));
14271
14300
  });
14272
14301
  const actionProps = {
14273
14302
  group: groupKey === DEFAULT_GROUP_NAME ? void 0 : groupKey,
@@ -14309,7 +14338,7 @@ function DefaultHomePage({
14309
14338
  additionalPluginChildrenEnd,
14310
14339
  additionalChildrenEnd
14311
14340
  ] }),
14312
- dialogOpenForGroup && /* @__PURE__ */ jsx(RenameGroupDialog, { open: true, initialName: dialogOpenForGroup, existingGroupNames: items.map((g_8) => g_8.name).filter((n) => n !== dialogOpenForGroup), onClose: () => setDialogOpenForGroup(null), onRename: (newName) => {
14341
+ dialogOpenForGroup && /* @__PURE__ */ jsx(RenameGroupDialog, { open: true, initialName: dialogOpenForGroup, existingGroupNames: items.map((g_9) => g_9.name).filter((n) => n !== dialogOpenForGroup), onClose: () => setDialogOpenForGroup(null), onRename: (newName) => {
14313
14342
  handleRenameGroup(dialogOpenForGroup, newName);
14314
14343
  setDialogOpenForGroup(null);
14315
14344
  } })
@@ -14374,11 +14403,11 @@ function EntityCollectionViewActions(t0) {
14374
14403
  if (selectionEnabled) {
14375
14404
  let t42;
14376
14405
  if ($[12] !== largeLayout || $[13] !== multipleDeleteEnabled || $[14] !== onMultipleDeleteClick || $[15] !== selectedEntities?.length) {
14377
- t42 = largeLayout ? /* @__PURE__ */ jsxs(Button, { variant: "text", disabled: !selectedEntities?.length || !multipleDeleteEnabled, startIcon: /* @__PURE__ */ jsx(DeleteIcon, {}), onClick: onMultipleDeleteClick, color: "primary", className: "lg:w-20", children: [
14406
+ t42 = largeLayout ? /* @__PURE__ */ jsxs(Button, { variant: "text", disabled: !selectedEntities?.length || !multipleDeleteEnabled, startIcon: /* @__PURE__ */ jsx(DeleteIcon, { size: "small" }), onClick: onMultipleDeleteClick, color: "primary", className: "lg:w-20", children: [
14378
14407
  "(",
14379
14408
  selectedEntities?.length,
14380
14409
  ")"
14381
- ] }) : /* @__PURE__ */ jsx(IconButton, { color: "primary", disabled: !selectedEntities?.length || !multipleDeleteEnabled, onClick: onMultipleDeleteClick, children: /* @__PURE__ */ jsx(DeleteIcon, {}) });
14410
+ ] }) : /* @__PURE__ */ jsx(IconButton, { size: "small", color: "primary", disabled: !selectedEntities?.length || !multipleDeleteEnabled, onClick: onMultipleDeleteClick, children: /* @__PURE__ */ jsx(DeleteIcon, { size: "small" }) });
14382
14411
  $[12] = largeLayout;
14383
14412
  $[13] = multipleDeleteEnabled;
14384
14413
  $[14] = onMultipleDeleteClick;
@@ -14690,7 +14719,7 @@ const getScrollableParent = (ele) => {
14690
14719
  return !ele || ele === document.body ? document.body : isScrollable(ele) ? ele : getScrollableParent(ele.parentNode);
14691
14720
  };
14692
14721
  function EntityFormActions(t0) {
14693
- const $ = c(14);
14722
+ const $ = c(16);
14694
14723
  const {
14695
14724
  fullPath,
14696
14725
  fullIdPath,
@@ -14702,12 +14731,14 @@ function EntityFormActions(t0) {
14702
14731
  disabled,
14703
14732
  status,
14704
14733
  pluginActions,
14705
- openEntityMode
14734
+ openEntityMode,
14735
+ navigateBack,
14736
+ formContext
14706
14737
  } = t0;
14707
14738
  const context = useFireCMSContext();
14708
14739
  const sideEntityController = useSideEntityController();
14709
14740
  let t1;
14710
- if ($[0] !== collection || $[1] !== context || $[2] !== disabled || $[3] !== entity || $[4] !== formex.isSubmitting || $[5] !== fullIdPath || $[6] !== fullPath || $[7] !== layout || $[8] !== openEntityMode || $[9] !== pluginActions || $[10] !== savingError || $[11] !== sideEntityController || $[12] !== status) {
14741
+ if ($[0] !== collection || $[1] !== context || $[2] !== disabled || $[3] !== entity || $[4] !== formContext || $[5] !== formex.isSubmitting || $[6] !== fullIdPath || $[7] !== fullPath || $[8] !== layout || $[9] !== navigateBack || $[10] !== openEntityMode || $[11] !== pluginActions || $[12] !== savingError || $[13] !== sideEntityController || $[14] !== status) {
14711
14742
  t1 = layout === "bottom" ? buildBottomActions$1({
14712
14743
  fullPath,
14713
14744
  fullIdPath,
@@ -14720,7 +14751,9 @@ function EntityFormActions(t0) {
14720
14751
  disabled,
14721
14752
  status,
14722
14753
  pluginActions,
14723
- openEntityMode
14754
+ openEntityMode,
14755
+ navigateBack,
14756
+ formContext
14724
14757
  }) : buildSideActions$1({
14725
14758
  fullPath,
14726
14759
  fullIdPath,
@@ -14733,24 +14766,28 @@ function EntityFormActions(t0) {
14733
14766
  disabled,
14734
14767
  status,
14735
14768
  pluginActions,
14736
- openEntityMode
14769
+ openEntityMode,
14770
+ navigateBack,
14771
+ formContext
14737
14772
  });
14738
14773
  $[0] = collection;
14739
14774
  $[1] = context;
14740
14775
  $[2] = disabled;
14741
14776
  $[3] = entity;
14742
- $[4] = formex.isSubmitting;
14743
- $[5] = fullIdPath;
14744
- $[6] = fullPath;
14745
- $[7] = layout;
14746
- $[8] = openEntityMode;
14747
- $[9] = pluginActions;
14748
- $[10] = savingError;
14749
- $[11] = sideEntityController;
14750
- $[12] = status;
14751
- $[13] = t1;
14777
+ $[4] = formContext;
14778
+ $[5] = formex.isSubmitting;
14779
+ $[6] = fullIdPath;
14780
+ $[7] = fullPath;
14781
+ $[8] = layout;
14782
+ $[9] = navigateBack;
14783
+ $[10] = openEntityMode;
14784
+ $[11] = pluginActions;
14785
+ $[12] = savingError;
14786
+ $[13] = sideEntityController;
14787
+ $[14] = status;
14788
+ $[15] = t1;
14752
14789
  } else {
14753
- t1 = $[13];
14790
+ t1 = $[15];
14754
14791
  }
14755
14792
  return t1;
14756
14793
  }
@@ -14767,24 +14804,29 @@ function buildBottomActions$1({
14767
14804
  disabled,
14768
14805
  status,
14769
14806
  pluginActions,
14770
- openEntityMode
14807
+ openEntityMode,
14808
+ navigateBack,
14809
+ formContext
14771
14810
  }) {
14772
14811
  return /* @__PURE__ */ jsxs(DialogActions, { position: "absolute", children: [
14773
14812
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) }),
14774
14813
  entity && (formActions ?? []).length > 0 && /* @__PURE__ */ jsx("div", { className: "flex-grow flex overflow-auto no-scrollbar", children: (formActions ?? []).map((action) => /* @__PURE__ */ jsx(IconButton, { color: "primary", onClick: (event) => {
14775
14814
  event.stopPropagation();
14776
14815
  if (entity) action.onClick({
14816
+ view: "form",
14777
14817
  entity,
14778
14818
  fullPath: fullPath ?? collection.path,
14779
14819
  fullIdPath: fullIdPath ?? collection.id,
14780
14820
  collection,
14781
14821
  context,
14782
14822
  sideEntityController,
14783
- openEntityMode
14823
+ openEntityMode,
14824
+ navigateBack,
14825
+ formContext
14784
14826
  });
14785
14827
  }, children: action.icon }, action.name)) }),
14786
14828
  pluginActions,
14787
- /* @__PURE__ */ jsx(Button, { variant: "text", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
14829
+ /* @__PURE__ */ jsx(Button, { variant: "text", disabled: disabled || isSubmitting, color: "primary", type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
14788
14830
  /* @__PURE__ */ jsxs(Button, { variant: "filled", color: "primary", type: "submit", disabled: disabled || isSubmitting, children: [
14789
14831
  status === "existing" && "Save",
14790
14832
  status === "copy" && "Create copy",
@@ -14796,6 +14838,9 @@ function buildSideActions$1({
14796
14838
  savingError,
14797
14839
  entity,
14798
14840
  formActions,
14841
+ fullPath,
14842
+ fullIdPath,
14843
+ openEntityMode,
14799
14844
  collection,
14800
14845
  context,
14801
14846
  sideEntityController,
@@ -14843,6 +14888,15 @@ function EntityForm({
14843
14888
  if (collection.customId && collection.formAutoSave) {
14844
14889
  console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
14845
14890
  }
14891
+ const sideEntityController = useSideEntityController();
14892
+ const navigationController = useNavigationController();
14893
+ const navigateBack = useCallback(() => {
14894
+ if (openEntityMode === "side_panel") {
14895
+ sideEntityController.close();
14896
+ } else {
14897
+ window.history.back();
14898
+ }
14899
+ }, []);
14846
14900
  const authController = useAuthController();
14847
14901
  const [status, setStatus] = useState(initialStatus);
14848
14902
  const updateStatus = (status_0) => {
@@ -15093,14 +15147,15 @@ function EntityForm({
15093
15147
  const pluginActions = [];
15094
15148
  const plugins = customizationController.plugins;
15095
15149
  const actionsDisabled = disabled || formex.isSubmitting || status === "existing" && !formex.dirty || Boolean(disabledProp);
15150
+ const parentCollectionIds = navigationController.getParentCollectionIds(path);
15096
15151
  if (plugins && collection) {
15097
15152
  const actionProps = {
15098
15153
  entityId,
15154
+ parentCollectionIds,
15099
15155
  path,
15100
15156
  status,
15101
15157
  collection,
15102
15158
  context,
15103
- currentEntityId: entityId,
15104
15159
  formContext,
15105
15160
  openEntityMode,
15106
15161
  disabled: actionsDisabled
@@ -15212,6 +15267,7 @@ function EntityForm({
15212
15267
  const formView = /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
15213
15268
  !Builder && /* @__PURE__ */ jsxs("div", { className: "w-full py-2 flex flex-col items-start my-4 lg:my-6", children: [
15214
15269
  /* @__PURE__ */ jsx(Typography, { className: "my-4 flex-grow line-clamp-1 " + (collection.hideIdFromForm ? "mb-6" : ""), variant: "h4", children: title ?? collection.singularName ?? collection.name }),
15270
+ !entity?.values && initialStatus === "existing" && /* @__PURE__ */ jsx(Alert, { color: "warning", size: "small", outerClassName: "w-full mb-4 text-xs", children: "This entity does not exist in the database" }),
15215
15271
  showEntityPath && /* @__PURE__ */ jsx(Alert, { color: "base", outerClassName: "w-full", size: "small", children: /* @__PURE__ */ jsxs("code", { className: "text-xs select-all text-text-secondary dark:text-text-secondary-dark", children: [
15216
15272
  entity?.path ?? path,
15217
15273
  "/",
@@ -15219,6 +15275,7 @@ function EntityForm({
15219
15275
  ] }) })
15220
15276
  ] }),
15221
15277
  children,
15278
+ initialEntityId && !entity && initialStatus !== "new" && /* @__PURE__ */ jsx(Alert, { color: "info", size: "small", children: "This entity does not exist in the database" }),
15222
15279
  !Builder && !collection.hideIdFromForm && /* @__PURE__ */ jsx(CustomIdField, { customId: collection.customId, entityId, status, onChange: setEntityId, error: entityIdError, loading: customIdLoading, entity }),
15223
15280
  entityId && formContext && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "mt-12 flex flex-col gap-8", ref: formRef, children: [
15224
15281
  formFields(),
@@ -15232,7 +15289,7 @@ function EntityForm({
15232
15289
  if (!resolvedCollection || !path) {
15233
15290
  throw Error("INTERNAL: Collection and path must be defined in form context");
15234
15291
  }
15235
- const dialogActions = /* @__PURE__ */ jsx(EntityFormActionsComponent, { collection: resolvedCollection, path, fullPath: path, fullIdPath, entity, layout: forceActionsAtTheBottom ? "bottom" : "side", savingError, formex, disabled: actionsDisabled, status, pluginActions, openEntityMode, showDefaultActions });
15292
+ const dialogActions = /* @__PURE__ */ jsx(EntityFormActionsComponent, { collection: resolvedCollection, path, fullPath: path, fullIdPath, entity, layout: forceActionsAtTheBottom ? "bottom" : "side", savingError, formex, disabled: actionsDisabled, status, pluginActions, openEntityMode, showDefaultActions, navigateBack, formContext });
15236
15293
  return /* @__PURE__ */ jsx(Formex, { value: formex, children: /* @__PURE__ */ jsxs("form", { onSubmit: formex.handleSubmit, onReset: () => formex.resetForm({
15237
15294
  values: getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs)
15238
15295
  }), noValidate: true, className: cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", className), children: [
@@ -16087,7 +16144,7 @@ function StorageUploadFieldBinding(t0) {
16087
16144
  const resolvedProperty = t6;
16088
16145
  let t7;
16089
16146
  if ($[21] !== minimalistView || $[22] !== property || $[23] !== propertyKey) {
16090
- t7 = !minimalistView && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, { propertyKey, icon: getIconForProperty(property, "small"), required: property.validation?.required, title: property.name, className: "h-8text-text-secondary dark:text-text-secondary-dark ml-3.5" });
16147
+ t7 = !minimalistView && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, { propertyKey, icon: getIconForProperty(property, "small"), required: property.validation?.required, title: property.name, className: "h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5" });
16091
16148
  $[21] = minimalistView;
16092
16149
  $[22] = property;
16093
16150
  $[23] = propertyKey;
@@ -18993,6 +19050,9 @@ function MarkdownEditorFieldBinding({
18993
19050
  const [fieldVersion, setFieldVersion] = useState(0);
18994
19051
  const internalValue = useRef(value);
18995
19052
  const onContentChange = useCallback((content) => {
19053
+ if (content === value || value === null && content === "") {
19054
+ return;
19055
+ }
18996
19056
  internalValue.current = content;
18997
19057
  setValue(content);
18998
19058
  }, [setValue]);
@@ -19992,9 +20052,10 @@ const EntityCollectionView = React__default.memo(function EntityCollectionView2(
19992
20052
  frozen
19993
20053
  }) => {
19994
20054
  const isSelected = Boolean(usedSelectionController.selectedEntities.find((e_1) => e_1.id == entity_6.id && e_1.path == entity_6.path));
20055
+ const customEntityActions_0 = (collection.entityActions ?? []).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
19995
20056
  const actions_0 = getActionsForEntity({
19996
20057
  entity: entity_6,
19997
- customEntityActions: collection.entityActions
20058
+ customEntityActions: customEntityActions_0
19998
20059
  });
19999
20060
  return /* @__PURE__ */ jsx(EntityCollectionRowActions, { entity: entity_6, width: width_0, frozen, isSelected, selectionEnabled, size: size_0, highlightEntity: setHighlightedEntity, unhighlightEntity: unselectNavigatedEntity, collection, fullPath, fullIdPath, actions: actions_0, hideId: collection?.hideIdFromCollection, onCollectionChange: updateLastDeleteTimestamp, selectionController: usedSelectionController, openEntityMode });
20000
20061
  }, [updateLastDeleteTimestamp, usedSelectionController]);
@@ -20354,7 +20415,7 @@ function ConfirmationDialog(t0) {
20354
20415
  }
20355
20416
  let t4;
20356
20417
  if ($[6] !== onCancel) {
20357
- t4 = /* @__PURE__ */ jsx(Button, { variant: "text", onClick: onCancel, autoFocus: true, children: "Cancel" });
20418
+ t4 = /* @__PURE__ */ jsx(Button, { color: "primary", variant: "text", onClick: onCancel, autoFocus: true, children: "Cancel" });
20358
20419
  $[6] = onCancel;
20359
20420
  $[7] = t4;
20360
20421
  } else {
@@ -22257,7 +22318,7 @@ function UnsavedChangesDialog(t0) {
22257
22318
  }
22258
22319
  let t5;
22259
22320
  if ($[7] !== handleCancel) {
22260
- t5 = /* @__PURE__ */ jsx(Button, { variant: "text", onClick: handleCancel, autoFocus: true, children: " Cancel " });
22321
+ t5 = /* @__PURE__ */ jsx(Button, { variant: "text", color: "primary", onClick: handleCancel, autoFocus: true, children: " Cancel " });
22261
22322
  $[7] = handleCancel;
22262
22323
  $[8] = t5;
22263
22324
  } else {
@@ -22265,7 +22326,7 @@ function UnsavedChangesDialog(t0) {
22265
22326
  }
22266
22327
  let t6;
22267
22328
  if ($[9] !== handleOk) {
22268
- t6 = /* @__PURE__ */ jsx(Button, { onClick: handleOk, children: " Ok " });
22329
+ t6 = /* @__PURE__ */ jsx(Button, { color: "primary", onClick: handleOk, children: " Ok " });
22269
22330
  $[9] = handleOk;
22270
22331
  $[10] = t6;
22271
22332
  } else {
@@ -22409,14 +22470,17 @@ function EntityEditViewFormActions({
22409
22470
  status,
22410
22471
  pluginActions,
22411
22472
  openEntityMode,
22412
- showDefaultActions = true
22473
+ showDefaultActions = true,
22474
+ navigateBack,
22475
+ formContext
22413
22476
  }) {
22414
22477
  const authController = useAuthController();
22415
22478
  const context = useFireCMSContext();
22416
22479
  const sideEntityController = useSideEntityController();
22417
22480
  const sideDialogContext = useSideDialogContext();
22481
+ const customizationController = useCustomizationController();
22418
22482
  const entityActions = useMemo(() => {
22419
- const customEntityActions = collection.entityActions;
22483
+ const customEntityActions = (collection.entityActions ?? []).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
22420
22484
  const createEnabled = canCreateEntity(collection, authController, path, null);
22421
22485
  const deleteEnabled = entity ? canDeleteEntity(collection, authController, path, entity) : false;
22422
22486
  const actions = [];
@@ -22424,7 +22488,7 @@ function EntityEditViewFormActions({
22424
22488
  if (deleteEnabled) actions.push(deleteEntityAction);
22425
22489
  if (customEntityActions) return mergeEntityActions(actions, customEntityActions);
22426
22490
  return actions;
22427
- }, [authController, collection, path]);
22491
+ }, [authController, collection, path, customizationController.entityActions?.length]);
22428
22492
  const formActions = showDefaultActions ? entityActions.filter((a) => a.includeInForm === void 0 || a.includeInForm) : [];
22429
22493
  return layout === "bottom" ? buildBottomActions({
22430
22494
  savingError,
@@ -22438,7 +22502,9 @@ function EntityEditViewFormActions({
22438
22502
  status,
22439
22503
  sideDialogContext,
22440
22504
  pluginActions,
22441
- openEntityMode
22505
+ openEntityMode,
22506
+ navigateBack,
22507
+ formContext
22442
22508
  }) : buildSideActions({
22443
22509
  savingError,
22444
22510
  entity,
@@ -22451,7 +22517,9 @@ function EntityEditViewFormActions({
22451
22517
  disabled,
22452
22518
  status,
22453
22519
  pluginActions,
22454
- openEntityMode
22520
+ openEntityMode,
22521
+ navigateBack,
22522
+ formContext
22455
22523
  });
22456
22524
  }
22457
22525
  function buildBottomActions({
@@ -22466,22 +22534,28 @@ function buildBottomActions({
22466
22534
  status,
22467
22535
  sideDialogContext,
22468
22536
  pluginActions,
22469
- openEntityMode
22537
+ openEntityMode,
22538
+ navigateBack,
22539
+ formContext
22470
22540
  }) {
22471
22541
  const canClose = openEntityMode === "side_panel";
22472
22542
  return /* @__PURE__ */ jsxs(DialogActions, { position: "absolute", children: [
22473
22543
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) }),
22474
- entity && formActions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex-grow flex overflow-auto no-scrollbar", children: formActions.map((action) => /* @__PURE__ */ jsx(IconButton, { color: "primary", onClick: (event) => {
22475
- event.stopPropagation();
22476
- if (entity) action.onClick({
22544
+ formActions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex-grow flex overflow-auto no-scrollbar", children: formActions.map((action) => {
22545
+ const props = {
22546
+ view: "form",
22477
22547
  entity,
22478
22548
  fullPath: collection.path,
22479
22549
  collection,
22480
22550
  context,
22481
22551
  sideEntityController,
22482
- openEntityMode
22483
- });
22484
- }, children: action.icon }, action.name)) }),
22552
+ openEntityMode,
22553
+ navigateBack,
22554
+ formContext
22555
+ };
22556
+ const isEnabled = !action.isEnabled || action.isEnabled(props);
22557
+ return /* @__PURE__ */ jsx(EntityActionButton, { action, enabled: isEnabled, props }, action.key);
22558
+ }) }),
22485
22559
  pluginActions,
22486
22560
  /* @__PURE__ */ jsx(Button, { variant: "text", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22487
22561
  /* @__PURE__ */ jsxs(Button, { variant: canClose ? "text" : "filled", color: "primary", type: "submit", disabled: disabled || isSubmitting, onClick: () => {
@@ -22511,7 +22585,10 @@ function buildSideActions({
22511
22585
  disabled,
22512
22586
  status,
22513
22587
  sideDialogContext,
22514
- pluginActions
22588
+ pluginActions,
22589
+ openEntityMode,
22590
+ navigateBack,
22591
+ formContext
22515
22592
  }) {
22516
22593
  return /* @__PURE__ */ jsxs("div", { className: cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin), children: [
22517
22594
  /* @__PURE__ */ jsxs(LoadingButton, { fullWidth: true, variant: "filled", color: "primary", type: "submit", size: "large", disabled: disabled || isSubmitting, onClick: () => {
@@ -22523,9 +22600,104 @@ function buildSideActions({
22523
22600
  ] }),
22524
22601
  /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "text", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22525
22602
  pluginActions,
22603
+ formActions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-row flex-wrap mt-2", children: formActions.map((action) => {
22604
+ const props = {
22605
+ view: "form",
22606
+ entity,
22607
+ fullPath: collection.path,
22608
+ collection,
22609
+ context,
22610
+ sideEntityController,
22611
+ openEntityMode,
22612
+ navigateBack,
22613
+ formContext
22614
+ };
22615
+ const isEnabled = !action.isEnabled || action.isEnabled(props);
22616
+ return /* @__PURE__ */ jsx(EntityActionButton, { action, enabled: isEnabled, props }, action.key);
22617
+ }) }),
22526
22618
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) })
22527
22619
  ] });
22528
22620
  }
22621
+ function EntityActionButton(t0) {
22622
+ const $ = c(14);
22623
+ const {
22624
+ action,
22625
+ enabled,
22626
+ props
22627
+ } = t0;
22628
+ const snackbarController = useSnackbarController();
22629
+ const [loading, setLoading] = React__default.useState(false);
22630
+ const t1 = !enabled;
22631
+ let t2;
22632
+ if ($[0] !== action || $[1] !== props || $[2] !== snackbarController) {
22633
+ t2 = (event) => {
22634
+ console.debug("Executing action", action.key, props);
22635
+ try {
22636
+ event.stopPropagation();
22637
+ if (props.entity) {
22638
+ const onClick = action.onClick(props);
22639
+ if (onClick instanceof Promise) {
22640
+ setLoading(true);
22641
+ onClick.catch((error) => {
22642
+ console.error("Error executing action", action.key, error);
22643
+ snackbarController.open({
22644
+ message: `Error executing action: ${error.message}`,
22645
+ type: "error"
22646
+ });
22647
+ }).finally(() => setLoading(false));
22648
+ } else {
22649
+ snackbarController.open({
22650
+ message: `Action ${action.name} executed successfully`,
22651
+ type: "success"
22652
+ });
22653
+ }
22654
+ }
22655
+ } catch (t32) {
22656
+ const e = t32;
22657
+ console.error("Error executing action", action.key, e);
22658
+ snackbarController.open({
22659
+ message: `Error executing action: ${e.message}`,
22660
+ type: "error"
22661
+ });
22662
+ }
22663
+ };
22664
+ $[0] = action;
22665
+ $[1] = props;
22666
+ $[2] = snackbarController;
22667
+ $[3] = t2;
22668
+ } else {
22669
+ t2 = $[3];
22670
+ }
22671
+ let t3;
22672
+ if ($[4] !== action.icon || $[5] !== loading) {
22673
+ t3 = loading ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : action.icon;
22674
+ $[4] = action.icon;
22675
+ $[5] = loading;
22676
+ $[6] = t3;
22677
+ } else {
22678
+ t3 = $[6];
22679
+ }
22680
+ let t4;
22681
+ if ($[7] !== t1 || $[8] !== t2 || $[9] !== t3) {
22682
+ t4 = /* @__PURE__ */ jsx(IconButton, { color: "primary", disabled: t1, onClick: t2, children: t3 });
22683
+ $[7] = t1;
22684
+ $[8] = t2;
22685
+ $[9] = t3;
22686
+ $[10] = t4;
22687
+ } else {
22688
+ t4 = $[10];
22689
+ }
22690
+ let t5;
22691
+ if ($[11] !== action.name || $[12] !== t4) {
22692
+ t5 = /* @__PURE__ */ jsx(Tooltip, { title: action.name, children: t4 });
22693
+ $[11] = action.name;
22694
+ $[12] = t4;
22695
+ $[13] = t5;
22696
+ } else {
22697
+ t5 = $[13];
22698
+ }
22699
+ return t5;
22700
+ }
22529
22701
  function EntityJsonPreview(t0) {
22530
22702
  const $ = c(6);
22531
22703
  const {
@@ -22701,6 +22873,22 @@ function EntityEditViewInner({
22701
22873
  const [formContext, setFormContext] = useState(void 0);
22702
22874
  const largeLayout = useLargeLayout();
22703
22875
  const customizationController = useCustomizationController();
22876
+ const plugins = customizationController.plugins;
22877
+ const pluginActionsTop = [];
22878
+ if (plugins && collection) {
22879
+ const actionProps = {
22880
+ entityId,
22881
+ parentCollectionIds,
22882
+ path,
22883
+ status,
22884
+ collection,
22885
+ context,
22886
+ formContext,
22887
+ openEntityMode: layout,
22888
+ disabled: false
22889
+ };
22890
+ pluginActionsTop.push(...plugins.map((plugin) => plugin.form?.ActionsTop ? /* @__PURE__ */ jsx(plugin.form.ActionsTop, { ...actionProps }, `actions_${plugin.key}`) : null).filter(Boolean));
22891
+ }
22704
22892
  const defaultSelectedView = useMemo(() => resolveDefaultSelectedView(collection ? collection.defaultSelectedView : void 0, {
22705
22893
  status,
22706
22894
  entityId
@@ -22717,7 +22905,6 @@ function EntityEditViewInner({
22717
22905
  const customViewsCount = customViews?.length ?? 0;
22718
22906
  const includeJsonView = collection.includeJsonView === void 0 ? true : collection.includeJsonView;
22719
22907
  const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0 || includeJsonView;
22720
- const plugins = customizationController.plugins;
22721
22908
  const {
22722
22909
  resolvedEntityViews,
22723
22910
  selectedEntityView,
@@ -22775,13 +22962,6 @@ function EntityEditViewInner({
22775
22962
  const subcollectionId = subcollection.id ?? subcollection.path;
22776
22963
  const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollection.path)}` : void 0;
22777
22964
  const newFullIdPath = fullIdPath ? `${fullIdPath}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : void 0;
22778
- console.debug("Rendering subcollection", {
22779
- subcollectionId,
22780
- fullIdPath,
22781
- newFullPath,
22782
- newFullIdPath,
22783
- selectedTab
22784
- });
22785
22965
  if (selectedTab !== subcollectionId) return null;
22786
22966
  return /* @__PURE__ */ jsxs("div", { className: "relative flex-1 h-full overflow-auto w-full", role: "tabpanel", children: [
22787
22967
  globalLoading && /* @__PURE__ */ jsx(CircularProgressCenter, {}),
@@ -22826,11 +23006,12 @@ function EntityEditViewInner({
22826
23006
  const customViewTabsEnd = resolvedEntityViews.filter((view_1) => !view_1.position || view_1.position === "end").map((view_2) => /* @__PURE__ */ jsx(Tab, { className: !view_2.tabComponent ? "text-sm min-w-[120px]" : void 0, value: view_2.key, children: view_2.tabComponent ?? view_2.name }, `entity_detail_collection_tab_${view_2.name}`));
22827
23007
  const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
22828
23008
  let result = /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col h-full w-full bg-white dark:bg-surface-900", children: [
22829
- shouldShowTopBar && /* @__PURE__ */ jsxs("div", { className: cls("h-14 flex overflow-visible overflow-x-scroll w-full no-scrollbar h-14 border-b pl-2 pr-2 pt-1 flex items-end bg-surface-50 dark:bg-surface-900", defaultBorderMixin), children: [
23009
+ shouldShowTopBar && /* @__PURE__ */ jsxs("div", { className: cls("h-14 items-center flex overflow-visible overflow-x-scroll w-full no-scrollbar h-14 border-b pl-2 pr-2 pt-1 flex bg-surface-50 dark:bg-surface-900", defaultBorderMixin), children: [
22830
23010
  barActions,
22831
23011
  /* @__PURE__ */ jsx("div", { className: "flex-grow" }),
23012
+ pluginActionsTop,
22832
23013
  globalLoading && /* @__PURE__ */ jsx("div", { className: "self-center", children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" }) }),
22833
- hasAdditionalViews && /* @__PURE__ */ jsxs(Tabs, { value: selectedTab, onValueChange: (value_1) => {
23014
+ hasAdditionalViews && /* @__PURE__ */ jsxs(Tabs, { className: "self-end", value: selectedTab, onValueChange: (value_1) => {
22834
23015
  onSideTabClick(value_1);
22835
23016
  }, children: [
22836
23017
  includeJsonView && /* @__PURE__ */ jsx(Tab, { disabled: !hasAdditionalViews, value: JSON_TAB_VALUE, className: "text-sm", children: /* @__PURE__ */ jsx(CodeIcon, { size: "small" }) }),
@@ -22849,9 +23030,9 @@ function EntityEditViewInner({
22849
23030
  subCollectionsViews
22850
23031
  ] });
22851
23032
  if (plugins) {
22852
- plugins.forEach((plugin) => {
22853
- if (plugin.form?.provider) {
22854
- result = /* @__PURE__ */ jsx(plugin.form.provider.Component, { status, path, collection, entity: usedEntity, context, formContext, ...plugin.form.provider.props, children: result });
23033
+ plugins.forEach((plugin_0) => {
23034
+ if (plugin_0.form?.provider) {
23035
+ result = /* @__PURE__ */ jsx(plugin_0.form.provider.Component, { status, path, collection, entity: usedEntity, context, formContext, ...plugin_0.form.provider.props, children: result });
22855
23036
  }
22856
23037
  });
22857
23038
  }
@@ -22905,7 +23086,7 @@ function EntitySidePanel(props) {
22905
23086
  const parentCollectionIds = useMemo(() => {
22906
23087
  return navigationController.getParentCollectionIds(path);
22907
23088
  }, [navigationController, path]);
22908
- const collection = props.collection ?? navigationController.getCollection(path);
23089
+ const collection = navigationController.getCollection(fullIdPath ?? path) ?? props.collection;
22909
23090
  useEffect(() => {
22910
23091
  function beforeunload(e) {
22911
23092
  if (blocked && collection) {
@@ -23687,6 +23868,7 @@ function FireCMS(props) {
23687
23868
  onAnalyticsEvent,
23688
23869
  propertyConfigs,
23689
23870
  entityViews,
23871
+ entityActions,
23690
23872
  components,
23691
23873
  navigationController,
23692
23874
  apiKey
@@ -23705,6 +23887,7 @@ function FireCMS(props) {
23705
23887
  entityLinkBuilder,
23706
23888
  plugins,
23707
23889
  entityViews: entityViews ?? [],
23890
+ entityActions: entityActions ?? [],
23708
23891
  propertyConfigs: propertyConfigs ?? {},
23709
23892
  components
23710
23893
  };
@@ -25580,6 +25763,7 @@ export {
25580
25763
  resolveCollection,
25581
25764
  resolveCollectionPathIds,
25582
25765
  resolveDefaultSelectedView,
25766
+ resolveEntityAction,
25583
25767
  resolveEntityView,
25584
25768
  resolveEnumValues,
25585
25769
  resolveNavigationFrom,