diff --git a/src/App.svelte b/src/App.svelte index 1c6af8b..394857e 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -18,6 +18,7 @@ import IssueCenter from "./lib/components/IssueCenter.svelte"; import { readWorkspaces, WORKSPACES_KEY, type Workspace, type WorkspaceState } from "./lib/workspaces"; import RepositoryDashboard from "./lib/components/RepositoryDashboard.svelte"; + import SidebarSectionMenu from "./lib/components/SidebarSectionMenu.svelte"; import ReviewCenter from "./lib/components/ReviewCenter.svelte"; import RepoTabs from "./lib/RepoTabs.svelte"; import AiReviewDialog from "./lib/components/AiReviewDialog.svelte"; @@ -296,6 +297,7 @@ const AUTO_REFRESH_ENABLED_KEY = "gitlite.autoRefreshEnabled.v1"; const COMMIT_PANEL_HEIGHT_KEY = "gitlite.commitPanelHeight.v1"; const LEFT_SIDEBAR_WIDTH_KEY = "gitlite.leftSidebarWidth.v1"; + const SIDEBAR_VISIBILITY_KEY = "gitlite.sidebarVisibility.v1"; const SIDEBAR_PANEL_HEIGHTS_KEY = "gitlite.sidebarPanelHeights.v2"; const BRANCH_PANEL_COLLAPSED_KEY = "gitlite.branchPanelCollapsed.v1"; const STASH_PANEL_COLLAPSED_KEY = "gitlite.stashPanelCollapsed.v2"; @@ -564,6 +566,9 @@ let resizingLeftSidebar = false; let leftSidebarResizeStartX = 0; let leftSidebarResizeStartWidth = 0; + let sidebarVisibility = loadSidebarVisibility(); + let sidebarSectionMenu: { x: number; y: number } | null = null; + let sidebarMenuReturnFocus: HTMLElement | null = null; let sidebarPanelHeights: Record = loadSidebarPanelHeights(); let resizingSidebarPanel: SidebarPanelId | null = null; let sidebarResizeStartY = 0; @@ -585,6 +590,7 @@ $: isBusy = operation.length > 0; $: hasRepository = activeRepoPath.length > 0 && status !== null; + $: if (activeView !== "repository") sidebarSectionMenu = null; $: workspaceActive = activeView === "repository" && hasRepository; $: openingRepo = operation === "Opening repository"; $: cloningRepo = operation === "Cloning repository"; @@ -625,8 +631,8 @@ ) as Record; $: remoteBranches = branches.filter((b) => b.remote); $: currentBranchIsLocalOnly = Boolean(status?.current_branch) && !status?.upstream; - $: leftSidebarRows = buildLeftSidebarRows(branchPanelCollapsed, worktreePanelCollapsed, tagsPanelCollapsed, stashPanelCollapsed, explorerPanelCollapsed, sidebarPanelHeights); - $: allLeftPanelsCollapsed = branchPanelCollapsed && worktreePanelCollapsed && tagsPanelCollapsed && stashPanelCollapsed && explorerPanelCollapsed; + $: leftSidebarRows = buildLeftSidebarRows(branchPanelCollapsed, worktreePanelCollapsed, tagsPanelCollapsed, stashPanelCollapsed, explorerPanelCollapsed, sidebarPanelHeights, sidebarVisibility); + $: allLeftPanelsCollapsed = branchPanelCollapsed && (!sidebarVisibility.worktree || worktreePanelCollapsed) && (!sidebarVisibility.tags || tagsPanelCollapsed) && (!sidebarVisibility.stash || stashPanelCollapsed) && (!sidebarVisibility.explorer || explorerPanelCollapsed); $: editorToolName = externalToolDisplayName("editor", externalToolsSettings.editor, detectedExternalTools); $: diffToolName = externalToolDisplayName("diff", externalToolsSettings.diff, detectedExternalTools); $: mergeToolName = externalToolDisplayName("merge", externalToolsSettings.merge, detectedExternalTools); @@ -2062,6 +2068,38 @@ // ── Sidebar panel sizing ─────────────────────────────────────────────────── + function loadSidebarVisibility(): Record { + const visible = { branch: true, worktree: true, tags: true, stash: true, explorer: true }; + try { + const stored = JSON.parse(localStorage.getItem(SIDEBAR_VISIBILITY_KEY) ?? "{}"); + for (const panel of SIDEBAR_PANEL_ORDER) { + if (panel !== "branch" && typeof stored?.[panel] === "boolean") visible[panel] = stored[panel]; + } + } catch { /* Keep all sections visible if stored preferences are unavailable. */ } + return visible; + } + + function toggleSidebarVisibility(id: string) { + if (id === "branch" || !SIDEBAR_PANEL_ORDER.includes(id as SidebarPanelId)) return; + const panel = id as SidebarPanelId; + sidebarVisibility = { ...sidebarVisibility, [panel]: !sidebarVisibility[panel] }; + try { localStorage.setItem(SIDEBAR_VISIBILITY_KEY, JSON.stringify(sidebarVisibility)); } + catch { /* Visibility changes still work without persistent storage. */ } + } + + function closeSidebarSectionMenu() { + sidebarSectionMenu = null; + if (sidebarMenuReturnFocus?.isConnected) sidebarMenuReturnFocus.focus({ preventScroll: true }); + } + + function openSidebarSectionMenu(event: MouseEvent) { + event.preventDefault(); + event.stopPropagation(); + closeRepoTabContextMenu(); + sidebarMenuReturnFocus = event.currentTarget as HTMLElement; + sidebarSectionMenu = { x: event.clientX, y: event.clientY }; + } + function clampSidebarPanelHeight(panel: SidebarPanelId, value: number): number { return Math.min(SIDEBAR_PANEL_MAX_HEIGHT, Math.max(SIDEBAR_PANEL_MIN_HEIGHT[panel], Math.round(value))); } @@ -2100,7 +2138,7 @@ /** Expanded panels, top to bottom. The last one always fills the leftover space. */ function expandedSidebarPanels(): SidebarPanelId[] { - return SIDEBAR_PANEL_ORDER.filter((panel) => !sidebarPanelIsCollapsed(panel)); + return SIDEBAR_PANEL_ORDER.filter((panel) => sidebarVisibility[panel] && !sidebarPanelIsCollapsed(panel)); } /** The first expanded panel below `panel` — the one that gives way while dragging. */ @@ -2111,9 +2149,9 @@ } /** A handle only makes sense between two expanded panels. */ - function sidebarHandleVisible(panel: SidebarPanelId, ...markers: boolean[]): boolean { + function sidebarHandleVisible(panel: SidebarPanelId, ...markers: unknown[]): boolean { void markers; - return !sidebarPanelIsCollapsed(panel) && nextExpandedSidebarPanel(panel) !== null; + return sidebarVisibility[panel] && !sidebarPanelIsCollapsed(panel) && nextExpandedSidebarPanel(panel) !== null; } function buildLeftSidebarRows( @@ -2123,6 +2161,7 @@ stashCollapsed: boolean, explorerCollapsed: boolean, heights: Record, + visibility: Record, ): string { void branchCollapsed; void worktreeCollapsed; void tagsCollapsed; void stashCollapsed; void explorerCollapsed; @@ -2131,6 +2170,7 @@ const rows: string[] = []; for (const panel of SIDEBAR_PANEL_ORDER) { + if (!visibility[panel]) continue; if (sidebarPanelIsCollapsed(panel)) rows.push("auto"); else if (panel === flexible) rows.push(`minmax(${SIDEBAR_PANEL_MIN_HEIGHT[panel]}px, 1fr)`); else rows.push(`${heights[panel]}px`); @@ -5902,6 +5942,17 @@ +{#if sidebarSectionMenu && activeView === "repository"} + +{/if} +
+ diff --git a/src/lib/components/SidebarSectionMenu.svelte b/src/lib/components/SidebarSectionMenu.svelte new file mode 100644 index 0000000..f8c88b6 --- /dev/null +++ b/src/lib/components/SidebarSectionMenu.svelte @@ -0,0 +1,79 @@ + + + { if (!menu.contains(event.target as Node)) onClose(); }} + onresize={() => { viewport = { width: window.innerWidth, height: window.innerHeight }; }} +/> + + +