@firecms/core 3.3.0 → 3.4.0-canary.0ef7442

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 (124) hide show
  1. package/dist/app/useApp.d.ts +1 -0
  2. package/dist/components/EntityCollectionTable/column_utils.d.ts +4 -3
  3. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +6 -1
  4. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +3 -1
  5. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +3 -1
  6. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +7 -0
  7. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +2 -1
  8. package/dist/components/EntityCollectionView/useBoardDataController.d.ts +3 -1
  9. package/dist/components/EntityPreview.d.ts +3 -1
  10. package/dist/components/ReferenceTable/ReferenceSelectionTable.d.ts +10 -1
  11. package/dist/components/SelectableTable/filters/filter_text_input.d.ts +30 -0
  12. package/dist/components/SelectableTable/filters/filter_text_input.test.d.ts +1 -0
  13. package/dist/components/common/table_url_params.d.ts +24 -0
  14. package/dist/components/common/useDataSourceTableController.d.ts +7 -1
  15. package/dist/components/common/useTableSearchHelper.d.ts +7 -1
  16. package/dist/core/DefaultDrawer.d.ts +10 -3
  17. package/dist/core/EntityEditView.d.ts +7 -1
  18. package/dist/core/field_configs.d.ts +1 -1
  19. package/dist/form/EntityForm.d.ts +6 -1
  20. package/dist/form/field_bindings/GeopointFieldBinding.d.ts +5 -0
  21. package/dist/form/index.d.ts +1 -0
  22. package/dist/hooks/data/save.d.ts +1 -1
  23. package/dist/hooks/data/useCollectionFetch.d.ts +7 -1
  24. package/dist/hooks/data/useEntityFetch.d.ts +6 -1
  25. package/dist/index.es.js +2915 -1330
  26. package/dist/index.es.js.map +1 -1
  27. package/dist/index.umd.js +2913 -1328
  28. package/dist/index.umd.js.map +1 -1
  29. package/dist/locales/pl.d.ts +2 -0
  30. package/dist/preview/index.d.ts +1 -0
  31. package/dist/preview/property_previews/GeopointPropertyPreview.d.ts +4 -0
  32. package/dist/types/collections.d.ts +24 -0
  33. package/dist/types/datasource.d.ts +59 -4
  34. package/dist/types/entities.d.ts +21 -1
  35. package/dist/types/properties.d.ts +17 -1
  36. package/dist/types/side_entity_controller.d.ts +6 -0
  37. package/dist/types/translations.d.ts +6 -0
  38. package/dist/util/__tests__/collections.test.d.ts +1 -0
  39. package/dist/util/__tests__/urls.test.d.ts +1 -0
  40. package/dist/util/collections.d.ts +1 -1
  41. package/dist/util/entities.d.ts +1 -0
  42. package/dist/util/geopoint.d.ts +22 -0
  43. package/dist/util/index.d.ts +2 -0
  44. package/dist/util/navigation_blocking.d.ts +22 -0
  45. package/dist/util/navigation_from_path.d.ts +17 -0
  46. package/dist/util/navigation_utils.d.ts +23 -1
  47. package/dist/util/parent_references_from_path.d.ts +1 -0
  48. package/dist/util/urls.d.ts +22 -0
  49. package/package.json +17 -10
  50. package/src/app/Scaffold.tsx +34 -44
  51. package/src/app/useApp.tsx +1 -0
  52. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +3 -1
  53. package/src/components/EntityCollectionTable/column_utils.tsx +11 -19
  54. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -1
  55. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +7 -0
  56. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -1
  57. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +4 -0
  58. package/src/components/EntityCollectionView/EntityCollectionView.tsx +29 -3
  59. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +4 -1
  60. package/src/components/EntityCollectionView/FiltersDialog.tsx +39 -28
  61. package/src/components/EntityCollectionView/useBoardDataController.tsx +11 -0
  62. package/src/components/EntityPreview.tsx +41 -40
  63. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +11 -0
  64. package/src/components/ReferenceWidget.tsx +1 -1
  65. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +2 -2
  66. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +33 -7
  67. package/src/components/SelectableTable/filters/filter_text_input.test.ts +184 -0
  68. package/src/components/SelectableTable/filters/filter_text_input.ts +71 -0
  69. package/src/components/VirtualTable/VirtualTable.tsx +19 -8
  70. package/src/components/common/table_url_params.ts +172 -0
  71. package/src/components/common/useDataSourceTableController.tsx +104 -159
  72. package/src/components/common/useTableSearchHelper.ts +21 -2
  73. package/src/contexts/SnackbarProvider.tsx +14 -1
  74. package/src/core/DefaultDrawer.tsx +150 -64
  75. package/src/core/DrawerNavigationGroup.tsx +27 -29
  76. package/src/core/DrawerNavigationItem.tsx +10 -12
  77. package/src/core/EntityEditView.tsx +78 -32
  78. package/src/core/EntitySidePanel.tsx +2 -0
  79. package/src/core/field_configs.tsx +15 -0
  80. package/src/form/EntityForm.tsx +9 -3
  81. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -1
  82. package/src/form/field_bindings/GeopointFieldBinding.tsx +139 -0
  83. package/src/form/index.tsx +1 -0
  84. package/src/hooks/data/delete.ts +2 -0
  85. package/src/hooks/data/save.ts +2 -0
  86. package/src/hooks/data/useCollectionFetch.tsx +13 -0
  87. package/src/hooks/data/useEntityFetch.tsx +18 -0
  88. package/src/hooks/useResolvedNavigationFrom.tsx +2 -0
  89. package/src/hooks/useValidateAuthenticator.tsx +4 -0
  90. package/src/i18n/FireCMSi18nProvider.tsx +2 -0
  91. package/src/internal/useBuildDataSource.ts +25 -4
  92. package/src/internal/useBuildSideEntityController.tsx +8 -1
  93. package/src/locales/de.ts +7 -2
  94. package/src/locales/en.ts +7 -2
  95. package/src/locales/es.ts +7 -2
  96. package/src/locales/fr.ts +7 -2
  97. package/src/locales/hi.ts +7 -2
  98. package/src/locales/it.ts +7 -2
  99. package/src/locales/pl.ts +735 -0
  100. package/src/locales/pt.ts +7 -2
  101. package/src/preview/PropertyPreview.tsx +12 -0
  102. package/src/preview/components/StorageThumbnail.tsx +24 -2
  103. package/src/preview/index.ts +1 -0
  104. package/src/preview/property_previews/GeopointPropertyPreview.tsx +23 -0
  105. package/src/routes/FireCMSRoute.tsx +47 -25
  106. package/src/types/collections.ts +26 -0
  107. package/src/types/datasource.ts +59 -4
  108. package/src/types/entities.ts +24 -1
  109. package/src/types/properties.ts +17 -0
  110. package/src/types/side_entity_controller.tsx +6 -0
  111. package/src/types/translations.ts +6 -0
  112. package/src/util/__tests__/collections.test.ts +32 -0
  113. package/src/util/__tests__/urls.test.ts +131 -0
  114. package/src/util/collections.ts +1 -1
  115. package/src/util/entities.ts +16 -1
  116. package/src/util/geopoint.ts +81 -0
  117. package/src/util/index.ts +2 -0
  118. package/src/util/navigation_blocking.ts +45 -0
  119. package/src/util/navigation_from_path.ts +41 -6
  120. package/src/util/navigation_utils.ts +40 -2
  121. package/src/util/parent_references_from_path.ts +14 -4
  122. package/src/util/previews.ts +8 -1
  123. package/src/util/resolutions.ts +8 -0
  124. package/src/util/urls.ts +52 -0
@@ -71,6 +71,11 @@ export type OnUpdateParams = {
71
71
 
72
72
  export type EntityFormProps<M extends Record<string, any>> = {
73
73
  path: string;
74
+ /**
75
+ * `path` split at its real segment boundaries; forwarded to the datasource for
76
+ * generateEntityId and checkUniqueField. Never derived from `path`.
77
+ */
78
+ pathSegments?: string[];
74
79
  fullIdPath?: string;
75
80
  collection: EntityCollection<M>;
76
81
  entityId?: string;
@@ -214,6 +219,7 @@ export function getChanges<T extends object>(source: Partial<T>, comparison: Par
214
219
 
215
220
  export function EntityForm<M extends Record<string, any>>({
216
221
  path,
222
+ pathSegments,
217
223
  fullIdPath,
218
224
  entityId: entityIdProp,
219
225
  collection,
@@ -292,7 +298,7 @@ export function EntityForm<M extends Record<string, any>>({
292
298
  if (mustSetCustomId) {
293
299
  return undefined;
294
300
  } else {
295
- return dataSource.generateEntityId(path, collection);
301
+ return dataSource.generateEntityId(path, collection, pathSegments);
296
302
  }
297
303
  } else {
298
304
  return entityIdProp;
@@ -388,7 +394,7 @@ export function EntityForm<M extends Record<string, any>>({
388
394
  },
389
395
  onValuesChangeDeferred: (values: M, controller: FormexController<M>) => {
390
396
  const key = (status === "new" || status === "copy") ? path + "#new" : path + "/" + entityId;
391
- if (controller.dirty) {
397
+ if (controller.dirty && localChangesBackup !== false) {
392
398
  const touchedValues = removeEmptyContainers(extractTouchedValues(values, controller.touched));
393
399
  if (touchedValues && Object.keys(touchedValues).length > 0) {
394
400
  saveEntityToCache(key, touchedValues);
@@ -692,7 +698,7 @@ export function EntityForm<M extends Record<string, any>>({
692
698
  const uniqueFieldValidator: CustomFieldValidator = useCallback(({
693
699
  name,
694
700
  value
695
- }) => dataSource.checkUniqueField(path, name, value, entityId, collection),
701
+ }) => dataSource.checkUniqueField(path, name, value, entityId, collection, pathSegments),
696
702
  [dataSource, path, entityId]);
697
703
 
698
704
  const validationSchema = useMemo(() => entityId
@@ -58,7 +58,7 @@ export function ArrayOfReferencesFieldBinding({
58
58
  }
59
59
 
60
60
  const onMultipleEntitiesSelected = useCallback((entities: Entity<any>[]) => {
61
- setValue(entities.map(e => getReferenceFrom(e)));
61
+ setValue(entities.filter(Boolean).map(e => getReferenceFrom(e)));
62
62
  }, [setValue]);
63
63
 
64
64
  const referenceDialogController = useReferenceDialog({
@@ -0,0 +1,139 @@
1
+ import React, { useEffect, useRef, useState } from "react";
2
+
3
+ import { CloseIcon, IconButton, TextField } from "@firecms/ui";
4
+ import { FieldProps, GeoPoint } from "../../types";
5
+ import { FieldHelperText, LabelWithIcon } from "../components";
6
+ import { PropertyIdCopyTooltip } from "../../components";
7
+ import { useClearRestoreValue } from "../useClearRestoreValue";
8
+ import { getIconForProperty } from "../../util";
9
+ import { formatGeoPoint, getGeoPointCoordinates, parseGeoPoint } from "../../util/geopoint";
10
+
11
+ type GeopointFieldBindingProps = FieldProps<GeoPoint>;
12
+
13
+ export function GeopointFieldBinding({
14
+ propertyKey,
15
+ value,
16
+ setValue,
17
+ error,
18
+ showError,
19
+ disabled,
20
+ autoFocus,
21
+ property,
22
+ includeDescription,
23
+ size = "large"
24
+ }: GeopointFieldBindingProps) {
25
+
26
+ const coordinates = getGeoPointCoordinates(value);
27
+ const canClear = Boolean((property as any).clearable);
28
+ const [latitude, setLatitude] = useState<string>(coordinates ? coordinates.latitude.toString() : "");
29
+ const [longitude, setLongitude] = useState<string>(coordinates ? coordinates.longitude.toString() : "");
30
+ const [localError, setLocalError] = useState<string | undefined>();
31
+ const skipSyncRef = useRef(false);
32
+
33
+ useClearRestoreValue({
34
+ property,
35
+ value,
36
+ setValue
37
+ });
38
+
39
+ useEffect(() => {
40
+ if (skipSyncRef.current) {
41
+ skipSyncRef.current = false;
42
+ return;
43
+ }
44
+ const nextCoordinates = getGeoPointCoordinates(value);
45
+ setLatitude(nextCoordinates ? nextCoordinates.latitude.toString() : "");
46
+ setLongitude(nextCoordinates ? nextCoordinates.longitude.toString() : "");
47
+ }, [value]);
48
+
49
+ const updateGeoPoint = (nextLatitude: string, nextLongitude: string) => {
50
+ skipSyncRef.current = true;
51
+ setLatitude(nextLatitude);
52
+ setLongitude(nextLongitude);
53
+
54
+ const trimmedLatitude = nextLatitude.trim();
55
+ const trimmedLongitude = nextLongitude.trim();
56
+
57
+ if (!trimmedLatitude && !trimmedLongitude) {
58
+ setLocalError(undefined);
59
+ setValue(null);
60
+ return;
61
+ }
62
+
63
+ const parsed = parseGeoPoint(`${trimmedLatitude}, ${trimmedLongitude}`);
64
+
65
+ if (parsed.error) {
66
+ setLocalError(parsed.error);
67
+ setValue(null);
68
+ return;
69
+ }
70
+
71
+ setLocalError(undefined);
72
+ setValue(parsed.point);
73
+ };
74
+
75
+ const handleClear = (event?: React.MouseEvent) => {
76
+ if (event) {
77
+ event.preventDefault();
78
+ event.stopPropagation();
79
+ }
80
+ updateGeoPoint("", "");
81
+ };
82
+
83
+ const resolvedError = localError ?? error;
84
+ const shouldShowError = Boolean(resolvedError) || Boolean(showError && error);
85
+
86
+ return (
87
+ <>
88
+ <PropertyIdCopyTooltip propertyKey={propertyKey}>
89
+ <div className="flex flex-col gap-2">
90
+ <div className="mt-1">
91
+ <LabelWithIcon
92
+ icon={getIconForProperty(property, "small")}
93
+ required={property.validation?.required}
94
+ title={property.name}
95
+ className={shouldShowError ? "text-red-500 dark:text-red-500" : "text-text-secondary dark:text-text-secondary-dark"}
96
+ />
97
+ </div>
98
+ <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
99
+ <TextField
100
+ size={size}
101
+ value={latitude}
102
+ onChange={(event) => updateGeoPoint(event.target.value, longitude)}
103
+ autoFocus={autoFocus}
104
+ label={"Latitude"}
105
+ type="number"
106
+ disabled={disabled}
107
+ endAdornment={canClear ? (
108
+ <IconButton onClick={handleClear}>
109
+ <CloseIcon />
110
+ </IconButton>
111
+ ) : undefined}
112
+ error={shouldShowError && Boolean(resolvedError)}
113
+ />
114
+ <TextField
115
+ size={size}
116
+ value={longitude}
117
+ onChange={(event) => updateGeoPoint(latitude, event.target.value)}
118
+ label={"Longitude"}
119
+ type="number"
120
+ disabled={disabled}
121
+ error={shouldShowError && Boolean(resolvedError)}
122
+ />
123
+ </div>
124
+ {value && !resolvedError && (
125
+ <div className="text-xs text-text-secondary dark:text-text-secondary-dark font-mono">
126
+ {formatGeoPoint(value)}
127
+ </div>
128
+ )}
129
+ </div>
130
+ </PropertyIdCopyTooltip>
131
+
132
+ <FieldHelperText includeDescription={includeDescription}
133
+ showError={shouldShowError}
134
+ error={resolvedError}
135
+ disabled={disabled}
136
+ property={property}/>
137
+ </>
138
+ );
139
+ }
@@ -11,6 +11,7 @@ export { StorageUploadFieldBinding } from "./field_bindings/StorageUploadFieldBi
11
11
  export { TextFieldBinding } from "./field_bindings/TextFieldBinding";
12
12
  export { SwitchFieldBinding } from "./field_bindings/SwitchFieldBinding";
13
13
  export { DateTimeFieldBinding } from "./field_bindings/DateTimeFieldBinding";
14
+ export { GeopointFieldBinding } from "./field_bindings/GeopointFieldBinding";
14
15
  export { ReferenceFieldBinding } from "./field_bindings/ReferenceFieldBinding";
15
16
  export { ReferenceAsStringFieldBinding } from "./field_bindings/ReferenceAsStringFieldBinding";
16
17
  export { MapFieldBinding } from "./field_bindings/MapFieldBinding";
@@ -81,6 +81,8 @@ export async function deleteEntityWithCallbacks<M extends Record<string, any>, U
81
81
  }
82
82
  }
83
83
  return dataSource.deleteEntity({
84
+ // Carried by the entity from the fetch that produced it; undefined if unknown.
85
+ pathSegments: entity.pathSegments,
84
86
  entity,
85
87
  collection
86
88
  }).then(() => {
@@ -43,6 +43,7 @@ export type SaveEntityWithCallbacksProps<M extends Record<string, any>> =
43
43
  export async function saveEntityWithCallbacks<M extends Record<string, any>, USER extends User>({
44
44
  collection,
45
45
  path,
46
+ pathSegments,
46
47
  entityId,
47
48
  values,
48
49
  previousValues,
@@ -103,6 +104,7 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>, USE
103
104
  return dataSource.saveEntity({
104
105
  collection,
105
106
  path: resolvedPath,
107
+ pathSegments,
106
108
  entityId,
107
109
  values: updatedValues,
108
110
  previousValues,
@@ -13,6 +13,12 @@ export interface CollectionFetchProps<M extends Record<string, any>> {
13
13
  * Absolute collection path
14
14
  */
15
15
  path: string;
16
+ /**
17
+ * `path` split at its real segment boundaries. Forwarded to the datasource so a parent
18
+ * entity id containing "/" stays unambiguous. Never derived from `path` — see
19
+ * `pathSegments` on the datasource props.
20
+ */
21
+ pathSegments?: string[];
16
22
 
17
23
  /**
18
24
  * collection of the entity displayed by this collection
@@ -63,6 +69,7 @@ export interface CollectionFetchResult<M extends Record<string, any>> {
63
69
  export function useCollectionFetch<M extends Record<string, any>, USER extends User>(
64
70
  {
65
71
  path: inputPath,
72
+ pathSegments,
66
73
  collection,
67
74
  filterValues,
68
75
  sortBy,
@@ -91,6 +98,10 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
91
98
  setDataLoading(true);
92
99
 
93
100
  const onEntitiesUpdate = async (entities: Entity<M>[]) => {
101
+ // See useEntityFetch: entities carry the segments they were loaded with.
102
+ if (pathSegments) {
103
+ entities = entities.map(e => e.pathSegments ? e : { ...e, pathSegments });
104
+ }
94
105
  if (collection.callbacks?.onFetch) {
95
106
  try {
96
107
  entities = await Promise.all(
@@ -124,6 +135,7 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
124
135
  if (dataSource.listenCollection) {
125
136
  return dataSource.listenCollection<M>({
126
137
  path,
138
+ pathSegments,
127
139
  collection,
128
140
  onUpdate: onEntitiesUpdate,
129
141
  onError,
@@ -137,6 +149,7 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
137
149
  } else {
138
150
  dataSource.fetchCollection<M>({
139
151
  path,
152
+ pathSegments,
140
153
  collection,
141
154
  searchString,
142
155
  filter: filterValues,
@@ -9,6 +9,11 @@ import { useFireCMSContext } from "../useFireCMSContext";
9
9
  */
10
10
  export interface EntityFetchProps<M extends Record<string, any>, USER extends User = User> {
11
11
  path: string;
12
+ /**
13
+ * `path` split at its real segment boundaries. Forwarded to the datasource so a parent
14
+ * entity id containing "/" stays unambiguous. Defaults to splitting the resolved path.
15
+ */
16
+ pathSegments?: string[];
12
17
  entityId?: string;
13
18
  databaseId?: string;
14
19
  collection: EntityCollection<M, USER>;
@@ -39,6 +44,7 @@ const CACHE: Record<string, Entity<any> | undefined> = {};
39
44
  export function useEntityFetch<M extends Record<string, any>, USER extends User>(
40
45
  {
41
46
  path: inputPath,
47
+ pathSegments: inputPathSegments,
42
48
  entityId,
43
49
  collection,
44
50
  databaseId,
@@ -49,6 +55,10 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User>
49
55
  const navigationController = useNavigationController();
50
56
 
51
57
  const path = navigationController.resolveIdsFrom(inputPath);
58
+ // Never fabricate segments. If the caller did not thread them, pass undefined so a
59
+ // delegate can tell "not provided" from a real answer — splitting `path` here would
60
+ // produce a confidently wrong array for any id containing "/".
61
+ const pathSegments = inputPathSegments;
52
62
 
53
63
  const context: FireCMSContext<USER> = useFireCMSContext();
54
64
 
@@ -61,6 +71,12 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User>
61
71
  setDataLoading(true);
62
72
 
63
73
  const onEntityUpdate = async (updatedEntity: Entity<M> | undefined) => {
74
+ // Attach the segments this entity was loaded with, so anything derived from it
75
+ // later — a reference, a delete — stays resolvable without re-deriving them
76
+ // from the flattened path. Delegates are not required to set this themselves.
77
+ if (updatedEntity && pathSegments && !updatedEntity.pathSegments) {
78
+ updatedEntity = { ...updatedEntity, pathSegments };
79
+ }
64
80
  if (collection.callbacks?.onFetch && updatedEntity) {
65
81
  try {
66
82
  updatedEntity = await collection.callbacks.onFetch({
@@ -97,6 +113,7 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User>
97
113
  if (dataSource.listenEntity) {
98
114
  return dataSource.listenEntity<M>({
99
115
  path,
116
+ pathSegments,
100
117
  entityId,
101
118
  databaseId,
102
119
  collection,
@@ -106,6 +123,7 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User>
106
123
  } else {
107
124
  dataSource.fetchEntity<M>({
108
125
  path,
126
+ pathSegments,
109
127
  entityId,
110
128
  databaseId,
111
129
  collection
@@ -86,6 +86,8 @@ export function resolveNavigationFrom<M extends Record<string, any>, USER extend
86
86
  }
87
87
  return dataSource.fetchEntity({
88
88
  path: entry.path,
89
+ // The navigation entry already knows the real segment boundaries.
90
+ pathSegments: entry.pathSegments,
89
91
  entityId: entry.entityId,
90
92
  collection
91
93
  })
@@ -74,6 +74,10 @@ export function useValidateAuthenticator<USER extends User = any>
74
74
 
75
75
  if (authenticator instanceof Function && delegateUser && !equal(checkedUserRef.current?.uid, delegateUser.uid)) {
76
76
  setAuthLoading(true);
77
+ // Reset the error of any previous authentication attempt. Otherwise, a
78
+ // rejected user would keep `canAccessMainView` to false for every user
79
+ // logging in afterwards, locking them out of the CMS until a page reload.
80
+ setNotAllowedError(false);
77
81
  try {
78
82
  const allowed = await authenticator({
79
83
  user: delegateUser,
@@ -8,6 +8,7 @@ import { fr } from "../locales/fr";
8
8
  import { it } from "../locales/it";
9
9
  import { hi } from "../locales/hi";
10
10
  import { pt } from "../locales/pt";
11
+ import { pl } from "../locales/pl";
11
12
  import { FireCMSTranslations } from "../types/translations";
12
13
 
13
14
  const FIRECMS_NS = "firecms_core";
@@ -139,6 +140,7 @@ function buildResources(
139
140
  it: { [FIRECMS_NS]: { ...it } },
140
141
  hi: { [FIRECMS_NS]: { ...hi } },
141
142
  pt: { [FIRECMS_NS]: { ...pt } },
143
+ pl: { [FIRECMS_NS]: { ...pl } },
142
144
  };
143
145
 
144
146
  if (!translations) return resources;
@@ -55,6 +55,7 @@ export function useBuildDataSource({
55
55
  */
56
56
  fetchCollection: useCallback(<M extends Record<string, any>>({
57
57
  path,
58
+ pathSegments,
58
59
  collection,
59
60
  filter,
60
61
  limit,
@@ -67,6 +68,7 @@ export function useBuildDataSource({
67
68
  const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
68
69
  return usedDelegate.fetchCollection<M>({
69
70
  path,
71
+ pathSegments,
70
72
  filter,
71
73
  limit,
72
74
  startAfter,
@@ -97,6 +99,7 @@ export function useBuildDataSource({
97
99
  ? useCallback(<M extends Record<string, any>>(
98
100
  {
99
101
  path,
102
+ pathSegments,
100
103
  collection: collectionProp,
101
104
  filter,
102
105
  limit,
@@ -117,6 +120,7 @@ export function useBuildDataSource({
117
120
 
118
121
  return usedDelegate.listenCollection<M>({
119
122
  path,
123
+ pathSegments,
120
124
  filter,
121
125
  limit,
122
126
  startAfter,
@@ -139,6 +143,7 @@ export function useBuildDataSource({
139
143
  */
140
144
  fetchEntity: useCallback(<M extends Record<string, any>>({
141
145
  path,
146
+ pathSegments,
142
147
  entityId,
143
148
  collection
144
149
  }: FetchEntityProps<M>
@@ -146,6 +151,7 @@ export function useBuildDataSource({
146
151
  const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
147
152
  return usedDelegate.fetchEntity({
148
153
  path,
154
+ pathSegments,
149
155
  entityId,
150
156
  collection
151
157
  });
@@ -165,6 +171,7 @@ export function useBuildDataSource({
165
171
  ? useCallback(<M extends Record<string, any>>(
166
172
  {
167
173
  path,
174
+ pathSegments,
168
175
  entityId,
169
176
  collection,
170
177
  onUpdate,
@@ -177,6 +184,7 @@ export function useBuildDataSource({
177
184
 
178
185
  return usedDelegate.listenEntity<M>({
179
186
  path,
187
+ pathSegments,
180
188
  entityId,
181
189
  onUpdate,
182
190
  onError,
@@ -198,6 +206,7 @@ export function useBuildDataSource({
198
206
  saveEntity: useCallback(async <M extends Record<string, any>>(
199
207
  {
200
208
  path,
209
+ pathSegments,
201
210
  entityId,
202
211
  values,
203
212
  collection: collectionProp,
@@ -244,6 +253,7 @@ export function useBuildDataSource({
244
253
  try {
245
254
  const entities = await usedDelegate.fetchCollection({
246
255
  path,
256
+ pathSegments,
247
257
  orderBy: orderProperty,
248
258
  order: "asc",
249
259
  limit: 1,
@@ -270,6 +280,7 @@ export function useBuildDataSource({
270
280
 
271
281
  return usedDelegate.saveEntity({
272
282
  path,
283
+ pathSegments,
273
284
  collection,
274
285
  entityId,
275
286
  values: finalValues,
@@ -292,12 +303,15 @@ export function useBuildDataSource({
292
303
  deleteEntity: useCallback(<M extends Record<string, any>>(
293
304
  {
294
305
  entity,
306
+ pathSegments,
295
307
  collection
296
308
  }: DeleteEntityProps<M>
297
309
  ): Promise<void> => {
298
310
  const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
299
311
  return usedDelegate.deleteEntity({
300
312
  entity,
313
+ // Falls back to what the entity carries from the fetch that produced it.
314
+ pathSegments: pathSegments ?? entity.pathSegments,
301
315
  collection
302
316
  });
303
317
  }, [delegate.deleteEntity]),
@@ -317,25 +331,28 @@ export function useBuildDataSource({
317
331
  name: string,
318
332
  value: any,
319
333
  entityId?: string,
320
- collection?: EntityCollection
334
+ collection?: EntityCollection,
335
+ pathSegments?: string[]
321
336
  ): Promise<boolean> => {
322
337
  const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
323
- return usedDelegate.checkUniqueField(path, name, value, entityId, collection);
338
+ return usedDelegate.checkUniqueField(path, name, value, entityId, collection, pathSegments);
324
339
  }, [delegate.checkUniqueField]),
325
340
 
326
- generateEntityId: useCallback((path: string, collection: EntityCollection): string => {
341
+ generateEntityId: useCallback((path: string, collection: EntityCollection, pathSegments?: string[]): string => {
327
342
  const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
328
- return usedDelegate.generateEntityId(path, collection);
343
+ return usedDelegate.generateEntityId(path, collection, pathSegments);
329
344
  }, [delegate.generateEntityId]),
330
345
 
331
346
  countEntities: delegate.countEntities ? async ({
332
347
  path,
348
+ pathSegments,
333
349
  collection,
334
350
  filter,
335
351
  order,
336
352
  orderBy
337
353
  }: {
338
354
  path: string,
355
+ pathSegments?: string[],
339
356
  collection: EntityCollection<any>,
340
357
  filter?: FilterValues<Extract<keyof any, string>>,
341
358
  orderBy?: string,
@@ -344,6 +361,7 @@ export function useBuildDataSource({
344
361
  const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
345
362
  return usedDelegate.countEntities!({
346
363
  path,
364
+ pathSegments,
347
365
  filter,
348
366
  orderBy,
349
367
  order,
@@ -353,11 +371,13 @@ export function useBuildDataSource({
353
371
 
354
372
  isFilterCombinationValid: useCallback(({
355
373
  path,
374
+ pathSegments,
356
375
  databaseId,
357
376
  filterValues,
358
377
  sortBy
359
378
  }: {
360
379
  path: string,
380
+ pathSegments?: string[],
361
381
  databaseId?: string,
362
382
  filterValues: FilterValues<any>,
363
383
  sortBy?: [string, "asc" | "desc"]
@@ -367,6 +387,7 @@ export function useBuildDataSource({
367
387
  return delegate.isFilterCombinationValid(
368
388
  {
369
389
  path,
390
+ pathSegments,
370
391
  databaseId,
371
392
  filterValues,
372
393
  sortBy
@@ -13,6 +13,7 @@ import {
13
13
  import { getNavigationEntriesFromPath, NavigationViewInternal } from "../util/navigation_from_path";
14
14
  import { useLocation } from "react-router-dom";
15
15
  import {
16
+ encodeEntityId,
16
17
  removeInitialAndTrailingSlashes,
17
18
  resolveCollection,
18
19
  resolveDefaultSelectedView,
@@ -223,12 +224,14 @@ export function buildSidePanelsFromUrl(path: string, collections: EntityCollecti
223
224
  let sidePanel: EntitySidePanelProps<any> | undefined = undefined;
224
225
  let lastCollectionPath = "";
225
226
  let lastCollectionId: string | undefined = undefined;
227
+ let lastCollectionSegments: string[] | undefined = undefined;
226
228
  for (let i = 0; i < navigationViewsForPath.length; i++) {
227
229
  const navigationEntry = navigationViewsForPath[i];
228
230
 
229
231
  if (navigationEntry.type === "collection") {
230
232
  lastCollectionPath = navigationEntry.path;
231
233
  lastCollectionId = navigationEntry.collection.id;
234
+ lastCollectionSegments = navigationEntry.pathSegments;
232
235
  }
233
236
 
234
237
  const previousEntry = navigationViewsForPath[i - 1];
@@ -236,6 +239,7 @@ export function buildSidePanelsFromUrl(path: string, collections: EntityCollecti
236
239
  sidePanel = {
237
240
  path: navigationEntry.path,
238
241
  fullIdPath: navigationEntry.fullIdPath,
242
+ pathSegments: navigationEntry.pathSegments,
239
243
  entityId: navigationEntry.entityId,
240
244
  copy: false,
241
245
  width: navigationEntry.parentCollection?.sideDialogWidth
@@ -258,6 +262,9 @@ export function buildSidePanelsFromUrl(path: string, collections: EntityCollecti
258
262
  sidePanel = {
259
263
  path: lastCollectionPath,
260
264
  fullIdPath: lastCollectionId,
265
+ // A new entity still needs unambiguous segments: it may be created inside a
266
+ // subcollection whose parent id contains "/".
267
+ pathSegments: lastCollectionSegments,
261
268
  copy: false
262
269
  }
263
270
  }
@@ -277,7 +284,7 @@ const propsToSidePanel = (props: EntitySidePanelProps,
277
284
  const collectionPath = removeInitialAndTrailingSlashes(props.path);
278
285
 
279
286
  const urlPath = props.entityId
280
- ? buildUrlCollectionPath(`${collectionPath}/${props.entityId}${props.selectedTab ? "/" + props.selectedTab : ""}${locationSearch}#${SIDE_URL_HASH}`)
287
+ ? buildUrlCollectionPath(`${collectionPath}/${encodeEntityId(props.entityId)}${props.selectedTab ? "/" + props.selectedTab : ""}${locationSearch}#${SIDE_URL_HASH}`)
281
288
  : buildUrlCollectionPath(`${collectionPath}${locationSearch}#${NEW_URL_HASH}`);
282
289
 
283
290
  const resolvedPanelProps: EntitySidePanelProps<any> = {
package/src/locales/de.ts CHANGED
@@ -146,8 +146,8 @@ export const de: FireCMSTranslations = {
146
146
  // ─── Error states ─────────────────────────────────────────────
147
147
  error: "Fehler",
148
148
  error_uploading_file: "Fehler beim Hochladen der Datei",
149
- error_deleting: "Fehler beim Löschen",
150
- error_before_delete: "Fehler vor dem Löschen",
149
+ error_deleting: "Fehler beim Löschen: {{message}}",
150
+ error_before_delete: "Fehler vor dem Löschen: {{message}}",
151
151
  error_updating_asset: "Fehler beim Aktualisieren des Assets",
152
152
  error_deleting_asset: "Fehler beim Löschen des Assets",
153
153
  error_firestore_index: "Für diese Abfrage ist ein Firestore-Index erforderlich.",
@@ -389,6 +389,7 @@ export const de: FireCMSTranslations = {
389
389
  download: "Herunterladen",
390
390
  large_number_of_documents: "Diese Sammlung hat eine große Anzahl von Dokumenten ({{count}}).",
391
391
  include_undefined_values: "Undefinierte Werte einschließen",
392
+ export_apply_filter_sort: "Nur Ergebnisse exportieren, die dem aktuellen Filter/der aktuellen Sortierung entsprechen",
392
393
  submit: "Einreichen",
393
394
 
394
395
  no_filterable_properties: "Keine filterbaren Eigenschaften verfügbar",
@@ -586,6 +587,10 @@ export const de: FireCMSTranslations = {
586
587
  order_property_not_found: "Eigenschaft \"{{property}}\" existiert nicht oder ist keine Zahlen-Eigenschaft. Bitte wählen Sie eine gültige Eigenschaft oder löschen Sie die Auswahl.",
587
588
  no_number_properties: "Keine Zahlen-Eigenschaften gefunden. Fügen Sie eine Zahlen-Eigenschaft hinzu, um die Sortierung zu aktivieren.",
588
589
  order_property_description: "Wählen Sie eine Zahlen-Eigenschaft, um die Reihenfolge der Elemente beizubehalten",
590
+ image_property: "Bild-Eigenschaft",
591
+ image_property_not_found: "Eigenschaft \"{{property}}\" existiert nicht oder ist keine Bild-Eigenschaft. Bitte wählen Sie eine gültige Eigenschaft oder löschen Sie die Auswahl.",
592
+ no_image_properties: "Keine Bild-Eigenschaften gefunden. Fügen Sie eine Speicher- oder Bild-URL-Eigenschaft hinzu, um sie als Vorschaubild zu verwenden.",
593
+ image_property_description: "Wählen Sie die Eigenschaft, die als Vorschaubild in den Ansichten Karten und Kanban verwendet wird. Wenn nicht festgelegt, wird automatisch die erste passende Bild-Eigenschaft verwendet.",
589
594
  display_settings: "Anzeigeeinstellungen",
590
595
  default_row_size: "Standard-Zeilengröße",
591
596
  side_dialog_width: "Seitendialog-Breite",
package/src/locales/en.ts CHANGED
@@ -154,8 +154,8 @@ export const en: FireCMSTranslations = {
154
154
  // ─── Error states ─────────────────────────────────────────────
155
155
  error: "Error",
156
156
  error_uploading_file: "Error uploading file",
157
- error_deleting: "Error deleting",
158
- error_before_delete: "Error before delete",
157
+ error_deleting: "Error deleting: {{message}}",
158
+ error_before_delete: "Error before delete: {{message}}",
159
159
  error_updating_asset: "Error updating asset",
160
160
  error_deleting_asset: "Error deleting asset",
161
161
  error_firestore_index: "A Firestore index is required for this query.",
@@ -397,6 +397,7 @@ export const en: FireCMSTranslations = {
397
397
  download: "Download",
398
398
  large_number_of_documents: "This collections has a large number of documents ({{count}}).",
399
399
  include_undefined_values: "Include undefined values",
400
+ export_apply_filter_sort: "Only export results matching the current filter/sort",
400
401
  submit: "Submit",
401
402
 
402
403
  no_filterable_properties: "No filterable properties available",
@@ -598,6 +599,10 @@ export const en: FireCMSTranslations = {
598
599
  order_property_not_found: "Property \"{{property}}\" does not exist or is not a number property. Please select a valid property or clear the selection.",
599
600
  no_number_properties: "No number properties found. Add a number property to enable ordering.",
600
601
  order_property_description: "Select a number property to persist the order of items",
602
+ image_property: "Image Property",
603
+ image_property_not_found: "Property \"{{property}}\" does not exist or is not an image property. Please select a valid property or clear the selection.",
604
+ no_image_properties: "No image properties found. Add a storage or URL image property to use as a thumbnail.",
605
+ image_property_description: "Select the property used as the thumbnail image in Cards and Kanban view modes. If not set, the first matching image property will be used automatically.",
601
606
  display_settings: "Display settings",
602
607
  default_row_size: "Default row size",
603
608
  side_dialog_width: "Side dialog width",