feat: Remember panel state per workspace (Vibe Kanban) (#2064)
* I've successfully implemented workspace-specific panel state persistence. Here's a summary of the changes:
## Summary
### Changes Made
1. **`frontend/src/stores/useUiPreferencesStore.ts`**
- Added `WorkspacePanelState` type to track `rightMainPanelMode` and `isLeftMainPanelVisible` per workspace
- Added `workspacePanelStates: Record<string, WorkspacePanelState>` to store state keyed by workspace ID
- Updated `toggleLeftMainPanel`, `toggleRightMainPanelMode`, `setRightMainPanelMode`, and `setLeftMainPanelVisible` to accept an optional `workspaceId` parameter
- Added `getWorkspacePanelState` and `setWorkspacePanelState` actions
- Added `useWorkspacePanelState(workspaceId)` hook for easy consumption
- Updated `partialize` to persist `workspacePanelStates` to localStorage
2. **`frontend/src/components/ui-new/containers/WorkspacesLayout.tsx`**
- Changed from using `useUiPreferencesStore` directly to using `useWorkspacePanelState(workspaceId)` hook
- Panel state is now workspace-specific
3. **`frontend/src/components/ui-new/actions/useActionVisibility.ts`**
- Updated to use `useWorkspacePanelState` for visibility context
4. **`frontend/src/components/ui-new/actions/index.ts`**
- Updated `ToggleLeftMainPanel`, `ToggleChangesMode`, `ToggleLogsMode`, `TogglePreviewMode`, and `ToggleDevServer` actions to pass `currentWorkspaceId` when toggling panel modes
### Scope
**Workspace-specific:**
- `rightMainPanelMode` - Which panel (Preview/Changes/Logs) is open
- `isLeftMainPanelVisible` - Whether the chat panel is visible
**Stays global:**
- `isLeftSidebarVisible` - Workspace list sidebar (navigation element)
- `isRightSidebarVisible` - Git/Settings sidebar (global actions)
- Panel sizes (already work correctly)
### Testing
Verified the feature works:
1. Opened Preview panel in Workspace A
2. Navigated to Workspace B → Preview panel is closed (default state)
3. Navigated back to Workspace A → Preview panel is still open ✓
* Cleanup script changes for workspace c8b73e59-ac41-4c0f-be78-7b6499d514f9
* The refactoring is complete. Let me provide a summary of the changes:
## Summary
Refactored `useUiPreferencesStore.ts` to remove duplicate code paths for workspace-specific panel state:
### Changes Made
1. **`frontend/src/stores/useUiPreferencesStore.ts`**
- Removed legacy global state (`isLeftMainPanelVisible`, `rightMainPanelMode`) from State type and initialization
- Simplified 4 panel state actions by removing `else` branches for legacy behavior:
- `toggleLeftMainPanel` - now early-returns if no workspaceId
- `toggleRightMainPanelMode` - simplified to single set() call
- `setRightMainPanelMode` - simplified to single set() call
- `setLeftMainPanelVisible` - simplified
- Removed `resetForCreateMode` action (was only setting legacy state)
- Removed unused `useIsRightMainPanelVisible` hook
2. **`frontend/src/contexts/WorkspaceContext.tsx`**
- Removed the `useEffect` that called `resetForCreateMode()` on create mode
- Removed unused `useUiPreferencesStore` import
3. **`frontend/src/contexts/LogsPanelContext.tsx`**
- Updated to use `useWorkspacePanelState` with workspace ID from context
4. **`frontend/src/components/ui-new/containers/SessionChatBoxContainer.tsx`**
- Updated to use `useWorkspacePanelState` with workspace ID
### Impact
- **~50 lines removed** from the codebase
- **Cleaner code** - no more `if (workspaceId) ... else ...` branching in every action
- **Same behavior** - all callers were already passing workspaceId
* Cleanup script changes for workspace c8b73e59-ac41-4c0f-be78-7b6499d514f9
This commit is contained in:
committed by
GitHub
parent
1e8783331c
commit
e1b0ef70a4
@@ -478,17 +478,18 @@ export const Actions = {
|
|||||||
|
|
||||||
ToggleLeftMainPanel: {
|
ToggleLeftMainPanel: {
|
||||||
id: 'toggle-left-main-panel',
|
id: 'toggle-left-main-panel',
|
||||||
label: () =>
|
label: 'Toggle Chat Panel',
|
||||||
useUiPreferencesStore.getState().isLeftMainPanelVisible
|
|
||||||
? 'Hide Chat Panel'
|
|
||||||
: 'Show Chat Panel',
|
|
||||||
icon: ChatsTeardropIcon,
|
icon: ChatsTeardropIcon,
|
||||||
requiresTarget: false,
|
requiresTarget: false,
|
||||||
isActive: (ctx) => ctx.isLeftMainPanelVisible,
|
isActive: (ctx) => ctx.isLeftMainPanelVisible,
|
||||||
isEnabled: (ctx) =>
|
isEnabled: (ctx) =>
|
||||||
!(ctx.isLeftMainPanelVisible && ctx.rightMainPanelMode === null),
|
!(ctx.isLeftMainPanelVisible && ctx.rightMainPanelMode === null),
|
||||||
execute: () => {
|
getLabel: (ctx) =>
|
||||||
useUiPreferencesStore.getState().toggleLeftMainPanel();
|
ctx.isLeftMainPanelVisible ? 'Hide Chat Panel' : 'Show Chat Panel',
|
||||||
|
execute: (ctx) => {
|
||||||
|
useUiPreferencesStore
|
||||||
|
.getState()
|
||||||
|
.toggleLeftMainPanel(ctx.currentWorkspaceId ?? undefined);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -508,60 +509,69 @@ export const Actions = {
|
|||||||
|
|
||||||
ToggleChangesMode: {
|
ToggleChangesMode: {
|
||||||
id: 'toggle-changes-mode',
|
id: 'toggle-changes-mode',
|
||||||
label: () =>
|
label: 'Toggle Changes Panel',
|
||||||
useUiPreferencesStore.getState().rightMainPanelMode ===
|
|
||||||
RIGHT_MAIN_PANEL_MODES.CHANGES
|
|
||||||
? 'Hide Changes Panel'
|
|
||||||
: 'Show Changes Panel',
|
|
||||||
icon: GitDiffIcon,
|
icon: GitDiffIcon,
|
||||||
requiresTarget: false,
|
requiresTarget: false,
|
||||||
isVisible: (ctx) => !ctx.isCreateMode,
|
isVisible: (ctx) => !ctx.isCreateMode,
|
||||||
isActive: (ctx) =>
|
isActive: (ctx) =>
|
||||||
ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.CHANGES,
|
ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.CHANGES,
|
||||||
isEnabled: (ctx) => !ctx.isCreateMode,
|
isEnabled: (ctx) => !ctx.isCreateMode,
|
||||||
execute: () => {
|
getLabel: (ctx) =>
|
||||||
|
ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.CHANGES
|
||||||
|
? 'Hide Changes Panel'
|
||||||
|
: 'Show Changes Panel',
|
||||||
|
execute: (ctx) => {
|
||||||
useUiPreferencesStore
|
useUiPreferencesStore
|
||||||
.getState()
|
.getState()
|
||||||
.toggleRightMainPanelMode(RIGHT_MAIN_PANEL_MODES.CHANGES);
|
.toggleRightMainPanelMode(
|
||||||
|
RIGHT_MAIN_PANEL_MODES.CHANGES,
|
||||||
|
ctx.currentWorkspaceId ?? undefined
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
ToggleLogsMode: {
|
ToggleLogsMode: {
|
||||||
id: 'toggle-logs-mode',
|
id: 'toggle-logs-mode',
|
||||||
label: () =>
|
label: 'Toggle Logs Panel',
|
||||||
useUiPreferencesStore.getState().rightMainPanelMode ===
|
|
||||||
RIGHT_MAIN_PANEL_MODES.LOGS
|
|
||||||
? 'Hide Logs Panel'
|
|
||||||
: 'Show Logs Panel',
|
|
||||||
icon: TerminalIcon,
|
icon: TerminalIcon,
|
||||||
requiresTarget: false,
|
requiresTarget: false,
|
||||||
isVisible: (ctx) => !ctx.isCreateMode,
|
isVisible: (ctx) => !ctx.isCreateMode,
|
||||||
isActive: (ctx) => ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.LOGS,
|
isActive: (ctx) => ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.LOGS,
|
||||||
isEnabled: (ctx) => !ctx.isCreateMode,
|
isEnabled: (ctx) => !ctx.isCreateMode,
|
||||||
execute: () => {
|
getLabel: (ctx) =>
|
||||||
|
ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.LOGS
|
||||||
|
? 'Hide Logs Panel'
|
||||||
|
: 'Show Logs Panel',
|
||||||
|
execute: (ctx) => {
|
||||||
useUiPreferencesStore
|
useUiPreferencesStore
|
||||||
.getState()
|
.getState()
|
||||||
.toggleRightMainPanelMode(RIGHT_MAIN_PANEL_MODES.LOGS);
|
.toggleRightMainPanelMode(
|
||||||
|
RIGHT_MAIN_PANEL_MODES.LOGS,
|
||||||
|
ctx.currentWorkspaceId ?? undefined
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
TogglePreviewMode: {
|
TogglePreviewMode: {
|
||||||
id: 'toggle-preview-mode',
|
id: 'toggle-preview-mode',
|
||||||
label: () =>
|
label: 'Toggle Preview Panel',
|
||||||
useUiPreferencesStore.getState().rightMainPanelMode ===
|
|
||||||
RIGHT_MAIN_PANEL_MODES.PREVIEW
|
|
||||||
? 'Hide Preview Panel'
|
|
||||||
: 'Show Preview Panel',
|
|
||||||
icon: DesktopIcon,
|
icon: DesktopIcon,
|
||||||
requiresTarget: false,
|
requiresTarget: false,
|
||||||
isVisible: (ctx) => !ctx.isCreateMode,
|
isVisible: (ctx) => !ctx.isCreateMode,
|
||||||
isActive: (ctx) =>
|
isActive: (ctx) =>
|
||||||
ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.PREVIEW,
|
ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.PREVIEW,
|
||||||
isEnabled: (ctx) => !ctx.isCreateMode,
|
isEnabled: (ctx) => !ctx.isCreateMode,
|
||||||
execute: () => {
|
getLabel: (ctx) =>
|
||||||
|
ctx.rightMainPanelMode === RIGHT_MAIN_PANEL_MODES.PREVIEW
|
||||||
|
? 'Hide Preview Panel'
|
||||||
|
: 'Show Preview Panel',
|
||||||
|
execute: (ctx) => {
|
||||||
useUiPreferencesStore
|
useUiPreferencesStore
|
||||||
.getState()
|
.getState()
|
||||||
.toggleRightMainPanelMode(RIGHT_MAIN_PANEL_MODES.PREVIEW);
|
.toggleRightMainPanelMode(
|
||||||
|
RIGHT_MAIN_PANEL_MODES.PREVIEW,
|
||||||
|
ctx.currentWorkspaceId ?? undefined
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -707,7 +717,10 @@ export const Actions = {
|
|||||||
// Auto-open preview mode when starting dev server
|
// Auto-open preview mode when starting dev server
|
||||||
useUiPreferencesStore
|
useUiPreferencesStore
|
||||||
.getState()
|
.getState()
|
||||||
.setRightMainPanelMode(RIGHT_MAIN_PANEL_MODES.PREVIEW);
|
.setRightMainPanelMode(
|
||||||
|
RIGHT_MAIN_PANEL_MODES.PREVIEW,
|
||||||
|
ctx.currentWorkspaceId ?? undefined
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useUiPreferencesStore } from '@/stores/useUiPreferencesStore';
|
import {
|
||||||
|
useUiPreferencesStore,
|
||||||
|
useWorkspacePanelState,
|
||||||
|
} from '@/stores/useUiPreferencesStore';
|
||||||
import { useDiffViewStore, useDiffViewMode } from '@/stores/useDiffViewStore';
|
import { useDiffViewStore, useDiffViewMode } from '@/stores/useDiffViewStore';
|
||||||
import { useWorkspaceContext } from '@/contexts/WorkspaceContext';
|
import { useWorkspaceContext } from '@/contexts/WorkspaceContext';
|
||||||
import { useUserSystem } from '@/components/ConfigProvider';
|
import { useUserSystem } from '@/components/ConfigProvider';
|
||||||
@@ -22,8 +25,11 @@ import type { CommandBarPage } from './pages';
|
|||||||
* action visibility and state conditions.
|
* action visibility and state conditions.
|
||||||
*/
|
*/
|
||||||
export function useActionVisibilityContext(): ActionVisibilityContext {
|
export function useActionVisibilityContext(): ActionVisibilityContext {
|
||||||
const layout = useUiPreferencesStore();
|
|
||||||
const { workspace, workspaceId, isCreateMode, repos } = useWorkspaceContext();
|
const { workspace, workspaceId, isCreateMode, repos } = useWorkspaceContext();
|
||||||
|
// Use workspace-specific panel state (pass undefined when in create mode)
|
||||||
|
const panelState = useWorkspacePanelState(
|
||||||
|
isCreateMode ? undefined : workspaceId
|
||||||
|
);
|
||||||
const diffPaths = useDiffViewStore((s) => s.diffPaths);
|
const diffPaths = useDiffViewStore((s) => s.diffPaths);
|
||||||
const diffViewMode = useDiffViewMode();
|
const diffViewMode = useDiffViewMode();
|
||||||
const expanded = useUiPreferencesStore((s) => s.expanded);
|
const expanded = useUiPreferencesStore((s) => s.expanded);
|
||||||
@@ -61,10 +67,10 @@ export function useActionVisibilityContext(): ActionVisibilityContext {
|
|||||||
false;
|
false;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rightMainPanelMode: layout.rightMainPanelMode,
|
rightMainPanelMode: panelState.rightMainPanelMode,
|
||||||
isLeftSidebarVisible: layout.isLeftSidebarVisible,
|
isLeftSidebarVisible: panelState.isLeftSidebarVisible,
|
||||||
isLeftMainPanelVisible: layout.isLeftMainPanelVisible,
|
isLeftMainPanelVisible: panelState.isLeftMainPanelVisible,
|
||||||
isRightSidebarVisible: layout.isRightSidebarVisible,
|
isRightSidebarVisible: panelState.isRightSidebarVisible,
|
||||||
isCreateMode,
|
isCreateMode,
|
||||||
hasWorkspace: !!workspace,
|
hasWorkspace: !!workspace,
|
||||||
workspaceArchived: workspace?.archived ?? false,
|
workspaceArchived: workspace?.archived ?? false,
|
||||||
@@ -81,10 +87,10 @@ export function useActionVisibilityContext(): ActionVisibilityContext {
|
|||||||
isAttemptRunning: isAttemptRunningVisible,
|
isAttemptRunning: isAttemptRunningVisible,
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
layout.rightMainPanelMode,
|
panelState.rightMainPanelMode,
|
||||||
layout.isLeftSidebarVisible,
|
panelState.isLeftSidebarVisible,
|
||||||
layout.isLeftMainPanelVisible,
|
panelState.isLeftMainPanelVisible,
|
||||||
layout.isRightSidebarVisible,
|
panelState.isRightSidebarVisible,
|
||||||
isCreateMode,
|
isCreateMode,
|
||||||
workspace,
|
workspace,
|
||||||
repos,
|
repos,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
type ExecutionStatus,
|
type ExecutionStatus,
|
||||||
} from '../primitives/SessionChatBox';
|
} from '../primitives/SessionChatBox';
|
||||||
import {
|
import {
|
||||||
useUiPreferencesStore,
|
useWorkspacePanelState,
|
||||||
RIGHT_MAIN_PANEL_MODES,
|
RIGHT_MAIN_PANEL_MODES,
|
||||||
} from '@/stores/useUiPreferencesStore';
|
} from '@/stores/useUiPreferencesStore';
|
||||||
import { Actions, type ActionDefinition } from '../actions';
|
import { Actions, type ActionDefinition } from '../actions';
|
||||||
@@ -102,7 +102,8 @@ export function SessionChatBoxContainer({
|
|||||||
|
|
||||||
const { executeAction } = useActions();
|
const { executeAction } = useActions();
|
||||||
const actionCtx = useActionVisibilityContext();
|
const actionCtx = useActionVisibilityContext();
|
||||||
const { rightMainPanelMode, setRightMainPanelMode } = useUiPreferencesStore();
|
const { rightMainPanelMode, setRightMainPanelMode } =
|
||||||
|
useWorkspacePanelState(workspaceId);
|
||||||
|
|
||||||
const handleViewCode = useCallback(() => {
|
const handleViewCode = useCallback(() => {
|
||||||
setRightMainPanelMode(
|
setRightMainPanelMode(
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useUserSystem } from '@/components/ConfigProvider';
|
|||||||
import {
|
import {
|
||||||
PERSIST_KEYS,
|
PERSIST_KEYS,
|
||||||
usePaneSize,
|
usePaneSize,
|
||||||
useUiPreferencesStore,
|
useWorkspacePanelState,
|
||||||
RIGHT_MAIN_PANEL_MODES,
|
RIGHT_MAIN_PANEL_MODES,
|
||||||
} from '@/stores/useUiPreferencesStore';
|
} from '@/stores/useUiPreferencesStore';
|
||||||
|
|
||||||
@@ -60,6 +60,7 @@ function ModeProvider({
|
|||||||
|
|
||||||
export function WorkspacesLayout() {
|
export function WorkspacesLayout() {
|
||||||
const {
|
const {
|
||||||
|
workspaceId,
|
||||||
workspace: selectedWorkspace,
|
workspace: selectedWorkspace,
|
||||||
isLoading,
|
isLoading,
|
||||||
isCreateMode,
|
isCreateMode,
|
||||||
@@ -72,6 +73,7 @@ export function WorkspacesLayout() {
|
|||||||
startNewSession,
|
startNewSession,
|
||||||
} = useWorkspaceContext();
|
} = useWorkspaceContext();
|
||||||
|
|
||||||
|
// Use workspace-specific panel state (pass undefined when in create mode)
|
||||||
const {
|
const {
|
||||||
isLeftSidebarVisible,
|
isLeftSidebarVisible,
|
||||||
isLeftMainPanelVisible,
|
isLeftMainPanelVisible,
|
||||||
@@ -79,7 +81,7 @@ export function WorkspacesLayout() {
|
|||||||
rightMainPanelMode,
|
rightMainPanelMode,
|
||||||
setLeftSidebarVisible,
|
setLeftSidebarVisible,
|
||||||
setLeftMainPanelVisible,
|
setLeftMainPanelVisible,
|
||||||
} = useUiPreferencesStore();
|
} = useWorkspacePanelState(isCreateMode ? undefined : workspaceId);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import type { LogsPanelContent } from '@/components/ui-new/containers/LogsContentContainer';
|
import type { LogsPanelContent } from '@/components/ui-new/containers/LogsContentContainer';
|
||||||
import {
|
import {
|
||||||
useUiPreferencesStore,
|
useWorkspacePanelState,
|
||||||
RIGHT_MAIN_PANEL_MODES,
|
RIGHT_MAIN_PANEL_MODES,
|
||||||
} from '@/stores/useUiPreferencesStore';
|
} from '@/stores/useUiPreferencesStore';
|
||||||
|
import { useWorkspaceContext } from '@/contexts/WorkspaceContext';
|
||||||
|
|
||||||
interface LogsPanelContextValue {
|
interface LogsPanelContextValue {
|
||||||
logsPanelContent: LogsPanelContent | null;
|
logsPanelContent: LogsPanelContent | null;
|
||||||
@@ -50,7 +51,10 @@ interface LogsPanelProviderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LogsPanelProvider({ children }: LogsPanelProviderProps) {
|
export function LogsPanelProvider({ children }: LogsPanelProviderProps) {
|
||||||
const { rightMainPanelMode, setRightMainPanelMode } = useUiPreferencesStore();
|
const { workspaceId, isCreateMode } = useWorkspaceContext();
|
||||||
|
const { rightMainPanelMode, setRightMainPanelMode } = useWorkspacePanelState(
|
||||||
|
isCreateMode ? undefined : workspaceId
|
||||||
|
);
|
||||||
|
|
||||||
const [logsPanelContent, setLogsPanelContent] =
|
const [logsPanelContent, setLogsPanelContent] =
|
||||||
useState<LogsPanelContent | null>(null);
|
useState<LogsPanelContent | null>(null);
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import {
|
|||||||
} from '@/hooks/useGitHubComments';
|
} from '@/hooks/useGitHubComments';
|
||||||
import { useDiffStream } from '@/hooks/useDiffStream';
|
import { useDiffStream } from '@/hooks/useDiffStream';
|
||||||
import { attemptsApi } from '@/lib/api';
|
import { attemptsApi } from '@/lib/api';
|
||||||
import { useUiPreferencesStore } from '@/stores/useUiPreferencesStore';
|
|
||||||
import { useDiffViewStore } from '@/stores/useDiffViewStore';
|
import { useDiffViewStore } from '@/stores/useDiffViewStore';
|
||||||
import type {
|
import type {
|
||||||
Workspace as ApiWorkspace,
|
Workspace as ApiWorkspace,
|
||||||
@@ -91,13 +90,6 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
// Derive isCreateMode from URL path instead of prop to allow provider to persist across route changes
|
// Derive isCreateMode from URL path instead of prop to allow provider to persist across route changes
|
||||||
const isCreateMode = location.pathname === '/workspaces/create';
|
const isCreateMode = location.pathname === '/workspaces/create';
|
||||||
|
|
||||||
// Reset UI state when entering create mode
|
|
||||||
useEffect(() => {
|
|
||||||
if (isCreateMode) {
|
|
||||||
useUiPreferencesStore.getState().resetForCreateMode();
|
|
||||||
}
|
|
||||||
}, [isCreateMode]);
|
|
||||||
|
|
||||||
// Fetch workspaces for sidebar display
|
// Fetch workspaces for sidebar display
|
||||||
const {
|
const {
|
||||||
workspaces: activeWorkspaces,
|
workspaces: activeWorkspaces,
|
||||||
|
|||||||
@@ -20,6 +20,17 @@ export type ContextBarPosition =
|
|||||||
| 'bottom-left'
|
| 'bottom-left'
|
||||||
| 'bottom-right';
|
| 'bottom-right';
|
||||||
|
|
||||||
|
// Workspace-specific panel state
|
||||||
|
export type WorkspacePanelState = {
|
||||||
|
rightMainPanelMode: RightMainPanelMode | null;
|
||||||
|
isLeftMainPanelVisible: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_WORKSPACE_PANEL_STATE: WorkspacePanelState = {
|
||||||
|
rightMainPanelMode: null,
|
||||||
|
isLeftMainPanelVisible: true,
|
||||||
|
};
|
||||||
|
|
||||||
// Centralized persist keys for type safety
|
// Centralized persist keys for type safety
|
||||||
export const PERSIST_KEYS = {
|
export const PERSIST_KEYS = {
|
||||||
// Sidebar sections
|
// Sidebar sections
|
||||||
@@ -76,13 +87,14 @@ type State = {
|
|||||||
paneSizes: Record<string, number | string>;
|
paneSizes: Record<string, number | string>;
|
||||||
collapsedPaths: Record<string, string[]>;
|
collapsedPaths: Record<string, string[]>;
|
||||||
|
|
||||||
// Layout state
|
// Global layout state (applies across all workspaces)
|
||||||
isLeftSidebarVisible: boolean;
|
isLeftSidebarVisible: boolean;
|
||||||
isLeftMainPanelVisible: boolean;
|
|
||||||
isRightSidebarVisible: boolean;
|
isRightSidebarVisible: boolean;
|
||||||
rightMainPanelMode: RightMainPanelMode | null;
|
|
||||||
previewRefreshKey: number;
|
previewRefreshKey: number;
|
||||||
|
|
||||||
|
// Workspace-specific panel state
|
||||||
|
workspacePanelStates: Record<string, WorkspacePanelState>;
|
||||||
|
|
||||||
// UI preferences actions
|
// UI preferences actions
|
||||||
setRepoAction: (repoId: string, action: RepoAction) => void;
|
setRepoAction: (repoId: string, action: RepoAction) => void;
|
||||||
setExpanded: (key: string, value: boolean) => void;
|
setExpanded: (key: string, value: boolean) => void;
|
||||||
@@ -94,14 +106,26 @@ type State = {
|
|||||||
|
|
||||||
// Layout actions
|
// Layout actions
|
||||||
toggleLeftSidebar: () => void;
|
toggleLeftSidebar: () => void;
|
||||||
toggleLeftMainPanel: () => void;
|
toggleLeftMainPanel: (workspaceId?: string) => void;
|
||||||
toggleRightSidebar: () => void;
|
toggleRightSidebar: () => void;
|
||||||
toggleRightMainPanelMode: (mode: RightMainPanelMode) => void;
|
toggleRightMainPanelMode: (
|
||||||
setRightMainPanelMode: (mode: RightMainPanelMode | null) => void;
|
mode: RightMainPanelMode,
|
||||||
|
workspaceId?: string
|
||||||
|
) => void;
|
||||||
|
setRightMainPanelMode: (
|
||||||
|
mode: RightMainPanelMode | null,
|
||||||
|
workspaceId?: string
|
||||||
|
) => void;
|
||||||
setLeftSidebarVisible: (value: boolean) => void;
|
setLeftSidebarVisible: (value: boolean) => void;
|
||||||
setLeftMainPanelVisible: (value: boolean) => void;
|
setLeftMainPanelVisible: (value: boolean, workspaceId?: string) => void;
|
||||||
triggerPreviewRefresh: () => void;
|
triggerPreviewRefresh: () => void;
|
||||||
resetForCreateMode: () => void;
|
|
||||||
|
// Workspace-specific panel state actions
|
||||||
|
getWorkspacePanelState: (workspaceId: string) => WorkspacePanelState;
|
||||||
|
setWorkspacePanelState: (
|
||||||
|
workspaceId: string,
|
||||||
|
state: Partial<WorkspacePanelState>
|
||||||
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUiPreferencesStore = create<State>()(
|
export const useUiPreferencesStore = create<State>()(
|
||||||
@@ -114,13 +138,14 @@ export const useUiPreferencesStore = create<State>()(
|
|||||||
paneSizes: {},
|
paneSizes: {},
|
||||||
collapsedPaths: {},
|
collapsedPaths: {},
|
||||||
|
|
||||||
// Layout state
|
// Global layout state
|
||||||
isLeftSidebarVisible: true,
|
isLeftSidebarVisible: true,
|
||||||
isLeftMainPanelVisible: true,
|
|
||||||
isRightSidebarVisible: true,
|
isRightSidebarVisible: true,
|
||||||
rightMainPanelMode: null,
|
|
||||||
previewRefreshKey: 0,
|
previewRefreshKey: 0,
|
||||||
|
|
||||||
|
// Workspace-specific panel state
|
||||||
|
workspacePanelStates: {},
|
||||||
|
|
||||||
// UI preferences actions
|
// UI preferences actions
|
||||||
setRepoAction: (repoId, action) =>
|
setRepoAction: (repoId, action) =>
|
||||||
set((s) => ({ repoActions: { ...s.repoActions, [repoId]: action } })),
|
set((s) => ({ repoActions: { ...s.repoActions, [repoId]: action } })),
|
||||||
@@ -151,59 +176,123 @@ export const useUiPreferencesStore = create<State>()(
|
|||||||
toggleLeftSidebar: () =>
|
toggleLeftSidebar: () =>
|
||||||
set((s) => ({ isLeftSidebarVisible: !s.isLeftSidebarVisible })),
|
set((s) => ({ isLeftSidebarVisible: !s.isLeftSidebarVisible })),
|
||||||
|
|
||||||
toggleLeftMainPanel: () => {
|
toggleLeftMainPanel: (workspaceId) => {
|
||||||
const { isLeftMainPanelVisible, rightMainPanelMode } = get();
|
if (!workspaceId) return;
|
||||||
if (isLeftMainPanelVisible && rightMainPanelMode === null) return;
|
const state = get();
|
||||||
set({ isLeftMainPanelVisible: !isLeftMainPanelVisible });
|
const wsState =
|
||||||
|
state.workspacePanelStates[workspaceId] ??
|
||||||
|
DEFAULT_WORKSPACE_PANEL_STATE;
|
||||||
|
if (
|
||||||
|
wsState.isLeftMainPanelVisible &&
|
||||||
|
wsState.rightMainPanelMode === null
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
set({
|
||||||
|
workspacePanelStates: {
|
||||||
|
...state.workspacePanelStates,
|
||||||
|
[workspaceId]: {
|
||||||
|
...wsState,
|
||||||
|
isLeftMainPanelVisible: !wsState.isLeftMainPanelVisible,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleRightSidebar: () =>
|
toggleRightSidebar: () =>
|
||||||
set((s) => ({ isRightSidebarVisible: !s.isRightSidebarVisible })),
|
set((s) => ({ isRightSidebarVisible: !s.isRightSidebarVisible })),
|
||||||
|
|
||||||
toggleRightMainPanelMode: (mode) => {
|
toggleRightMainPanelMode: (mode, workspaceId) => {
|
||||||
const { rightMainPanelMode } = get();
|
if (!workspaceId) return;
|
||||||
const isCurrentlyActive = rightMainPanelMode === mode;
|
const state = get();
|
||||||
|
const wsState =
|
||||||
|
state.workspacePanelStates[workspaceId] ??
|
||||||
|
DEFAULT_WORKSPACE_PANEL_STATE;
|
||||||
|
const isCurrentlyActive = wsState.rightMainPanelMode === mode;
|
||||||
|
|
||||||
if (isCurrentlyActive) {
|
set({
|
||||||
set({
|
workspacePanelStates: {
|
||||||
rightMainPanelMode: null,
|
...state.workspacePanelStates,
|
||||||
isLeftSidebarVisible: true,
|
[workspaceId]: {
|
||||||
});
|
...wsState,
|
||||||
} else {
|
rightMainPanelMode: isCurrentlyActive ? null : mode,
|
||||||
set({
|
},
|
||||||
rightMainPanelMode: mode,
|
},
|
||||||
isLeftSidebarVisible: isWideScreen()
|
isLeftSidebarVisible: isCurrentlyActive
|
||||||
? get().isLeftSidebarVisible
|
? true
|
||||||
|
: isWideScreen()
|
||||||
|
? state.isLeftSidebarVisible
|
||||||
: false,
|
: false,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setRightMainPanelMode: (mode) => {
|
setRightMainPanelMode: (mode, workspaceId) => {
|
||||||
if (mode !== null) {
|
if (!workspaceId) return;
|
||||||
set({
|
const state = get();
|
||||||
rightMainPanelMode: mode,
|
const wsState =
|
||||||
|
state.workspacePanelStates[workspaceId] ??
|
||||||
|
DEFAULT_WORKSPACE_PANEL_STATE;
|
||||||
|
set({
|
||||||
|
workspacePanelStates: {
|
||||||
|
...state.workspacePanelStates,
|
||||||
|
[workspaceId]: {
|
||||||
|
...wsState,
|
||||||
|
rightMainPanelMode: mode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...(mode !== null && {
|
||||||
isLeftSidebarVisible: isWideScreen()
|
isLeftSidebarVisible: isWideScreen()
|
||||||
? get().isLeftSidebarVisible
|
? state.isLeftSidebarVisible
|
||||||
: false,
|
: false,
|
||||||
});
|
}),
|
||||||
} else {
|
});
|
||||||
set({ rightMainPanelMode: null });
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setLeftSidebarVisible: (value) => set({ isLeftSidebarVisible: value }),
|
setLeftSidebarVisible: (value) => set({ isLeftSidebarVisible: value }),
|
||||||
|
|
||||||
setLeftMainPanelVisible: (value) =>
|
setLeftMainPanelVisible: (value, workspaceId) => {
|
||||||
set({ isLeftMainPanelVisible: value }),
|
if (!workspaceId) return;
|
||||||
|
const state = get();
|
||||||
|
const wsState =
|
||||||
|
state.workspacePanelStates[workspaceId] ??
|
||||||
|
DEFAULT_WORKSPACE_PANEL_STATE;
|
||||||
|
set({
|
||||||
|
workspacePanelStates: {
|
||||||
|
...state.workspacePanelStates,
|
||||||
|
[workspaceId]: {
|
||||||
|
...wsState,
|
||||||
|
isLeftMainPanelVisible: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
triggerPreviewRefresh: () =>
|
triggerPreviewRefresh: () =>
|
||||||
set((s) => ({ previewRefreshKey: s.previewRefreshKey + 1 })),
|
set((s) => ({ previewRefreshKey: s.previewRefreshKey + 1 })),
|
||||||
|
|
||||||
resetForCreateMode: () =>
|
// Workspace-specific panel state actions
|
||||||
|
getWorkspacePanelState: (workspaceId) => {
|
||||||
|
const state = get();
|
||||||
|
return (
|
||||||
|
state.workspacePanelStates[workspaceId] ??
|
||||||
|
DEFAULT_WORKSPACE_PANEL_STATE
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
setWorkspacePanelState: (workspaceId, panelState) => {
|
||||||
|
const state = get();
|
||||||
|
const currentWsState =
|
||||||
|
state.workspacePanelStates[workspaceId] ??
|
||||||
|
DEFAULT_WORKSPACE_PANEL_STATE;
|
||||||
set({
|
set({
|
||||||
rightMainPanelMode: null,
|
workspacePanelStates: {
|
||||||
}),
|
...state.workspacePanelStates,
|
||||||
|
[workspaceId]: {
|
||||||
|
...currentWsState,
|
||||||
|
...panelState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'ui-preferences',
|
name: 'ui-preferences',
|
||||||
@@ -214,10 +303,11 @@ export const useUiPreferencesStore = create<State>()(
|
|||||||
contextBarPosition: state.contextBarPosition,
|
contextBarPosition: state.contextBarPosition,
|
||||||
paneSizes: state.paneSizes,
|
paneSizes: state.paneSizes,
|
||||||
collapsedPaths: state.collapsedPaths,
|
collapsedPaths: state.collapsedPaths,
|
||||||
// Layout (only persist panel visibility, not mode states)
|
// Global layout (persist sidebar visibility)
|
||||||
isLeftSidebarVisible: state.isLeftSidebarVisible,
|
isLeftSidebarVisible: state.isLeftSidebarVisible,
|
||||||
isLeftMainPanelVisible: state.isLeftMainPanelVisible,
|
|
||||||
isRightSidebarVisible: state.isRightSidebarVisible,
|
isRightSidebarVisible: state.isRightSidebarVisible,
|
||||||
|
// Workspace-specific panel state (persisted)
|
||||||
|
workspacePanelStates: state.workspacePanelStates,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -301,6 +391,70 @@ export function usePersistedCollapsedPaths(
|
|||||||
return [pathSet, setPathSet];
|
return [pathSet, setPathSet];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layout convenience hooks
|
// Hook for workspace-specific panel state
|
||||||
export const useIsRightMainPanelVisible = () =>
|
export function useWorkspacePanelState(workspaceId: string | undefined) {
|
||||||
useUiPreferencesStore((s) => s.rightMainPanelMode !== null);
|
// Get workspace-specific state (falls back to defaults when no workspaceId)
|
||||||
|
const workspacePanelStates = useUiPreferencesStore(
|
||||||
|
(s) => s.workspacePanelStates
|
||||||
|
);
|
||||||
|
const wsState = workspaceId
|
||||||
|
? (workspacePanelStates[workspaceId] ?? DEFAULT_WORKSPACE_PANEL_STATE)
|
||||||
|
: DEFAULT_WORKSPACE_PANEL_STATE;
|
||||||
|
|
||||||
|
// Global state (sidebars are global)
|
||||||
|
const isLeftSidebarVisible = useUiPreferencesStore(
|
||||||
|
(s) => s.isLeftSidebarVisible
|
||||||
|
);
|
||||||
|
const isRightSidebarVisible = useUiPreferencesStore(
|
||||||
|
(s) => s.isRightSidebarVisible
|
||||||
|
);
|
||||||
|
|
||||||
|
// Actions from store
|
||||||
|
const toggleRightMainPanelMode = useUiPreferencesStore(
|
||||||
|
(s) => s.toggleRightMainPanelMode
|
||||||
|
);
|
||||||
|
const setRightMainPanelMode = useUiPreferencesStore(
|
||||||
|
(s) => s.setRightMainPanelMode
|
||||||
|
);
|
||||||
|
const setLeftMainPanelVisible = useUiPreferencesStore(
|
||||||
|
(s) => s.setLeftMainPanelVisible
|
||||||
|
);
|
||||||
|
const setLeftSidebarVisible = useUiPreferencesStore(
|
||||||
|
(s) => s.setLeftSidebarVisible
|
||||||
|
);
|
||||||
|
|
||||||
|
// Memoized callbacks that include workspaceId
|
||||||
|
const toggleRightMainPanelModeForWorkspace = useCallback(
|
||||||
|
(mode: RightMainPanelMode) => toggleRightMainPanelMode(mode, workspaceId),
|
||||||
|
[toggleRightMainPanelMode, workspaceId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const setRightMainPanelModeForWorkspace = useCallback(
|
||||||
|
(mode: RightMainPanelMode | null) =>
|
||||||
|
setRightMainPanelMode(mode, workspaceId),
|
||||||
|
[setRightMainPanelMode, workspaceId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const setLeftMainPanelVisibleForWorkspace = useCallback(
|
||||||
|
(value: boolean) => setLeftMainPanelVisible(value, workspaceId),
|
||||||
|
[setLeftMainPanelVisible, workspaceId]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Workspace-specific state
|
||||||
|
rightMainPanelMode: wsState.rightMainPanelMode,
|
||||||
|
isLeftMainPanelVisible: wsState.isLeftMainPanelVisible,
|
||||||
|
|
||||||
|
// Global state (sidebars)
|
||||||
|
isLeftSidebarVisible,
|
||||||
|
isRightSidebarVisible,
|
||||||
|
|
||||||
|
// Workspace-specific actions
|
||||||
|
toggleRightMainPanelMode: toggleRightMainPanelModeForWorkspace,
|
||||||
|
setRightMainPanelMode: setRightMainPanelModeForWorkspace,
|
||||||
|
setLeftMainPanelVisible: setLeftMainPanelVisibleForWorkspace,
|
||||||
|
|
||||||
|
// Global actions
|
||||||
|
setLeftSidebarVisible,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user