@firecms/core 3.3.0 → 3.4.0-canary.f6a889a

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 (65) 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/EntityCollectionView/FiltersDialog.d.ts +2 -1
  4. package/dist/components/EntityPreview.d.ts +3 -1
  5. package/dist/core/DefaultDrawer.d.ts +10 -3
  6. package/dist/core/field_configs.d.ts +1 -1
  7. package/dist/form/field_bindings/GeopointFieldBinding.d.ts +5 -0
  8. package/dist/form/index.d.ts +1 -0
  9. package/dist/index.es.js +1979 -721
  10. package/dist/index.es.js.map +1 -1
  11. package/dist/index.umd.js +1978 -720
  12. package/dist/index.umd.js.map +1 -1
  13. package/dist/locales/pl.d.ts +2 -0
  14. package/dist/preview/index.d.ts +1 -0
  15. package/dist/preview/property_previews/GeopointPropertyPreview.d.ts +4 -0
  16. package/dist/types/collections.d.ts +17 -0
  17. package/dist/util/entities.d.ts +1 -0
  18. package/dist/util/geopoint.d.ts +22 -0
  19. package/dist/util/index.d.ts +1 -0
  20. package/dist/util/navigation_blocking.d.ts +22 -0
  21. package/dist/util/navigation_from_path.d.ts +10 -0
  22. package/dist/util/navigation_utils.d.ts +20 -0
  23. package/package.json +16 -9
  24. package/src/app/Scaffold.tsx +34 -44
  25. package/src/app/useApp.tsx +1 -0
  26. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +2 -1
  27. package/src/components/EntityCollectionTable/column_utils.tsx +11 -19
  28. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -1
  29. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +4 -1
  30. package/src/components/EntityCollectionView/FiltersDialog.tsx +39 -28
  31. package/src/components/EntityPreview.tsx +41 -40
  32. package/src/components/ReferenceWidget.tsx +1 -1
  33. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +2 -2
  34. package/src/components/VirtualTable/VirtualTable.tsx +19 -8
  35. package/src/components/common/useDataSourceTableController.tsx +66 -10
  36. package/src/core/DefaultDrawer.tsx +150 -64
  37. package/src/core/DrawerNavigationGroup.tsx +27 -29
  38. package/src/core/DrawerNavigationItem.tsx +10 -12
  39. package/src/core/EntityEditView.tsx +57 -32
  40. package/src/core/field_configs.tsx +15 -0
  41. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -1
  42. package/src/form/field_bindings/GeopointFieldBinding.tsx +139 -0
  43. package/src/form/index.tsx +1 -0
  44. package/src/i18n/FireCMSi18nProvider.tsx +2 -0
  45. package/src/internal/useBuildSideEntityController.tsx +2 -1
  46. package/src/locales/de.ts +2 -2
  47. package/src/locales/en.ts +2 -2
  48. package/src/locales/es.ts +2 -2
  49. package/src/locales/fr.ts +2 -2
  50. package/src/locales/hi.ts +2 -2
  51. package/src/locales/it.ts +2 -2
  52. package/src/locales/pl.ts +730 -0
  53. package/src/locales/pt.ts +2 -2
  54. package/src/preview/PropertyPreview.tsx +12 -0
  55. package/src/preview/index.ts +1 -0
  56. package/src/preview/property_previews/GeopointPropertyPreview.tsx +23 -0
  57. package/src/routes/FireCMSRoute.tsx +44 -25
  58. package/src/types/collections.ts +18 -0
  59. package/src/util/entities.ts +13 -0
  60. package/src/util/geopoint.ts +81 -0
  61. package/src/util/index.ts +1 -0
  62. package/src/util/navigation_blocking.ts +45 -0
  63. package/src/util/navigation_from_path.ts +23 -6
  64. package/src/util/navigation_utils.ts +36 -2
  65. package/src/util/parent_references_from_path.ts +4 -2
@@ -62,43 +62,41 @@ export function DrawerNavigationGroup({
62
62
  const { t } = useTranslation();
63
63
  return (
64
64
  <div
65
- className={"bg-surface-50 dark:bg-surface-800/30 my-4 rounded-lg ml-3 mr-1"}
65
+ className={"my-2 mx-2 flex flex-col"}
66
66
  key={`drawer_group_${group}`}
67
67
  >
68
68
  {/* Group Header */}
69
- {drawerOpen ? (
70
- <div
71
- className="pl-4 pr-2 py-2 flex flex-row items-center cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-700/50 rounded-t-lg transition-colors"
72
- onClick={onToggleCollapsed}
73
- >
74
- <ExpandMoreIcon
75
- size={"smallest"}
76
- className={cls(
77
- "text-surface-500 dark:text-surface-400 transition-transform duration-200 mr-1",
78
- collapsed ? "-rotate-90" : "rotate-0"
79
- )}
80
- />
81
- <Typography
82
- variant={"caption"}
83
- color={"secondary"}
84
- className="font-medium flex-grow line-clamp-1"
85
- >
86
- {(group && group !== "__default__" ? group : t("views_group")).toUpperCase()}
87
- </Typography>
88
- {headerActions && (
89
- <div onClick={(e) => e.stopPropagation()}>
90
- {headerActions}
91
- </div>
69
+ <div
70
+ className={cls("pl-3 pr-2 py-0.5 flex flex-row items-center transition-colors",
71
+ drawerOpen ? "cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-800/40 rounded-lg" : "opacity-0 invisible pointer-events-none"
72
+ )}
73
+ onClick={drawerOpen ? onToggleCollapsed : undefined}
74
+ >
75
+ <ExpandMoreIcon
76
+ size={"smallest"}
77
+ className={cls(
78
+ "text-surface-400 dark:text-surface-400 transition-transform duration-200 mr-1",
79
+ collapsed ? "-rotate-90" : "rotate-0"
92
80
  )}
93
- </div>
94
- ) : (
95
- <div className="w-full" />
96
- )}
81
+ />
82
+ <Typography
83
+ variant={"caption"}
84
+ color={"secondary"}
85
+ className="font-semibold text-[11px] uppercase tracking-wider flex-grow line-clamp-1 text-surface-400 dark:text-surface-400"
86
+ >
87
+ {(group && group !== "__default__" ? group : t("views_group"))}
88
+ </Typography>
89
+ {headerActions && (
90
+ <div onClick={(e) => e.stopPropagation()}>
91
+ {headerActions}
92
+ </div>
93
+ )}
94
+ </div>
97
95
 
98
96
  {/* Collapsible Content */}
99
97
  <div
100
98
  className={cls(
101
- "overflow-hidden transition-all duration-200 ease-in-out",
99
+ "transition-all duration-200 ease-in-out overflow-hidden rounded-lg",
102
100
  collapsed ? "max-h-0 opacity-0" : "max-h-[2000px] opacity-100"
103
101
  )}
104
102
  >
@@ -22,7 +22,7 @@ export function DrawerNavigationItem({
22
22
  }) {
23
23
 
24
24
  const iconWrap = <div
25
- className={"text-text-secondary dark:text-text-secondary-dark"}>
25
+ className={"shrink-0 flex items-center justify-center w-[44px] h-[30px] text-surface-500 dark:text-text-secondary-dark group-hover/nav:text-primary transition-colors duration-150"}>
26
26
  {icon}
27
27
  </div>;
28
28
 
@@ -33,14 +33,12 @@ export function DrawerNavigationItem({
33
33
  width: "100%",
34
34
  transition: drawerOpen ? "width 150ms ease-in" : undefined
35
35
  }}
36
- className={({ isActive }: any) => cls("rounded-lg truncate",
37
- "hover:bg-surface-accent-300 hover:bg-opacity-75 hover:bg-surface-accent-300/75 dark:hover:bg-surface-accent-800 dark:hover:bg-opacity-75 dark:hover:bg-surface-accent-800/75 text-text-primary dark:text-surface-200 hover:text-surface-900 hover:dark:text-white hover:bg-surface-accent-300/75 dark:hover:bg-surface-accent-800/75",
38
- "flex flex-row items-center mr-8",
39
- // "transition-all ease-in-out delay-100 duration-300",
40
- // drawerOpen ? "w-full" : "w-18",
41
- drawerOpen ? "pl-4 h-10" : "pl-4 h-9",
42
- "font-semibold text-xs",
43
- isActive ? "bg-surface-accent-200 bg-opacity-60 dark:bg-surface-800 dark:bg-opacity-50 bg-surface-accent-200/60 dark:bg-surface-800/50" : ""
36
+ className={({ isActive }: { isActive: boolean }) => cls("rounded-lg truncate group/nav",
37
+ "hover:bg-primary/5 dark:hover:bg-primary/5 text-surface-700 dark:text-surface-300 hover:text-surface-900 dark:hover:text-white",
38
+ "flex flex-row items-center",
39
+ drawerOpen ? "pr-4 h-[30px]" : "h-[30px]",
40
+ "font-medium text-[13px]",
41
+ isActive ? "bg-primary/8 dark:bg-primary/10 text-primary dark:text-primary [&_div]:text-primary" : ""
44
42
  )}
45
43
  to={url}
46
44
  >
@@ -49,11 +47,11 @@ export function DrawerNavigationItem({
49
47
 
50
48
  <div
51
49
  className={cls(
52
- "text-text-primary dark:text-surface-200",
50
+ "text-surface-700 dark:text-surface-300",
53
51
  drawerOpen ? "opacity-100" : "opacity-0 hidden",
54
- "ml-4 font-inherit"
52
+ "font-inherit truncate space-x-2"
55
53
  )}>
56
- {name.toUpperCase()}
54
+ {name}
57
55
  </div>
58
56
  </NavLink>
59
57
  </div>;
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useMemo, useState } from "react";
1
+ import React, { useEffect, useMemo, useRef, useState } from "react";
2
2
  import {
3
3
  Entity,
4
4
  EntityCollection,
@@ -13,6 +13,7 @@ import {
13
13
  import { CircularProgressCenter, EntityCollectionView, EntityView, ErrorBoundary } from "../components";
14
14
  import {
15
15
  canEditEntity,
16
+ encodeEntityId,
16
17
  removeInitialAndTrailingSlashes,
17
18
  resolveCollection,
18
19
  resolveDefaultSelectedView,
@@ -246,6 +247,47 @@ export function EntityEditViewInner<M extends Record<string, any>>({
246
247
 
247
248
  const authController = useAuthController();
248
249
 
250
+ // Track which tabs have been visited so we keep them mounted (preserving
251
+ // their state) without eagerly mounting tabs that were never opened.
252
+ const mountedTabsRef = useRef<Set<string>>(new Set());
253
+ if (selectedTab) {
254
+ mountedTabsRef.current.add(selectedTab);
255
+ }
256
+
257
+ // Memoize the read-only fallback form context so it is not recreated on
258
+ // every render when there is no real formContext (read-only entity views).
259
+ const readOnlyFormContext = useMemo<FormContext | undefined>(() => {
260
+ if (formContext) return undefined; // real formContext takes precedence
261
+ if (!entityId) return undefined;
262
+ const formexStub = createFormexStub<M>(usedEntity?.values ?? {} as M);
263
+ return {
264
+ entityId,
265
+ disabled: false,
266
+ openEntityMode: layout,
267
+ status: status,
268
+ values: usedEntity?.values ?? {},
269
+ setFieldValue: (key: string, value: any) => {
270
+ throw new Error("You can't update values in read only mode");
271
+ },
272
+ save: () => {
273
+ throw new Error("You can't save in read only mode");
274
+ },
275
+ collection: resolveCollection<M>({
276
+ collection,
277
+ path,
278
+ entityId,
279
+ values: usedEntity?.values ?? {},
280
+ previousValues: usedEntity?.values ?? {},
281
+ propertyConfigs: customizationController.propertyConfigs,
282
+ authController
283
+ }),
284
+ path,
285
+ entity: usedEntity,
286
+ savingError: undefined,
287
+ formex: formexStub
288
+ };
289
+ }, [formContext, entityId, layout, status, usedEntity, collection, path, customizationController.propertyConfigs, authController]);
290
+
249
291
  const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
250
292
  .filter(e => !e.includeActions)
251
293
  .map((customView) => {
@@ -262,38 +304,18 @@ export function EntityEditViewInner<M extends Record<string, any>>({
262
304
  return null;
263
305
  }
264
306
 
265
- const formexStub = createFormexStub<M>(usedEntity?.values ?? {} as M);
266
- const usedFormContext: FormContext = formContext ?? {
267
- entityId,
268
- disabled: false,
269
- openEntityMode: layout,
270
- status: status,
271
- values: usedEntity?.values ?? {},
272
- setFieldValue: (key: string, value: any) => {
273
- throw new Error("You can't update values in read only mode");
274
- },
275
- save: () => {
276
- throw new Error("You can't save in read only mode");
277
- },
278
- collection: resolveCollection<M>({
279
- collection,
280
- path,
281
- entityId,
282
- values: usedEntity?.values ?? {},
283
- previousValues: usedEntity?.values ?? {},
284
- propertyConfigs: customizationController.propertyConfigs,
285
- authController
286
- }),
287
- path,
288
- entity: usedEntity,
289
- savingError: undefined,
290
- formex: formexStub
291
- };
307
+ // Only mount tabs that are active or have been visited before.
308
+ const isActive = selectedTab === customView.key;
309
+ if (!isActive && !mountedTabsRef.current.has(customView.key)) {
310
+ return null;
311
+ }
312
+
313
+ const usedFormContext: FormContext = formContext ?? readOnlyFormContext!;
292
314
 
293
315
  return <div
294
316
  className={cls(defaultBorderMixin,
295
317
  "relative flex-1 w-full h-full overflow-auto",
296
- { "hidden": selectedTab !== customView.key }
318
+ { "hidden": !isActive }
297
319
  )}
298
320
  key={`custom_view_${customView.key}`}
299
321
  role="tabpanel">
@@ -311,7 +333,8 @@ export function EntityEditViewInner<M extends Record<string, any>>({
311
333
 
312
334
  const globalLoading = dataLoading && !usedEntity;
313
335
 
314
- const jsonView = <div
336
+ // Only mount the JSON view once its tab has been selected at least once.
337
+ const jsonView = (selectedTab === JSON_TAB_VALUE || mountedTabsRef.current.has(JSON_TAB_VALUE)) ? <div
315
338
  className={cls("relative flex-1 h-full overflow-auto w-full",
316
339
  { "hidden": selectedTab !== JSON_TAB_VALUE })}
317
340
  key={"json_view"}
@@ -320,12 +343,14 @@ export function EntityEditViewInner<M extends Record<string, any>>({
320
343
  <EntityJsonPreview
321
344
  values={formContext?.values ?? entity?.values ?? {}} />
322
345
  </ErrorBoundary>
323
- </div>;
346
+ </div> : null;
324
347
 
325
348
  const subCollectionsViews = subcollections && subcollections.map((subcollection) => {
326
349
  const subcollectionId = subcollection.id ?? subcollection.path;
350
+ // `newFullPath` addresses the datasource, so the parent id stays raw.
327
351
  const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollection.path)}` : undefined;
328
- const newFullIdPath = fullIdPath ? `${fullIdPath}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
352
+ // `newFullIdPath` is fed to buildUrlCollectionPath downstream, so it stays escaped.
353
+ const newFullIdPath = fullIdPath && usedEntity ? `${fullIdPath}/${encodeEntityId(usedEntity.id)}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
329
354
 
330
355
  if (selectedTab !== subcollectionId) return null;
331
356
  return (
@@ -6,6 +6,7 @@ import {
6
6
  ArrayOfReferencesFieldBinding,
7
7
  BlockFieldBinding,
8
8
  DateTimeFieldBinding,
9
+ GeopointFieldBinding,
9
10
  KeyValueFieldBinding,
10
11
  MapFieldBinding,
11
12
  MultiSelectFieldBinding,
@@ -34,6 +35,7 @@ import {
34
35
  ListAltIcon,
35
36
  ListIcon,
36
37
  MailIcon,
38
+ LocationOnIcon,
37
39
  NumbersIcon, PersonIcon,
38
40
  RepeatIcon,
39
41
  ScheduleIcon,
@@ -273,6 +275,17 @@ export const DEFAULT_FIELD_CONFIGS: Record<string, PropertyConfig<any>> = {
273
275
  Field: DateTimeFieldBinding
274
276
  }
275
277
  },
278
+ geopoint: {
279
+ key: "geopoint",
280
+ name: "Geopoint",
281
+ description: "Latitude and longitude pair",
282
+ Icon: LocationOnIcon,
283
+ color: "#0ea5e9",
284
+ property: {
285
+ dataType: "geopoint",
286
+ Field: GeopointFieldBinding
287
+ }
288
+ },
276
289
  group: {
277
290
  key: "group",
278
291
  name: "Group",
@@ -419,6 +432,8 @@ export function getDefaultFieldId(property: Property | ResolvedProperty) {
419
432
  return "switch";
420
433
  } else if (property.dataType === "date") {
421
434
  return "date_time";
435
+ } else if (property.dataType === "geopoint") {
436
+ return "geopoint";
422
437
  } else if (property.dataType === "reference") {
423
438
  return "reference";
424
439
  }
@@ -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";
@@ -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;
@@ -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,
@@ -277,7 +278,7 @@ const propsToSidePanel = (props: EntitySidePanelProps,
277
278
  const collectionPath = removeInitialAndTrailingSlashes(props.path);
278
279
 
279
280
  const urlPath = props.entityId
280
- ? buildUrlCollectionPath(`${collectionPath}/${props.entityId}${props.selectedTab ? "/" + props.selectedTab : ""}${locationSearch}#${SIDE_URL_HASH}`)
281
+ ? buildUrlCollectionPath(`${collectionPath}/${encodeEntityId(props.entityId)}${props.selectedTab ? "/" + props.selectedTab : ""}${locationSearch}#${SIDE_URL_HASH}`)
281
282
  : buildUrlCollectionPath(`${collectionPath}${locationSearch}#${NEW_URL_HASH}`);
282
283
 
283
284
  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.",
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.",
package/src/locales/es.ts CHANGED
@@ -152,8 +152,8 @@ export const es: FireCMSTranslations = {
152
152
  // ─── Error states ─────────────────────────────────────────────
153
153
  error: "Error",
154
154
  error_uploading_file: "Error al subir archivo",
155
- error_deleting: "Error al eliminar",
156
- error_before_delete: "Error antes de eliminar",
155
+ error_deleting: "Error al eliminar: {{message}}",
156
+ error_before_delete: "Error antes de eliminar: {{message}}",
157
157
  error_updating_asset: "Error al actualizar recurso",
158
158
  error_deleting_asset: "Error al eliminar recurso",
159
159
  error_firestore_index: "Se requiere un índice de Firestore para esta consulta.",
package/src/locales/fr.ts CHANGED
@@ -146,8 +146,8 @@ export const fr: FireCMSTranslations = {
146
146
  // ─── Error states ─────────────────────────────────────────────
147
147
  error: "Erreur",
148
148
  error_uploading_file: "Erreur lors du téléchargement du fichier",
149
- error_deleting: "Erreur lors de la suppression",
150
- error_before_delete: "Erreur avant la suppression",
149
+ error_deleting: "Erreur lors de la suppression: {{message}}",
150
+ error_before_delete: "Erreur avant la suppression: {{message}}",
151
151
  error_updating_asset: "Erreur lors de la mise à jour de l'actif",
152
152
  error_deleting_asset: "Erreur lors de la suppression de l'actif",
153
153
  error_firestore_index: "Un index Firestore est requis pour cette requête.",
package/src/locales/hi.ts CHANGED
@@ -146,8 +146,8 @@ export const hi: FireCMSTranslations = {
146
146
  // ─── Error states ─────────────────────────────────────────────
147
147
  error: "त्रुटि",
148
148
  error_uploading_file: "फ़ाइल अपलोड करने में त्रुटि",
149
- error_deleting: "हटाने में त्रुटि",
150
- error_before_delete: "हटाने से पहले त्रुटि",
149
+ error_deleting: "हटाने में त्रुटि: {{message}}",
150
+ error_before_delete: "हटाने से पहले त्रुटि: {{message}}",
151
151
  error_updating_asset: "एसेट अपडेट करने में त्रुटि",
152
152
  error_deleting_asset: "एसेट हटाने में त्रुटि",
153
153
  error_firestore_index: "इस क्वेरी के लिए Firestore इंडेक्स आवश्यक है।",
package/src/locales/it.ts CHANGED
@@ -146,8 +146,8 @@ export const it: FireCMSTranslations = {
146
146
  // ─── Error states ─────────────────────────────────────────────
147
147
  error: "Errore",
148
148
  error_uploading_file: "Errore durante il caricamento del file",
149
- error_deleting: "Errore durante l'eliminazione",
150
- error_before_delete: "Errore prima dell'eliminazione",
149
+ error_deleting: "Errore durante l'eliminazione: {{message}}",
150
+ error_before_delete: "Errore prima dell'eliminazione: {{message}}",
151
151
  error_updating_asset: "Errore durante l'aggiornamento dell'asset",
152
152
  error_deleting_asset: "Errore durante l'eliminazione dell'asset",
153
153
  error_firestore_index: "Per questa query è richiesto un indice Firestore.",