refactor: unify layout customization mode (record pages + navigation) (#18640)
### 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
This commit is contained in:
+35
@@ -0,0 +1,35 @@
|
||||
import { activeCustomizationPageLayoutIdsState } from '@/layout-customization/states/activeCustomizationPageLayoutIdsState';
|
||||
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
|
||||
import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/common/utils/filterWorkspaceNavigationMenuItems';
|
||||
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/common/states/navigationMenuItemsDraftState';
|
||||
import { navigationMenuItemsSelector } from '@/navigation-menu-item/common/states/navigationMenuItemsSelector';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useEnterLayoutCustomizationMode = () => {
|
||||
const store = useStore();
|
||||
|
||||
const enterLayoutCustomizationMode = useCallback(() => {
|
||||
const isLayoutCustomizationModeAlreadyEnabled = store.get(
|
||||
isLayoutCustomizationModeEnabledState.atom,
|
||||
);
|
||||
|
||||
if (isLayoutCustomizationModeAlreadyEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prefetchNavigationMenuItems = store.get(
|
||||
navigationMenuItemsSelector.atom,
|
||||
);
|
||||
const workspaceNavigationMenuItems = filterWorkspaceNavigationMenuItems(
|
||||
prefetchNavigationMenuItems,
|
||||
);
|
||||
store.set(navigationMenuItemsDraftState.atom, workspaceNavigationMenuItems);
|
||||
|
||||
store.set(activeCustomizationPageLayoutIdsState.atom, []);
|
||||
|
||||
store.set(isLayoutCustomizationModeEnabledState.atom, true);
|
||||
}, [store]);
|
||||
|
||||
return { enterLayoutCustomizationMode };
|
||||
};
|
||||
Reference in New Issue
Block a user