@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.
- package/dist/app/useApp.d.ts +1 -0
- package/dist/components/EntityCollectionTable/column_utils.d.ts +4 -3
- package/dist/components/EntityCollectionView/FiltersDialog.d.ts +2 -1
- package/dist/components/EntityPreview.d.ts +3 -1
- package/dist/core/DefaultDrawer.d.ts +10 -3
- package/dist/core/field_configs.d.ts +1 -1
- package/dist/form/field_bindings/GeopointFieldBinding.d.ts +5 -0
- package/dist/form/index.d.ts +1 -0
- package/dist/index.es.js +1979 -721
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1978 -720
- package/dist/index.umd.js.map +1 -1
- package/dist/locales/pl.d.ts +2 -0
- package/dist/preview/index.d.ts +1 -0
- package/dist/preview/property_previews/GeopointPropertyPreview.d.ts +4 -0
- package/dist/types/collections.d.ts +17 -0
- package/dist/util/entities.d.ts +1 -0
- package/dist/util/geopoint.d.ts +22 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/navigation_blocking.d.ts +22 -0
- package/dist/util/navigation_from_path.d.ts +10 -0
- package/dist/util/navigation_utils.d.ts +20 -0
- package/package.json +16 -9
- package/src/app/Scaffold.tsx +34 -44
- package/src/app/useApp.tsx +1 -0
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +2 -1
- package/src/components/EntityCollectionTable/column_utils.tsx +11 -19
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -1
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +4 -1
- package/src/components/EntityCollectionView/FiltersDialog.tsx +39 -28
- package/src/components/EntityPreview.tsx +41 -40
- package/src/components/ReferenceWidget.tsx +1 -1
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +2 -2
- package/src/components/VirtualTable/VirtualTable.tsx +19 -8
- package/src/components/common/useDataSourceTableController.tsx +66 -10
- package/src/core/DefaultDrawer.tsx +150 -64
- package/src/core/DrawerNavigationGroup.tsx +27 -29
- package/src/core/DrawerNavigationItem.tsx +10 -12
- package/src/core/EntityEditView.tsx +57 -32
- package/src/core/field_configs.tsx +15 -0
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -1
- package/src/form/field_bindings/GeopointFieldBinding.tsx +139 -0
- package/src/form/index.tsx +1 -0
- package/src/i18n/FireCMSi18nProvider.tsx +2 -0
- package/src/internal/useBuildSideEntityController.tsx +2 -1
- package/src/locales/de.ts +2 -2
- package/src/locales/en.ts +2 -2
- package/src/locales/es.ts +2 -2
- package/src/locales/fr.ts +2 -2
- package/src/locales/hi.ts +2 -2
- package/src/locales/it.ts +2 -2
- package/src/locales/pl.ts +730 -0
- package/src/locales/pt.ts +2 -2
- package/src/preview/PropertyPreview.tsx +12 -0
- package/src/preview/index.ts +1 -0
- package/src/preview/property_previews/GeopointPropertyPreview.tsx +23 -0
- package/src/routes/FireCMSRoute.tsx +44 -25
- package/src/types/collections.ts +18 -0
- package/src/util/entities.ts +13 -0
- package/src/util/geopoint.ts +81 -0
- package/src/util/index.ts +1 -0
- package/src/util/navigation_blocking.ts +45 -0
- package/src/util/navigation_from_path.ts +23 -6
- package/src/util/navigation_utils.ts +36 -2
- 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={"
|
|
65
|
+
className={"my-2 mx-2 flex flex-col"}
|
|
66
66
|
key={`drawer_group_${group}`}
|
|
67
67
|
>
|
|
68
68
|
{/* Group Header */}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
"
|
|
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={"
|
|
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 }:
|
|
37
|
-
"hover:bg-
|
|
38
|
-
"flex flex-row items-center
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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-
|
|
50
|
+
"text-surface-700 dark:text-surface-300",
|
|
53
51
|
drawerOpen ? "opacity-100" : "opacity-0 hidden",
|
|
54
|
-
"
|
|
52
|
+
"font-inherit truncate space-x-2"
|
|
55
53
|
)}>
|
|
56
|
-
{name
|
|
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
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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":
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/form/index.tsx
CHANGED
|
@@ -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.",
|