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

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.
@@ -11,6 +11,12 @@ export type OnUpdateParams = {
11
11
  selectedTab?: string;
12
12
  collection: EntityCollection<any>;
13
13
  };
14
+ export type BarActionsParams = {
15
+ values: object;
16
+ status: EntityStatus;
17
+ path: string;
18
+ entityId?: string;
19
+ };
14
20
  export type OnTabChangeParams<M extends Record<string, any>> = {
15
21
  path: string;
16
22
  entityId?: string;
@@ -32,11 +38,11 @@ export interface EntityEditViewProps<M extends Record<string, any>> {
32
38
  copy?: boolean;
33
39
  selectedTab?: string;
34
40
  parentCollectionIds: string[];
35
- onValuesModified?: (modified: boolean) => void;
41
+ onValuesModified?: (modified: boolean, values: M) => void;
36
42
  onSaved?: (params: OnUpdateParams) => void;
37
43
  onTabChange?: (props: OnTabChangeParams<M>) => void;
38
44
  layout?: "side_panel" | "full_screen";
39
- barActions?: React.ReactNode;
45
+ barActions?: (params: BarActionsParams) => React.ReactNode;
40
46
  formProps?: Partial<EntityFormProps<M>>;
41
47
  }
42
48
  /**
@@ -19,7 +19,7 @@ export type EntityFormProps<M extends Record<string, any>> = {
19
19
  entity?: Entity<M>;
20
20
  databaseId?: string;
21
21
  onIdChange?: (id: string) => void;
22
- onValuesModified?: (modified: boolean) => void;
22
+ onValuesModified?: (modified: boolean, values: M) => void;
23
23
  onSaved?: (params: OnUpdateParams) => void;
24
24
  initialDirtyValues?: Partial<M>;
25
25
  onFormContextReady?: (formContext: FormContext) => void;
@@ -0,0 +1,11 @@
1
+ import { FormexController } from "@firecms/formex";
2
+ import { ResolvedProperties } from "../../types";
3
+ interface LocalChangesMenuProps<M extends object> {
4
+ cacheKey: string;
5
+ localChangesData: Partial<M>;
6
+ formex: FormexController<M>;
7
+ onClearLocalChanges?: () => void;
8
+ properties: ResolvedProperties<M>;
9
+ }
10
+ export declare function LocalChangesMenu<M extends object>({ localChangesData, formex, onClearLocalChanges, cacheKey, properties }: LocalChangesMenuProps<M>): import("react/jsx-runtime").JSX.Element;
11
+ export {};
package/dist/index.es.js CHANGED
@@ -2,10 +2,10 @@ import { jsx, Fragment, jsxs } from "react/jsx-runtime";
2
2
  import { c } from "react-compiler-runtime";
3
3
  import * as React from "react";
4
4
  import React__default, { useRef, useEffect, useContext, useCallback, useMemo, useState, createElement, createRef, createContext, forwardRef, useLayoutEffect } from "react";
5
- import { getColorSchemeForSeed, CHIP_COLORS, FunctionsIcon, CircleIcon, iconKeys, coolIconKeys, Icon, Tooltip, ErrorIcon, Typography, IconButton, ContentCopyIcon, OpenInNewIcon, DescriptionIcon, cls, Skeleton, Chip, defaultBorderMixin, KeyboardTabIcon, Checkbox, AccountCircleIcon, Markdown, TextareaAutosize, focusedDisabled, MultiSelect, MultiSelectItem, Select, SelectItem, BooleanSwitch, DateTimeField, paperMixin, EditIcon, DoNotDisturbOnIcon, Menu, MenuItem, MoreVertIcon, CircularProgress, SearchBar, Badge, ArrowUpwardIcon, Popover, FilterListIcon, Button, CenteredView, AssignmentIcon, Label, CloseIcon, TextField, BooleanSwitchWithLabel, useOutsideAlerter, Dialog, DialogTitle, DialogContent, DialogActions, FileCopyIcon, DeleteIcon, AddIcon, StarIcon, Collapse, ExpandablePanel, ArrowForwardIcon, Card, cardMixin, cardClickableMixin, Container, LoadingButton, Alert, CheckIcon, NotesIcon, InfoIcon, fieldBackgroundMixin, RemoveIcon, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, ArrowDropDownIcon, FilterListOffIcon, SearchIcon, Avatar, DarkModeIcon, LightModeIcon, BrightnessMediumIcon, LogoutIcon, HandleIcon, KeyboardArrowUpIcon, KeyboardArrowDownIcon, debounce, Sheet, Tab, Tabs, CodeIcon, OpenInFullIcon, ViewStreamIcon, RepeatIcon, BallotIcon, ScheduleIcon, AddLinkIcon, LinkIcon, DriveFolderUploadIcon, UploadFileIcon, FormatListNumberedIcon, NumbersIcon, PersonIcon, ListAltIcon, ListIcon, FlagIcon, MailIcon, HttpIcon, FormatQuoteIcon, SubjectIcon, ShortTextIcon, MenuIcon, ChevronLeftIcon } from "@firecms/ui";
5
+ import { getColorSchemeForSeed, CHIP_COLORS, FunctionsIcon, CircleIcon, iconKeys, coolIconKeys, Icon, Tooltip, ErrorIcon, Typography, IconButton, ContentCopyIcon, OpenInNewIcon, DescriptionIcon, cls, Skeleton, Chip, defaultBorderMixin, KeyboardTabIcon, Checkbox, AccountCircleIcon, Markdown, TextareaAutosize, focusedDisabled, MultiSelect, MultiSelectItem, Select, SelectItem, BooleanSwitch, DateTimeField, paperMixin, EditIcon, DoNotDisturbOnIcon, Menu, MenuItem, MoreVertIcon, CircularProgress, SearchBar, Badge, ArrowUpwardIcon, Popover, FilterListIcon, Button, CenteredView, AssignmentIcon, Label, CloseIcon, TextField, BooleanSwitchWithLabel, useOutsideAlerter, Dialog, DialogTitle, DialogContent, DialogActions, FileCopyIcon, DeleteIcon, AddIcon, StarIcon, Collapse, ExpandablePanel, ArrowForwardIcon, Card, cardMixin, cardClickableMixin, Container, LoadingButton, WarningIcon, KeyboardArrowDownIcon, VisibilityIcon, CheckIcon, CancelIcon, Alert, NotesIcon, InfoIcon, fieldBackgroundMixin, RemoveIcon, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, ArrowDropDownIcon, FilterListOffIcon, SearchIcon, Avatar, DarkModeIcon, LightModeIcon, BrightnessMediumIcon, LogoutIcon, HandleIcon, KeyboardArrowUpIcon, debounce, Sheet, Tab, Tabs, CodeIcon, OpenInFullIcon, ViewStreamIcon, RepeatIcon, BallotIcon, ScheduleIcon, AddLinkIcon, LinkIcon, DriveFolderUploadIcon, UploadFileIcon, FormatListNumberedIcon, NumbersIcon, PersonIcon, ListAltIcon, ListIcon, FlagIcon, MailIcon, HttpIcon, FormatQuoteIcon, SubjectIcon, ShortTextIcon, MenuIcon, ChevronLeftIcon } from "@firecms/ui";
6
6
  import { SnackbarProvider as SnackbarProvider$1, useSnackbar } from "notistack";
7
7
  import hash from "object-hash";
8
- import { getIn, useFormex, setIn, useCreateFormex, flattenKeys, Formex, Field } from "@firecms/formex";
8
+ import { getIn, useFormex, flattenKeys, setIn, useCreateFormex, Formex, Field } from "@firecms/formex";
9
9
  import { useNavigate, useLocation, Link, NavLink, Routes, Route, createBrowserRouter, RouterProvider } from "react-router-dom";
10
10
  import Fuse from "fuse.js";
11
11
  import equal from "react-fast-compare";
@@ -1054,6 +1054,12 @@ const applyPermissionsFunctionIfEmpty = (collections, permissionsBuilder) => {
1054
1054
  };
1055
1055
  });
1056
1056
  };
1057
+ function getLocalChangesBackup(collection) {
1058
+ if (!collection.localChangesBackup) {
1059
+ return "manual_apply";
1060
+ }
1061
+ return collection.localChangesBackup;
1062
+ }
1057
1063
  const kebabCaseRegex = /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g;
1058
1064
  const toKebabCase = (str) => {
1059
1065
  const regExpMatchArray = str.match(kebabCaseRegex);
@@ -9757,7 +9763,6 @@ function customReviver(key, value) {
9757
9763
  return value;
9758
9764
  }
9759
9765
  function saveEntityToCache(path, data) {
9760
- entityCache.set(path, data);
9761
9766
  if (isLocalStorageAvailable) {
9762
9767
  try {
9763
9768
  const key = LOCAL_STORAGE_PREFIX + path;
@@ -9768,17 +9773,22 @@ function saveEntityToCache(path, data) {
9768
9773
  }
9769
9774
  }
9770
9775
  }
9771
- function getEntityFromCache(path, useLocalStorage = true) {
9772
- if (entityCache.has(path)) {
9773
- return entityCache.get(path);
9774
- }
9775
- if (isLocalStorageAvailable && useLocalStorage) {
9776
+ function removeEntityFromMemoryCache(path) {
9777
+ entityCache.delete(path);
9778
+ }
9779
+ function saveEntityToMemoryCache(path, data) {
9780
+ entityCache.set(path, data);
9781
+ }
9782
+ function getEntityFromMemoryCache(path) {
9783
+ return entityCache.get(path);
9784
+ }
9785
+ function getEntityFromCache(path) {
9786
+ if (isLocalStorageAvailable) {
9776
9787
  try {
9777
9788
  const key = LOCAL_STORAGE_PREFIX + path;
9778
9789
  const entityString = localStorage.getItem(key);
9779
9790
  if (entityString) {
9780
9791
  const entity = JSON.parse(entityString, customReviver);
9781
- entityCache.set(path, entity);
9782
9792
  return entity;
9783
9793
  }
9784
9794
  } catch (error) {
@@ -9787,12 +9797,7 @@ function getEntityFromCache(path, useLocalStorage = true) {
9787
9797
  }
9788
9798
  return void 0;
9789
9799
  }
9790
- function hasEntityInCache(path) {
9791
- return entityCache.has(path);
9792
- }
9793
9800
  function removeEntityFromCache(path) {
9794
- console.debug("Removing entity from cache", path);
9795
- entityCache.delete(path);
9796
9801
  if (isLocalStorageAvailable) {
9797
9802
  try {
9798
9803
  const key = LOCAL_STORAGE_PREFIX + path;
@@ -9829,7 +9834,8 @@ const EntityCollectionRowActions = function EntityCollectionRowActions2({
9829
9834
  const hasCollapsedActions = actions.some((a) => a.collapsed || a.collapsed === void 0);
9830
9835
  const collapsedActions = actions.filter((a_0) => a_0.collapsed || a_0.collapsed === void 0);
9831
9836
  const uncollapsedActions = actions.filter((a_1) => a_1.collapsed === false);
9832
- const hasDraft = hasEntityInCache(fullPath + "/" + entity.id);
9837
+ const enableLocalChangesBackup = collection ? getLocalChangesBackup(collection) : false;
9838
+ const hasDraft = enableLocalChangesBackup ? getEntityFromCache(fullPath + "/" + entity.id) : false;
9833
9839
  return /* @__PURE__ */ jsxs("div", { className: cls("h-full flex items-center justify-center flex-col bg-surface-50 dark:bg-surface-900 bg-opacity-90 dark:bg-opacity-90 z-10", frozen ? "sticky left-0" : ""), onClick: useCallback((event) => {
9834
9840
  event.stopPropagation();
9835
9841
  }, []), style: {
@@ -15164,6 +15170,273 @@ function buildSideActions$1({
15164
15170
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) })
15165
15171
  ] });
15166
15172
  }
15173
+ function LocalChangesMenu(t0) {
15174
+ const $ = c(43);
15175
+ const {
15176
+ localChangesData,
15177
+ formex,
15178
+ onClearLocalChanges,
15179
+ cacheKey,
15180
+ properties
15181
+ } = t0;
15182
+ const snackbarController = useSnackbarController();
15183
+ const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
15184
+ const [open, setOpen] = useState(false);
15185
+ let t1;
15186
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
15187
+ t1 = () => {
15188
+ setOpen(true);
15189
+ };
15190
+ $[0] = t1;
15191
+ } else {
15192
+ t1 = $[0];
15193
+ }
15194
+ const handleOpenMenu = t1;
15195
+ let t2;
15196
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
15197
+ t2 = () => {
15198
+ setOpen(false);
15199
+ };
15200
+ $[1] = t2;
15201
+ } else {
15202
+ t2 = $[1];
15203
+ }
15204
+ const handleCloseMenu = t2;
15205
+ let t3;
15206
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
15207
+ t3 = () => {
15208
+ setPreviewDialogOpen(true);
15209
+ handleCloseMenu();
15210
+ };
15211
+ $[2] = t3;
15212
+ } else {
15213
+ t3 = $[2];
15214
+ }
15215
+ const handlePreview = t3;
15216
+ let t4;
15217
+ if ($[3] !== formex || $[4] !== localChangesData || $[5] !== onClearLocalChanges || $[6] !== snackbarController) {
15218
+ t4 = () => {
15219
+ const mergedValues = mergeDeep(formex.values, localChangesData);
15220
+ const touched = {
15221
+ ...formex.touched
15222
+ };
15223
+ const newTouched = flattenKeys(localChangesData);
15224
+ newTouched.forEach((key) => {
15225
+ touched[key] = true;
15226
+ });
15227
+ formex.setTouched(touched);
15228
+ formex.setValues(mergedValues);
15229
+ snackbarController.open({
15230
+ type: "info",
15231
+ message: "Local changes applied to the form"
15232
+ });
15233
+ handleCloseMenu();
15234
+ onClearLocalChanges?.();
15235
+ };
15236
+ $[3] = formex;
15237
+ $[4] = localChangesData;
15238
+ $[5] = onClearLocalChanges;
15239
+ $[6] = snackbarController;
15240
+ $[7] = t4;
15241
+ } else {
15242
+ t4 = $[7];
15243
+ }
15244
+ const handleApply = t4;
15245
+ let t5;
15246
+ if ($[8] !== cacheKey || $[9] !== onClearLocalChanges || $[10] !== snackbarController) {
15247
+ t5 = () => {
15248
+ removeEntityFromCache(cacheKey);
15249
+ snackbarController.open({
15250
+ type: "info",
15251
+ message: "Local changes discarded"
15252
+ });
15253
+ handleCloseMenu();
15254
+ onClearLocalChanges?.();
15255
+ };
15256
+ $[8] = cacheKey;
15257
+ $[9] = onClearLocalChanges;
15258
+ $[10] = snackbarController;
15259
+ $[11] = t5;
15260
+ } else {
15261
+ t5 = $[11];
15262
+ }
15263
+ const handleDiscard = t5;
15264
+ let t6;
15265
+ if ($[12] === Symbol.for("react.memo_cache_sentinel")) {
15266
+ t6 = /* @__PURE__ */ jsx(WarningIcon, { size: "smallest", className: "mr-1 text-yellow-600 dark:text-yellow-400" });
15267
+ $[12] = t6;
15268
+ } else {
15269
+ t6 = $[12];
15270
+ }
15271
+ let t7;
15272
+ if ($[13] === Symbol.for("react.memo_cache_sentinel")) {
15273
+ t7 = /* @__PURE__ */ jsxs(Button, { size: "small", 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", onClick: handleOpenMenu, children: [
15274
+ t6,
15275
+ "Unsaved Local changes",
15276
+ /* @__PURE__ */ jsx(KeyboardArrowDownIcon, { size: "smallest" })
15277
+ ] });
15278
+ $[13] = t7;
15279
+ } else {
15280
+ t7 = $[13];
15281
+ }
15282
+ let t8;
15283
+ if ($[14] === Symbol.for("react.memo_cache_sentinel")) {
15284
+ t8 = /* @__PURE__ */ jsx("div", { className: "max-w-xs px-4 py-4 text-sm text-gray-700 dark:text-gray-300", children: "This document was edited locally and has unsaved changes." });
15285
+ $[14] = t8;
15286
+ } else {
15287
+ t8 = $[14];
15288
+ }
15289
+ let t9;
15290
+ if ($[15] === Symbol.for("react.memo_cache_sentinel")) {
15291
+ t9 = /* @__PURE__ */ jsxs(MenuItem, { dense: true, onClick: handlePreview, children: [
15292
+ /* @__PURE__ */ jsx(VisibilityIcon, { size: "small" }),
15293
+ "Preview Changes"
15294
+ ] });
15295
+ $[15] = t9;
15296
+ } else {
15297
+ t9 = $[15];
15298
+ }
15299
+ let t10;
15300
+ if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
15301
+ t10 = /* @__PURE__ */ jsx(CheckIcon, { size: "small" });
15302
+ $[16] = t10;
15303
+ } else {
15304
+ t10 = $[16];
15305
+ }
15306
+ let t11;
15307
+ if ($[17] !== handleApply) {
15308
+ t11 = /* @__PURE__ */ jsxs(MenuItem, { dense: true, onClick: handleApply, children: [
15309
+ t10,
15310
+ "Apply Changes"
15311
+ ] });
15312
+ $[17] = handleApply;
15313
+ $[18] = t11;
15314
+ } else {
15315
+ t11 = $[18];
15316
+ }
15317
+ let t12;
15318
+ if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
15319
+ t12 = /* @__PURE__ */ jsx(CancelIcon, { size: "small" });
15320
+ $[19] = t12;
15321
+ } else {
15322
+ t12 = $[19];
15323
+ }
15324
+ let t13;
15325
+ if ($[20] !== handleDiscard) {
15326
+ t13 = /* @__PURE__ */ jsxs(MenuItem, { dense: true, onClick: handleDiscard, children: [
15327
+ t12,
15328
+ "Discard Local Changes"
15329
+ ] });
15330
+ $[20] = handleDiscard;
15331
+ $[21] = t13;
15332
+ } else {
15333
+ t13 = $[21];
15334
+ }
15335
+ let t14;
15336
+ if ($[22] !== open || $[23] !== t11 || $[24] !== t13) {
15337
+ t14 = /* @__PURE__ */ jsxs(Menu, { trigger: t7, open, onOpenChange: setOpen, children: [
15338
+ t8,
15339
+ t9,
15340
+ t11,
15341
+ t13
15342
+ ] });
15343
+ $[22] = open;
15344
+ $[23] = t11;
15345
+ $[24] = t13;
15346
+ $[25] = t14;
15347
+ } else {
15348
+ t14 = $[25];
15349
+ }
15350
+ let t15;
15351
+ let t16;
15352
+ if ($[26] === Symbol.for("react.memo_cache_sentinel")) {
15353
+ t15 = /* @__PURE__ */ jsx("h3", { className: "text-2xl mb-4", children: "Preview Local Changes" });
15354
+ t16 = /* @__PURE__ */ jsx("p", { className: "mb-4", children: "These are the local changes that will be applied to the form." });
15355
+ $[26] = t15;
15356
+ $[27] = t16;
15357
+ } else {
15358
+ t15 = $[26];
15359
+ t16 = $[27];
15360
+ }
15361
+ let t17;
15362
+ if ($[28] !== localChangesData || $[29] !== properties) {
15363
+ t17 = flattenKeys(localChangesData).map((key_0) => {
15364
+ const value = getIn(localChangesData, key_0);
15365
+ const property = getPropertyInPath(properties, key_0);
15366
+ if (!property) {
15367
+ return null;
15368
+ }
15369
+ return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-12 gap-x-4 px-4 py-3 items-center", children: [
15370
+ /* @__PURE__ */ jsx("div", { className: "col-span-3 text-right", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-gray-500 dark:text-gray-400 break-words", children: property.name || key_0 }) }),
15371
+ /* @__PURE__ */ jsx("div", { className: "col-span-9", children: /* @__PURE__ */ jsx(PropertyPreview, { propertyKey: key_0, value, property, size: "small" }) })
15372
+ ] }, key_0);
15373
+ });
15374
+ $[28] = localChangesData;
15375
+ $[29] = properties;
15376
+ $[30] = t17;
15377
+ } else {
15378
+ t17 = $[30];
15379
+ }
15380
+ let t18;
15381
+ if ($[31] !== t17) {
15382
+ t18 = /* @__PURE__ */ jsxs(DialogContent, { children: [
15383
+ t15,
15384
+ t16,
15385
+ /* @__PURE__ */ jsx("div", { className: `border rounded-lg divide-y divide-surface-200 divide-surface-opacity-40 dark:divide-surface-700 dark:divide-opacity-40 ${defaultBorderMixin}`, children: t17 })
15386
+ ] });
15387
+ $[31] = t17;
15388
+ $[32] = t18;
15389
+ } else {
15390
+ t18 = $[32];
15391
+ }
15392
+ let t19;
15393
+ if ($[33] === Symbol.for("react.memo_cache_sentinel")) {
15394
+ t19 = /* @__PURE__ */ jsx(Button, { onClick: () => setPreviewDialogOpen(false), children: "Close" });
15395
+ $[33] = t19;
15396
+ } else {
15397
+ t19 = $[33];
15398
+ }
15399
+ let t20;
15400
+ if ($[34] !== handleApply) {
15401
+ t20 = /* @__PURE__ */ jsxs(DialogActions, { children: [
15402
+ t19,
15403
+ /* @__PURE__ */ jsx(Button, { variant: "filled", onClick: () => {
15404
+ handleApply();
15405
+ setPreviewDialogOpen(false);
15406
+ }, children: "Apply changes" })
15407
+ ] });
15408
+ $[34] = handleApply;
15409
+ $[35] = t20;
15410
+ } else {
15411
+ t20 = $[35];
15412
+ }
15413
+ let t21;
15414
+ if ($[36] !== previewDialogOpen || $[37] !== t18 || $[38] !== t20) {
15415
+ t21 = /* @__PURE__ */ jsxs(Dialog, { open: previewDialogOpen, onOpenChange: setPreviewDialogOpen, maxWidth: "4xl", children: [
15416
+ t18,
15417
+ t20
15418
+ ] });
15419
+ $[36] = previewDialogOpen;
15420
+ $[37] = t18;
15421
+ $[38] = t20;
15422
+ $[39] = t21;
15423
+ } else {
15424
+ t21 = $[39];
15425
+ }
15426
+ let t22;
15427
+ if ($[40] !== t14 || $[41] !== t21) {
15428
+ t22 = /* @__PURE__ */ jsxs(Fragment, { children: [
15429
+ t14,
15430
+ t21
15431
+ ] });
15432
+ $[40] = t14;
15433
+ $[41] = t21;
15434
+ $[42] = t22;
15435
+ } else {
15436
+ t22 = $[42];
15437
+ }
15438
+ return t22;
15439
+ }
15167
15440
  function extractTouchedValues(values, touched) {
15168
15441
  let acc = {};
15169
15442
  if (!touched || typeof touched !== "object") {
@@ -15251,6 +15524,12 @@ function EntityForm({
15251
15524
  const [entityIdError, setEntityIdError] = useState(false);
15252
15525
  const [savingError, setSavingError] = useState();
15253
15526
  const autoSave = collection.formAutoSave && !collection.customId;
15527
+ const baseInitialValues = useMemo(() => getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs), [authController, collection, path, status, entity, customizationController.propertyConfigs]);
15528
+ const localChangesDataRaw = useMemo(() => entityId ? getEntityFromCache(path + "/" + entityId) : getEntityFromCache(path + "#new"), [entityId, path]);
15529
+ const [localChangesCleared, setLocalChangesCleared] = useState(false);
15530
+ const localChangesBackup = getLocalChangesBackup(collection);
15531
+ const autoApplyLocalChanges = localChangesBackup === "auto_apply";
15532
+ const manualApplyLocalChanges = localChangesBackup === "manual_apply";
15254
15533
  const onSubmit = (values, formexController) => {
15255
15534
  if (mustSetCustomId && !entityId) {
15256
15535
  console.error("Missing custom Id");
@@ -15281,12 +15560,31 @@ function EntityForm({
15281
15560
  formexController.setSubmitting(false);
15282
15561
  });
15283
15562
  };
15284
- const baseInitialValues = getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs);
15285
- const initialValues = initialDirtyValues ? mergeDeep(baseInitialValues, initialDirtyValues) : baseInitialValues;
15286
- const initialDirty = Boolean(initialDirtyValues) && initialDirtyValues && Object.keys(initialDirtyValues).length > 0;
15563
+ const [initialValues_0, initialDirty_0] = useMemo(() => {
15564
+ const initialValuesWithLocalChanges = autoApplyLocalChanges && localChangesDataRaw ? mergeDeep(baseInitialValues, localChangesDataRaw) : baseInitialValues;
15565
+ const initialValues = initialDirtyValues ? mergeDeep(initialValuesWithLocalChanges, initialDirtyValues) : initialValuesWithLocalChanges;
15566
+ const initialDirty = Boolean(initialDirtyValues) && initialDirtyValues && Object.keys(initialDirtyValues).length > 0;
15567
+ return [initialValues, initialDirty];
15568
+ }, [autoApplyLocalChanges, localChangesDataRaw, baseInitialValues, initialDirtyValues]);
15569
+ const localChangesData = useMemo(() => {
15570
+ if (!localChangesDataRaw) {
15571
+ return void 0;
15572
+ }
15573
+ let filteredChanges = {};
15574
+ const flattenedKeys = flattenKeys(localChangesDataRaw);
15575
+ flattenedKeys.forEach((key) => {
15576
+ const localValue = getIn(localChangesDataRaw, key);
15577
+ const initialValue = getIn(initialValues_0, key);
15578
+ if (!equal(localValue, initialValue)) {
15579
+ filteredChanges = setIn(filteredChanges, key, localValue);
15580
+ }
15581
+ });
15582
+ return filteredChanges;
15583
+ }, [localChangesDataRaw, initialValues_0]);
15584
+ const hasLocalChanges = !localChangesCleared && localChangesData && Object.keys(localChangesData).length > 0;
15287
15585
  const formex = formexProp ?? useCreateFormex({
15288
- initialValues,
15289
- initialDirty,
15586
+ initialValues: initialValues_0,
15587
+ initialDirty: initialDirty_0,
15290
15588
  initialTouched: initialDirtyValues ? flattenKeys(initialDirtyValues).reduce((previousValue, currentValue) => ({
15291
15589
  ...previousValue,
15292
15590
  [currentValue]: true
@@ -15294,13 +15592,13 @@ function EntityForm({
15294
15592
  onSubmit,
15295
15593
  onReset: () => {
15296
15594
  clearDirtyCache();
15297
- onValuesModified?.(false);
15595
+ onValuesModified?.(false, initialValues_0);
15298
15596
  },
15299
15597
  onValuesChangeDeferred: (values_0, controller) => {
15300
- const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
15598
+ const key_0 = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
15301
15599
  if (controller.dirty) {
15302
15600
  const touchedValues = extractTouchedValues(values_0, controller.touched);
15303
- saveEntityToCache(key, touchedValues);
15601
+ saveEntityToCache(key_0, touchedValues);
15304
15602
  }
15305
15603
  },
15306
15604
  validation: (values_1) => {
@@ -15353,14 +15651,16 @@ function EntityForm({
15353
15651
  }, [snackbarController]);
15354
15652
  function clearDirtyCache() {
15355
15653
  if (status === "new" || status === "copy") {
15654
+ removeEntityFromMemoryCache(path + "#new");
15356
15655
  removeEntityFromCache(path + "#new");
15357
15656
  } else {
15657
+ removeEntityFromMemoryCache(path + "/" + entityId);
15358
15658
  removeEntityFromCache(path + "/" + entityId);
15359
15659
  }
15360
15660
  }
15361
15661
  const onSaveSuccess = (updatedEntity) => {
15362
15662
  clearDirtyCache();
15363
- onValuesModified?.(false);
15663
+ onValuesModified?.(false, updatedEntity.values);
15364
15664
  if (!autoSave) snackbarController.open({
15365
15665
  type: "success",
15366
15666
  message: `${collection.singularName ?? collection.name}: Saved correctly`
@@ -15520,7 +15820,7 @@ function EntityForm({
15520
15820
  }, [doOnIdUpdate]);
15521
15821
  useEffect(() => {
15522
15822
  if (!autoSave) {
15523
- onValuesModified?.(modified);
15823
+ onValuesModified?.(modified, formex.values);
15524
15824
  }
15525
15825
  }, [formex.dirty]);
15526
15826
  const modified = formex.dirty;
@@ -15532,11 +15832,11 @@ function EntityForm({
15532
15832
  useOnAutoSave(autoSave, formex, lastSavedValues, save);
15533
15833
  useEffect(() => {
15534
15834
  if (!autoSave && !formex.isSubmitting && underlyingChanges && entity) {
15535
- Object.entries(underlyingChanges).forEach(([key_0, value_0]) => {
15536
- const formValue = formex.values[key_0];
15537
- if (!equal(value_0, formValue) && !formex.touched[key_0]) {
15538
- console.debug("Updated value from the datasource:", key_0, value_0);
15539
- formex.setFieldValue(key_0, value_0 !== void 0 ? value_0 : null);
15835
+ Object.entries(underlyingChanges).forEach(([key_1, value_0]) => {
15836
+ const formValue = formex.values[key_1];
15837
+ if (!equal(value_0, formValue) && !formex.touched[key_1]) {
15838
+ console.debug("Updated value from the datasource:", key_1, value_0);
15839
+ formex.setFieldValue(key_1, value_0 !== void 0 ? value_0 : null);
15540
15840
  }
15541
15841
  });
15542
15842
  }
@@ -15546,16 +15846,16 @@ function EntityForm({
15546
15846
  if (Builder) {
15547
15847
  return /* @__PURE__ */ jsx(Builder, { collection, entity, modifiedValues: formex.values, formContext });
15548
15848
  }
15549
- return /* @__PURE__ */ jsx(FormLayout, { children: formFieldKeys.map((key_1) => {
15550
- const property = resolvedCollection.properties[key_1];
15849
+ return /* @__PURE__ */ jsx(FormLayout, { children: formFieldKeys.map((key_2) => {
15850
+ const property = resolvedCollection.properties[key_2];
15551
15851
  if (property) {
15552
- const underlyingValueHasChanged = !!underlyingChanges && Object.keys(underlyingChanges).includes(key_1) && formex.touched[key_1];
15852
+ const underlyingValueHasChanged = !!underlyingChanges && Object.keys(underlyingChanges).includes(key_2) && formex.touched[key_2];
15553
15853
  const disabled_0 = disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property) || Boolean(property.disabled);
15554
15854
  const hidden = isHidden(property);
15555
15855
  if (hidden) return null;
15556
15856
  const widthPercentage = property.widthPercentage ?? 100;
15557
15857
  const cmsFormFieldProps = {
15558
- propertyKey: key_1,
15858
+ propertyKey: key_2,
15559
15859
  disabled: disabled_0,
15560
15860
  property,
15561
15861
  includeDescription: property.description || property.longDescription,
@@ -15565,9 +15865,9 @@ function EntityForm({
15565
15865
  minimalistView: false,
15566
15866
  autoFocus: false
15567
15867
  };
15568
- return /* @__PURE__ */ jsx(FormEntry, { propertyKey: key_1, widthPercentage, children: /* @__PURE__ */ jsx(PropertyFieldBinding, { ...cmsFormFieldProps }) }, `field_${key_1}`);
15868
+ return /* @__PURE__ */ jsx(FormEntry, { propertyKey: key_2, widthPercentage, children: /* @__PURE__ */ jsx(PropertyFieldBinding, { ...cmsFormFieldProps }) }, `field_${key_2}`);
15569
15869
  }
15570
- const additionalField = resolvedCollection.additionalFields?.find((f) => f.key === key_1);
15870
+ const additionalField = resolvedCollection.additionalFields?.find((f) => f.key === key_2);
15571
15871
  if (additionalField && entity) {
15572
15872
  const Builder_0 = additionalField.Builder;
15573
15873
  if (!Builder_0 && !additionalField.value) {
@@ -15578,11 +15878,11 @@ function EntityForm({
15578
15878
  context
15579
15879
  })?.toString() });
15580
15880
  return /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
15581
- /* @__PURE__ */ jsx(LabelWithIconAndTooltip, { propertyKey: key_1, icon: /* @__PURE__ */ jsx(NotesIcon, { size: "small" }), title: additionalField.name, className: "text-text-secondary dark:text-text-secondary-dark ml-3.5" }),
15881
+ /* @__PURE__ */ jsx(LabelWithIconAndTooltip, { propertyKey: key_2, icon: /* @__PURE__ */ jsx(NotesIcon, { size: "small" }), title: additionalField.name, className: "text-text-secondary dark:text-text-secondary-dark ml-3.5" }),
15582
15882
  /* @__PURE__ */ jsx("div", { className: cls(paperMixin, "w-full min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar"), children: /* @__PURE__ */ jsx(ErrorBoundary, { children: child }) })
15583
- ] }, `additional_${key_1}`);
15883
+ ] }, `additional_${key_2}`);
15584
15884
  }
15585
- console.warn(`Property ${key_1} not found in collection ${resolvedCollection.name} in properties or additional fields. Skipping.`);
15885
+ console.warn(`Property ${key_2} not found in collection ${resolvedCollection.name} in properties or additional fields. Skipping.`);
15586
15886
  return null;
15587
15887
  }).filter(Boolean) });
15588
15888
  };
@@ -15617,7 +15917,10 @@ function EntityForm({
15617
15917
  values: baseInitialValues
15618
15918
  }), noValidate: true, className: cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", className), children: [
15619
15919
  /* @__PURE__ */ jsx("div", { id: `form_${path}`, className: cls("relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit"), children: /* @__PURE__ */ jsxs("div", { className: cls("flex flex-col w-full pt-12 pb-16 px-4 sm:px-8 md:px-10"), children: [
15620
- formex.dirty ? /* @__PURE__ */ jsx(Tooltip, { title: "Local unsaved changes", className: "self-end sticky top-4 z-10", children: /* @__PURE__ */ jsx(Chip, { size: "small", colorScheme: "orangeDarker", children: /* @__PURE__ */ jsx(EditIcon, { size: "smallest" }) }) }) : /* @__PURE__ */ jsx(Tooltip, { title: "In sync with the database", className: "self-end sticky top-4 z-10", children: /* @__PURE__ */ jsx(Chip, { size: "small", children: /* @__PURE__ */ jsx(CheckIcon, { size: "smallest" }) }) }),
15920
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-4 self-end sticky top-4 z-10", children: [
15921
+ manualApplyLocalChanges && hasLocalChanges && /* @__PURE__ */ jsx(LocalChangesMenu, { cacheKey: status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId, properties: resolvedCollection.properties, localChangesData, formex, onClearLocalChanges: () => setLocalChangesCleared(true) }),
15922
+ formex.dirty ? /* @__PURE__ */ jsx(Tooltip, { title: "There are local unsaved changes", children: /* @__PURE__ */ jsx(Chip, { size: "small", colorScheme: "orangeDarker", children: /* @__PURE__ */ jsx(EditIcon, { size: "smallest" }) }) }) : /* @__PURE__ */ jsx(Tooltip, { title: "The current form is in sync with the database", children: /* @__PURE__ */ jsx(Chip, { size: "small", children: /* @__PURE__ */ jsx(CheckIcon, { size: "smallest" }) }) })
15923
+ ] }),
15621
15924
  formView
15622
15925
  ] }) }),
15623
15926
  dialogActions
@@ -20427,7 +20730,7 @@ const EntityCollectionView = React__default.memo(function EntityCollectionView2(
20427
20730
  /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", children: "So empty..." }),
20428
20731
  /* @__PURE__ */ jsxs(Button, { color: "primary", variant: "outlined", onClick: onNewClick, className: "mt-4", children: [
20429
20732
  /* @__PURE__ */ jsx(AddIcon, {}),
20430
- "Create your first entity"
20733
+ "Create your first entry"
20431
20734
  ] })
20432
20735
  ] }) : /* @__PURE__ */ jsx(Typography, { variant: "label", children: "No results with the applied filter/sort" }), hoverRow, inlineEditing: checkInlineEditing(), AdditionalHeaderWidget: buildAdditionalHeaderWidget, AddColumnComponent: addColumnComponentInternal, getIdColumnWidth, additionalIDHeaderWidget: /* @__PURE__ */ jsx(EntityIdHeaderWidget, { path: fullPath, fullIdPath: fullIdPath ?? fullPath, collection }), openEntityMode }, `collection_table_${fullPath}`),
20433
20736
  popupCell && /* @__PURE__ */ jsx(PopupFormField, { open: Boolean(popupCell), onClose: onPopupClose, cellRect: popupCell?.cellRect, propertyKey: popupCell?.propertyKey, collection, entityId: popupCell.entityId, tableKey: tableKey.current, customFieldValidator: uniqueFieldValidator, path: resolvedFullPath, onCellValueChange: onValueChange, container: containerRef.current }, `popup_form_${popupCell?.propertyKey}_${popupCell?.entityId}`),
@@ -23086,6 +23389,7 @@ function EntityJsonPreview(t0) {
23086
23389
  function createFormexStub(values) {
23087
23390
  const errorMessage = "You are in a read-only context. You cannot modify the formex controller.";
23088
23391
  return {
23392
+ debugId: "",
23089
23393
  values,
23090
23394
  initialValues: values,
23091
23395
  touched: {},
@@ -23100,6 +23404,9 @@ function createFormexStub(values) {
23100
23404
  setValues: () => {
23101
23405
  throw new Error(errorMessage);
23102
23406
  },
23407
+ setTouched(touched) {
23408
+ throw new Error(errorMessage);
23409
+ },
23103
23410
  setFieldValue: () => {
23104
23411
  throw new Error(errorMessage);
23105
23412
  },
@@ -23159,8 +23466,7 @@ function EntityEditView({
23159
23466
  databaseId: props.databaseId,
23160
23467
  useCache: false
23161
23468
  });
23162
- const enableLocalChangesBackup = props.collection.enableLocalChangesBackup !== void 0 ? props.collection.enableLocalChangesBackup : true;
23163
- const initialDirtyValues = entityId ? getEntityFromCache(props.path + "/" + entityId, enableLocalChangesBackup) : getEntityFromCache(props.path + "#new", enableLocalChangesBackup);
23469
+ const initialDirtyValues = entityId ? getEntityFromMemoryCache(props.path + "/" + entityId) : getEntityFromMemoryCache(props.path + "#new");
23164
23470
  const authController = useAuthController();
23165
23471
  const initialStatus = props.copy ? "copy" : entityId ? "existing" : "new";
23166
23472
  const [status, setStatus] = useState(initialStatus);
@@ -23319,6 +23625,7 @@ function EntityEditViewInner({
23319
23625
  /* @__PURE__ */ jsx("div", { className: "h-16" })
23320
23626
  ] }) }) : null;
23321
23627
  const entityView = /* @__PURE__ */ jsx(EntityForm, { fullIdPath, collection, path, entityId: entityId ?? usedEntity?.id, onValuesModified, entity, initialDirtyValues, openEntityMode: layout, forceActionsAtTheBottom: actionsAtTheBottom, initialStatus: status, className: cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className), EntityFormActionsComponent: EntityEditViewFormActions, disabled: !canEdit, ...formProps, onEntityChange: (entity_0) => {
23628
+ console.log("333 EntityEditView onEntityChange:", entity_0);
23322
23629
  setUsedEntity(entity_0);
23323
23630
  formProps?.onEntityChange?.(entity_0);
23324
23631
  }, onStatusChange: (status_0) => {
@@ -23341,7 +23648,12 @@ function EntityEditViewInner({
23341
23648
  const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
23342
23649
  let result = /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col h-full w-full bg-white dark:bg-surface-900", children: [
23343
23650
  shouldShowTopBar && /* @__PURE__ */ jsxs("div", { className: cls("h-14 items-center flex overflow-visible overflow-x-scroll w-full no-scrollbar h-14 border-b pl-2 pr-2 pt-1 flex bg-surface-50 dark:bg-surface-900", defaultBorderMixin), children: [
23344
- barActions,
23651
+ barActions?.({
23652
+ path: fullIdPath ?? path,
23653
+ entityId,
23654
+ values: formContext?.values ?? usedEntity?.values ?? {},
23655
+ status
23656
+ }),
23345
23657
  /* @__PURE__ */ jsx("div", { className: "flex-grow" }),
23346
23658
  pluginActionsTop,
23347
23659
  globalLoading && /* @__PURE__ */ jsx("div", { className: "self-center", children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" }) }),
@@ -23444,9 +23756,14 @@ function EntitySidePanel(props) {
23444
23756
  if (!props || !collection) {
23445
23757
  return /* @__PURE__ */ jsx("div", { className: "w-full" });
23446
23758
  }
23447
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(EntityEditView, { ...props, fullIdPath, layout: "side_panel", collection, parentCollectionIds, onValuesModified, onSaved: onUpdate, barActions: /* @__PURE__ */ jsxs(Fragment, { children: [
23759
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(EntityEditView, { ...props, fullIdPath, layout: "side_panel", collection, parentCollectionIds, onValuesModified, onSaved: onUpdate, barActions: ({
23760
+ status,
23761
+ values
23762
+ }) => /* @__PURE__ */ jsxs(Fragment, { children: [
23448
23763
  /* @__PURE__ */ jsx(IconButton, { className: "self-center", onClick: onClose, children: /* @__PURE__ */ jsx(CloseIcon, { size: "small" }) }),
23449
23764
  allowFullScreen && /* @__PURE__ */ jsx(IconButton, { className: "self-center", onClick: () => {
23765
+ const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
23766
+ saveEntityToMemoryCache(key, values);
23450
23767
  if (entityId) navigate(location.pathname);
23451
23768
  else navigate(location.pathname + "#new");
23452
23769
  }, children: /* @__PURE__ */ jsx(OpenInFullIcon, { size: "small" }) })
@@ -26227,6 +26544,7 @@ export {
26227
26544
  getIdIcon,
26228
26545
  getLabelOrConfigFrom,
26229
26546
  getLastSegment,
26547
+ getLocalChangesBackup,
26230
26548
  getPropertiesWithPropertiesOrder,
26231
26549
  getPropertyInPath,
26232
26550
  getRandomId,