@firecms/core 3.0.0-canary.287 → 3.0.0-canary.288

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.
@@ -1,23 +1,23 @@
1
1
  import React, { useState } from "react";
2
2
  import {
3
- Button, CancelIcon, CheckIcon,
3
+ Button,
4
+ CancelIcon,
5
+ CheckIcon,
4
6
  defaultBorderMixin,
5
7
  Dialog,
6
8
  DialogActions,
7
9
  DialogContent,
8
10
  KeyboardArrowDownIcon,
9
11
  Menu,
10
- MenuItem,
11
- Typography, VisibilityIcon,
12
+ MenuItem, VisibilityIcon,
12
13
  WarningIcon
13
14
  } from "@firecms/ui";
14
- import { flattenKeys, FormexController, getIn } from "@firecms/formex";
15
+ import { FormexController } from "@firecms/formex";
15
16
  import { useSnackbarController } from "../../hooks";
16
17
  import { mergeDeep } from "../../util";
17
- import { removeEntityFromCache } from "../../util/entity_cache";
18
- import { getPropertyInPath } from "../../util";
19
- import { PropertyPreview } from "../../preview";
20
- import { ResolvedProperties, ResolvedProperty } from "../../types";
18
+ import { flattenKeys, removeEntityFromCache } from "../../util/entity_cache";
19
+ import { ResolvedProperties } from "../../types";
20
+ import { PropertyCollectionView } from "../../components/PropertyCollectionView";
21
21
 
22
22
  interface LocalChangesMenuProps<M extends object> {
23
23
  cacheKey: string;
@@ -38,13 +38,9 @@ export function LocalChangesMenu<M extends object>({
38
38
  const snackbarController = useSnackbarController();
39
39
  const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
40
40
  const [open, setOpen] = useState(false);
41
- const handleOpenMenu = () => {
42
- setOpen(true)
43
- };
44
41
 
45
- const handleCloseMenu = () => {
46
- setOpen(false)
47
- };
42
+ const handleOpenMenu = () => setOpen(true);
43
+ const handleCloseMenu = () => setOpen(false);
48
44
 
49
45
  const handlePreview = () => {
50
46
  setPreviewDialogOpen(true);
@@ -54,8 +50,8 @@ export function LocalChangesMenu<M extends object>({
54
50
  const handleApply = () => {
55
51
  const mergedValues = mergeDeep(formex.values, localChangesData);
56
52
  const touched = { ...formex.touched };
57
- const newTouched: string[] = flattenKeys(localChangesData);
58
- newTouched.forEach((key) => {
53
+ const previewKeys = flattenKeys(localChangesData);
54
+ previewKeys.forEach((key) => {
59
55
  touched[key] = true;
60
56
  });
61
57
 
@@ -81,23 +77,26 @@ export function LocalChangesMenu<M extends object>({
81
77
 
82
78
  return (
83
79
  <>
84
-
85
80
  <Menu
86
- trigger={<Button
87
- size={"small"}
88
- className={"font-semibold text-xs rounded-full px-4 py-1 bg-yellow-200 dark:bg-yellow-900 hover:bg-yellow-300 dark:hover:bg-yellow-800 text-yellow-800 dark:text-yellow-200"}
89
- onClick={handleOpenMenu}>
90
- <WarningIcon
91
- size={"smallest"}
92
- className={"mr-1 text-yellow-600 dark:text-yellow-400"}/>
93
- Unsaved Local changes
94
- <KeyboardArrowDownIcon size={"smallest"}/>
95
- </Button>}
81
+ trigger={
82
+ <Button
83
+ size={"small"}
84
+ className={
85
+ "font-semibold text-xs rounded-full px-4 py-1 bg-yellow-200 dark:bg-yellow-900 hover:bg-yellow-300 dark:hover:bg-yellow-800 text-yellow-800 dark:text-yellow-200"
86
+ }
87
+ onClick={handleOpenMenu}
88
+ >
89
+ <WarningIcon size={"smallest"} className={"mr-1 text-yellow-600 dark:text-yellow-400"}/>
90
+ Unsaved Local changes
91
+ <KeyboardArrowDownIcon size={"smallest"}/>
92
+ </Button>
93
+ }
96
94
  open={open}
97
95
  onOpenChange={setOpen}
98
96
  >
99
97
  <div className={"max-w-xs px-4 py-4 text-sm text-gray-700 dark:text-gray-300"}>
100
- This document was edited locally and has unsaved changes.
98
+ This document was edited locally and has unsaved changes. These local changes will be lost if you
99
+ don't apply them.
101
100
  </div>
102
101
  <MenuItem dense onClick={handlePreview}><VisibilityIcon size={"small"}/>Preview Changes</MenuItem>
103
102
  <MenuItem dense onClick={handleApply}><CheckIcon size={"small"}/>Apply Changes</MenuItem>
@@ -114,32 +113,13 @@ export function LocalChangesMenu<M extends object>({
114
113
  <p className={"mb-4"}>
115
114
  These are the local changes that will be applied to the form.
116
115
  </p>
117
- <div
118
- className={`border rounded-lg divide-y divide-surface-200 divide-surface-opacity-40 dark:divide-surface-700 dark:divide-opacity-40 ${defaultBorderMixin}`}>
119
- {flattenKeys(localChangesData).map((key) => {
120
- const value = getIn(localChangesData, key);
121
- const property = getPropertyInPath(properties, key) as ResolvedProperty;
122
- if (!property) {
123
- return null;
124
- }
125
- return (
126
- <div key={key}
127
- className="grid grid-cols-12 gap-x-4 px-4 py-3 items-center">
128
- <div
129
- className="col-span-3 text-right">
130
- <Typography variant="caption"
131
- className="text-gray-500 dark:text-gray-400 break-words">{property.name || key}</Typography>
132
- </div>
133
- <div className="col-span-9">
134
- <PropertyPreview
135
- propertyKey={key}
136
- value={value}
137
- property={property}
138
- size={"small"}/>
139
- </div>
140
- </div>
141
- );
142
- })}
116
+ <div className={`border rounded-lg ${defaultBorderMixin}`} style={{
117
+ maxHeight: 520,
118
+ overflow: "auto"
119
+ }}>
120
+ <div className="p-4">
121
+ <PropertyCollectionView data={localChangesData} properties={properties as ResolvedProperties}/>
122
+ </div>
143
123
  </div>
144
124
  </DialogContent>
145
125
  <DialogActions>
@@ -149,7 +129,10 @@ export function LocalChangesMenu<M extends object>({
149
129
  onClick={() => {
150
130
  handleApply();
151
131
  setPreviewDialogOpen(false);
152
- }}>Apply changes</Button>
132
+ }}
133
+ >
134
+ Apply changes
135
+ </Button>
153
136
  </DialogActions>
154
137
  </Dialog>
155
138
  </>
@@ -16,12 +16,12 @@ export function NumberPropertyPreview({
16
16
  const enumKey = value;
17
17
  const enumValues = enumToObjectEntries(property.enumValues);
18
18
  if (!enumValues)
19
- return <>{value}</>;
19
+ return <span className={size === "small" ? "text-sm" : ""}>{value}</span>;
20
20
  return <EnumValuesChip
21
21
  enumKey={enumKey}
22
22
  enumValues={enumValues}
23
23
  size={size !== "medium" ? "small" : "medium"}/>;
24
24
  } else {
25
- return <>{value}</>;
25
+ return <span className={size === "small" ? "text-sm" : ""}>{value}</span>;
26
26
  }
27
27
  }
@@ -1,4 +1,5 @@
1
1
  import { EntityReference, GeoPoint, Vector } from "../types";
2
+ import { isObject, isPlainObject } from "./objects";
2
3
 
3
4
  // Define a unique prefix for entity keys in localStorage to avoid key collisions
4
5
  const LOCAL_STORAGE_PREFIX = "entity_cache::";
@@ -87,6 +88,10 @@ export function saveEntityToCache(path: string, data: object): void {
87
88
  try {
88
89
  const key = LOCAL_STORAGE_PREFIX + path;
89
90
  const entityString = JSON.stringify(data, customReplacer);
91
+ console.log("Saving entity to localStorage:", {
92
+ key,
93
+ entityString
94
+ });
90
95
  localStorage.setItem(key, entityString);
91
96
  } catch (error) {
92
97
  console.error(
@@ -129,6 +134,10 @@ export function getEntityFromCache(path: string): object | undefined {
129
134
  const entityString = localStorage.getItem(key);
130
135
  if (entityString) {
131
136
  const entity: object = JSON.parse(entityString, customReviver);
137
+ console.log("Loaded entity from localStorage:", {
138
+ key,
139
+ entity
140
+ });
132
141
  return entity;
133
142
  }
134
143
  } catch (error) {
@@ -186,3 +195,29 @@ export function clearEntityCache(): void {
186
195
  }
187
196
  }
188
197
  }
198
+
199
+ export function flattenKeys(obj: any, prefix = "", result: string[] = []): string[] {
200
+
201
+ if (isObject(obj) || Array.isArray(obj)) {
202
+ const plainObject = isPlainObject(obj);
203
+ if (!plainObject && prefix) {
204
+ result.push(prefix);
205
+ } else {
206
+ for (const key in obj) {
207
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
208
+ const newKey = prefix
209
+ ? Array.isArray(obj)
210
+ ? `${prefix}[${key}]`
211
+ : `${prefix}.${key}`
212
+ : key;
213
+ if (isObject(obj[key]) || Array.isArray(obj[key])) {
214
+ flattenKeys(obj[key], newKey, result);
215
+ } else {
216
+ result.push(newKey);
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+ return result;
223
+ }
@@ -12,6 +12,21 @@ export function isObject(item: any) {
12
12
  return item && typeof item === "object" && !Array.isArray(item);
13
13
  }
14
14
 
15
+
16
+ export function isPlainObject(obj:any) {
17
+ // 1. Rule out non-objects, null, and arrays
18
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
19
+ return false;
20
+ }
21
+
22
+ // 2. Get the object's direct prototype
23
+ const proto = Object.getPrototypeOf(obj);
24
+
25
+ // 3. A plain object's direct prototype is Object.prototype
26
+ return proto === Object.prototype;
27
+ }
28
+
29
+
15
30
  export function mergeDeep<T extends Record<any, any>, U extends Record<any, any>>(
16
31
  target: T,
17
32
  source: U,
@@ -47,8 +62,31 @@ export function mergeDeep<T extends Record<any, any>, U extends Record<any, any>
47
62
  // If source value is a Date, create a new Date instance.
48
63
  (output as any)[key] = new Date(sourceValue.getTime());
49
64
  } else if (Array.isArray(sourceValue)) {
50
- // If source value is an array, create a shallow copy of the array.
51
- (output as any)[key] = [...sourceValue];
65
+ if (Array.isArray(outputValue)) {
66
+ const newArray = [];
67
+ const maxLength = Math.max(outputValue.length, sourceValue.length);
68
+ for (let i = 0; i < maxLength; i++) {
69
+ const sourceItem = sourceValue[i];
70
+ const targetItem = outputValue[i];
71
+
72
+ if (i >= sourceValue.length) { // source is shorter
73
+ newArray[i] = targetItem;
74
+ } else if (i >= outputValue.length) { // target is shorter
75
+ newArray[i] = sourceItem;
76
+ } else if (sourceItem === null) {
77
+ newArray[i] = targetItem;
78
+ } else if (isObject(sourceItem) && isObject(targetItem)) {
79
+ newArray[i] = mergeDeep(targetItem, sourceItem, ignoreUndefined);
80
+ } else {
81
+ newArray[i] = sourceItem;
82
+ }
83
+ }
84
+ (output as any)[key] = newArray;
85
+ } else {
86
+ // If output's value (from target) is not an array,
87
+ // overwrite with a shallow copy of the source array.
88
+ (output as any)[key] = [...sourceValue];
89
+ }
52
90
  } else if (isObject(sourceValue)) {
53
91
  // If source value is an object:
54
92
  if (isObject(outputValue)) {