### What Unifies record page layout editing and navigation menu editing into a single global "layout customization" session. Dashboard editing stays separate. ### How it works **Two edit mode systems, one context-based read:** - `isLayoutCustomizationModeEnabledState` -- global atom for record pages + navigation - `isDashboardInEditModeComponentState` -- dashboard-only, independent per-component atom - `PageLayoutEditModeProvider` -- context that dispatches to `RecordPageLayoutEditModeProvider` (reads global atom) or `DashboardPageLayoutEditModeProvider` (reads component atom), one component per file **Session registry + independent atoms:** - `activeCustomizationPageLayoutIdsState` -- accumulates page layout IDs as user navigates during customization (`string[]`) - Save/cancel iterate the ID list and read each layout's draft/persisted atoms independently - Follows the same pattern as `settingsRoleIdsState` + `settingsDraftRoleFamilyState` **Unified UI:** - `LayoutCustomizationBar` replaces the old `NavigationMenuEditModeBar` - Enter once -- edit record layouts + navigation -- save/cancel everything together - `useSaveLayoutCustomization` orchestrates sequential save: navigation draft -- page layouts -- field widget groups - Error snackbar on partial save failure (with TODO for future atomic server mutation) **Draft protection during customization:** - `PageLayoutRelationWidgetsSyncEffect` guarded -- only updates persisted state from server, skips draft/currentLayouts while customization is active - `useExecuteTasksOnAnyLocationChange` skips draft reset when customization mode is enabled - Command execution blocked during layout customization ### Cleanup - Deleted `NavigationMenuEditModeBar`, `isNavigationMenuInEditModeState`, `isPageLayoutInEditModeComponentState`, `useIsGlobalLayoutCustomizationActive` - `DraftPageLayout` type changed from `Omit` to `Pick` (explicit fields) - Removed save/cancel from `DefaultRecordCommandMenuItemsConfig` (bar handles it now) - Extracted `useSaveFieldsWidgetGroups` from save orchestration - Split `PageLayoutEditModeProvider` into 3 separate files (one component per file, Twenty convention) ### Known issues - **Stale deleted widget after save (pre-existing on `main`)**: Delete widget -- save -- exit customization -- Apollo cache stale -- sync effect overwrites Jotai from stale data -- widget reappears until refresh. Separate PR needed, likely tied to the planned server-side `saveLayoutCustomization` atomic endpoint. ### Open questions - **Module location**: Layout customization hooks/states live in `/app` -- should they move to their own `modules/layout-customization/`? - **Atomic server mutation**: All save mutations are on metadata schema (`createNavigationMenuItem`, `deleteNavigationMenuItem`, `updateNavigationMenuItem`, `updatePageLayoutWithTabsAndWidgets`, `upsertFieldsWidget`). A single `saveLayoutCustomization` endpoint could make saves truly atomic. https://github.com/user-attachments/assets/036ef542-97f3-485b-a68f-3726002c81fb
43 lines
1.8 KiB
TypeScript
43 lines
1.8 KiB
TypeScript
import { activeCustomizationPageLayoutIdsState } from '@/layout-customization/states/activeCustomizationPageLayoutIdsState';
|
|
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
|
|
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/common/states/navigationMenuItemsDraftState';
|
|
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/common/states/selectedNavigationMenuItemInEditModeState';
|
|
import { currentPageLayoutIdState } from '@/page-layout/states/currentPageLayoutIdState';
|
|
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
|
|
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
|
import { useStore } from 'jotai';
|
|
import { useCallback } from 'react';
|
|
|
|
export const useExitLayoutCustomizationMode = () => {
|
|
const store = useStore();
|
|
const { closeSidePanelMenu } = useSidePanelMenu();
|
|
|
|
const setNavigationMenuItemsDraft = useSetAtomState(
|
|
navigationMenuItemsDraftState,
|
|
);
|
|
const setSelectedNavigationMenuItemInEditMode = useSetAtomState(
|
|
selectedNavigationMenuItemInEditModeState,
|
|
);
|
|
const setIsLayoutCustomizationModeEnabled = useSetAtomState(
|
|
isLayoutCustomizationModeEnabledState,
|
|
);
|
|
|
|
const exitLayoutCustomizationMode = useCallback(() => {
|
|
setNavigationMenuItemsDraft(null);
|
|
setSelectedNavigationMenuItemInEditMode(null);
|
|
|
|
store.set(currentPageLayoutIdState.atom, null);
|
|
store.set(activeCustomizationPageLayoutIdsState.atom, []);
|
|
setIsLayoutCustomizationModeEnabled(false);
|
|
closeSidePanelMenu();
|
|
}, [
|
|
setNavigationMenuItemsDraft,
|
|
setSelectedNavigationMenuItemInEditMode,
|
|
setIsLayoutCustomizationModeEnabled,
|
|
closeSidePanelMenu,
|
|
store,
|
|
]);
|
|
|
|
return { exitLayoutCustomizationMode };
|
|
};
|