diff --git a/src/App.svelte b/src/App.svelte index c78f283..257cf4b 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -165,13 +165,33 @@ const AI_SETTINGS_KEY = "gitlite.aiSettings.v1"; const ANALYTICS_SETTINGS_KEY = "gitlite.analyticsSettings.v1"; const COMMIT_PANEL_HEIGHT_KEY = "gitlite.commitPanelHeight.v1"; + const LEFT_SIDEBAR_WIDTH_KEY = "gitlite.leftSidebarWidth.v1"; + const LEFT_BRANCH_PANEL_HEIGHT_KEY = "gitlite.leftBranchPanelHeight.v1"; + const LEFT_STASH_PANEL_HEIGHT_KEY = "gitlite.leftStashPanelHeight.v1"; + const BRANCH_PANEL_COLLAPSED_KEY = "gitlite.branchPanelCollapsed.v1"; + const STASH_PANEL_COLLAPSED_KEY = "gitlite.stashPanelCollapsed.v2"; + const EXPLORER_PANEL_COLLAPSED_KEY = "gitlite.explorerPanelCollapsed.v1"; const HISTORY_ASIDE_WIDTH_KEY = "gitlite.historyAsideWidth.v1"; + const FILE_HISTORY_WIDTH_KEY = "gitlite.fileHistoryWidth.v1"; const COMMIT_PANEL_DEFAULT_HEIGHT = 220; const COMMIT_PANEL_MIN_HEIGHT = COMMIT_PANEL_DEFAULT_HEIGHT; const COMMIT_PANEL_MAX_HEIGHT = 640; + const LEFT_SIDEBAR_DEFAULT_WIDTH = 280; + const LEFT_SIDEBAR_MIN_WIDTH = 220; + const LEFT_SIDEBAR_MAX_WIDTH = 420; + const LEFT_BRANCH_PANEL_DEFAULT_HEIGHT = 260; + const LEFT_BRANCH_PANEL_MIN_HEIGHT = 180; + const LEFT_BRANCH_PANEL_MAX_HEIGHT = 560; + const LEFT_STASH_PANEL_DEFAULT_HEIGHT = 190; + const LEFT_STASH_PANEL_MIN_HEIGHT = 150; + const LEFT_STASH_PANEL_MAX_HEIGHT = 420; + const LEFT_EXPLORER_PANEL_MIN_HEIGHT = 220; const HISTORY_ASIDE_DEFAULT_WIDTH = 620; const HISTORY_ASIDE_MIN_WIDTH = 560; const HISTORY_ASIDE_MAX_WIDTH = 920; + const FILE_HISTORY_DEFAULT_WIDTH = 300; + const FILE_HISTORY_MIN_WIDTH = 240; + const FILE_HISTORY_MAX_WIDTH = 520; const ERROR_AUTO_HIDE_MS = 6000; // ── State ────────────────────────────────────────────────────────────────── @@ -292,10 +312,30 @@ let resizingCommitPanel = false; let resizeStartY = 0; let resizeStartHeight = 0; + let leftSidebarWidth = loadLeftSidebarWidth(); + let resizingLeftSidebar = false; + let leftSidebarResizeStartX = 0; + let leftSidebarResizeStartWidth = 0; + let leftBranchPanelHeight = loadLeftBranchPanelHeight(); + let resizingLeftBranchPanel = false; + let leftBranchResizeStartY = 0; + let leftBranchResizeStartHeight = 0; + let leftStashPanelHeight = loadLeftStashPanelHeight(); + let resizingLeftStashPanel = false; + let leftStashResizeStartY = 0; + let leftStashResizeStartHeight = 0; + let branchPanelCollapsed = loadStoredBoolean(BRANCH_PANEL_COLLAPSED_KEY, false); + let stashPanelCollapsed = loadStoredBoolean(STASH_PANEL_COLLAPSED_KEY, false); + let explorerPanelCollapsed = loadStoredBoolean(EXPLORER_PANEL_COLLAPSED_KEY, false); let historyAsideWidth = loadHistoryAsideWidth(); let resizingHistoryAside = false; let historyResizeStartX = 0; let historyResizeStartWidth = 0; + let fileHistoryWidth = loadFileHistoryWidth(); + let resizingFileHistory = false; + let fileHistoryResizeStartX = 0; + let fileHistoryResizeStartWidth = 0; + let fileHistoryCollapsed = true; // ── Derived ──────────────────────────────────────────────────────────────── @@ -345,6 +385,8 @@ $: favoriteRepoRows = favoriteRepoPaths .map(repoRowFromPath) .filter(repoMatchesSearch); + $: leftSidebarRows = buildLeftSidebarRows(branchPanelCollapsed, stashPanelCollapsed, explorerPanelCollapsed); + $: allLeftPanelsCollapsed = branchPanelCollapsed && stashPanelCollapsed && explorerPanelCollapsed; // ── Lifecycle ────────────────────────────────────────────────────────────── @@ -1024,6 +1066,25 @@ } } + function loadStoredBoolean(key: string, fallback: boolean): boolean { + try { + const stored = localStorage.getItem(key); + if (stored === "true") return true; + if (stored === "false") return false; + } catch { + // Local storage is best-effort only; panel defaults are enough. + } + return fallback; + } + + function persistStoredBoolean(key: string, value: boolean) { + try { + localStorage.setItem(key, String(value)); + } catch { + // Local storage is best-effort only; toggles must keep working without it. + } + } + function clampCommitPanelHeight(value: number): number { return Math.min(COMMIT_PANEL_MAX_HEIGHT, Math.max(COMMIT_PANEL_MIN_HEIGHT, Math.round(value))); } @@ -1046,6 +1107,72 @@ } } + function clampLeftSidebarWidth(value: number): number { + return Math.min(LEFT_SIDEBAR_MAX_WIDTH, Math.max(LEFT_SIDEBAR_MIN_WIDTH, Math.round(value))); + } + + function loadLeftSidebarWidth(): number { + try { + const stored = Number(localStorage.getItem(LEFT_SIDEBAR_WIDTH_KEY)); + if (Number.isFinite(stored) && stored > 0) return clampLeftSidebarWidth(stored); + } catch { + // Fall through to the default below. + } + return LEFT_SIDEBAR_DEFAULT_WIDTH; + } + + function persistLeftSidebarWidth(value: number) { + try { + localStorage.setItem(LEFT_SIDEBAR_WIDTH_KEY, String(value)); + } catch { + // Local storage is best-effort only; resizing must keep working without it. + } + } + + function clampLeftBranchPanelHeight(value: number): number { + return Math.min(LEFT_BRANCH_PANEL_MAX_HEIGHT, Math.max(LEFT_BRANCH_PANEL_MIN_HEIGHT, Math.round(value))); + } + + function loadLeftBranchPanelHeight(): number { + try { + const stored = Number(localStorage.getItem(LEFT_BRANCH_PANEL_HEIGHT_KEY)); + if (Number.isFinite(stored) && stored > 0) return clampLeftBranchPanelHeight(stored); + } catch { + // Fall through to the default below. + } + return LEFT_BRANCH_PANEL_DEFAULT_HEIGHT; + } + + function persistLeftBranchPanelHeight(value: number) { + try { + localStorage.setItem(LEFT_BRANCH_PANEL_HEIGHT_KEY, String(value)); + } catch { + // Local storage is best-effort only; resizing must keep working without it. + } + } + + function clampLeftStashPanelHeight(value: number): number { + return Math.min(LEFT_STASH_PANEL_MAX_HEIGHT, Math.max(LEFT_STASH_PANEL_MIN_HEIGHT, Math.round(value))); + } + + function loadLeftStashPanelHeight(): number { + try { + const stored = Number(localStorage.getItem(LEFT_STASH_PANEL_HEIGHT_KEY)); + if (Number.isFinite(stored) && stored > 0) return clampLeftStashPanelHeight(stored); + } catch { + // Fall through to the default below. + } + return LEFT_STASH_PANEL_DEFAULT_HEIGHT; + } + + function persistLeftStashPanelHeight(value: number) { + try { + localStorage.setItem(LEFT_STASH_PANEL_HEIGHT_KEY, String(value)); + } catch { + // Local storage is best-effort only; resizing must keep working without it. + } + } + function clampHistoryAsideWidth(value: number): number { return Math.min(HISTORY_ASIDE_MAX_WIDTH, Math.max(HISTORY_ASIDE_MIN_WIDTH, Math.round(value))); } @@ -1068,6 +1195,28 @@ } } + function clampFileHistoryWidth(value: number): number { + return Math.min(FILE_HISTORY_MAX_WIDTH, Math.max(FILE_HISTORY_MIN_WIDTH, Math.round(value))); + } + + function loadFileHistoryWidth(): number { + try { + const stored = Number(localStorage.getItem(FILE_HISTORY_WIDTH_KEY)); + if (Number.isFinite(stored) && stored > 0) return clampFileHistoryWidth(stored); + } catch { + // Fall through to the default below. + } + return FILE_HISTORY_DEFAULT_WIDTH; + } + + function persistFileHistoryWidth(value: number) { + try { + localStorage.setItem(FILE_HISTORY_WIDTH_KEY, String(value)); + } catch { + // Local storage is best-effort only; resizing must keep working without it. + } + } + function startCommitPanelResize(event: PointerEvent) { event.preventDefault(); resizingCommitPanel = true; @@ -1096,6 +1245,131 @@ persistCommitPanelHeight(commitPanelHeight); } + function startLeftSidebarResize(event: PointerEvent) { + event.preventDefault(); + resizingLeftSidebar = true; + leftSidebarResizeStartX = event.clientX; + leftSidebarResizeStartWidth = leftSidebarWidth; + (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId); + } + + function onLeftSidebarResizeMove(event: PointerEvent) { + if (!resizingLeftSidebar) return; + leftSidebarWidth = clampLeftSidebarWidth(leftSidebarResizeStartWidth + (event.clientX - leftSidebarResizeStartX)); + } + + function endLeftSidebarResize(event: PointerEvent) { + if (!resizingLeftSidebar) return; + resizingLeftSidebar = false; + persistLeftSidebarWidth(leftSidebarWidth); + const target = event.currentTarget as HTMLElement; + if (target.hasPointerCapture(event.pointerId)) target.releasePointerCapture(event.pointerId); + } + + function onLeftSidebarResizeKeydown(event: KeyboardEvent) { + if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return; + event.preventDefault(); + leftSidebarWidth = clampLeftSidebarWidth(leftSidebarWidth + (event.key === "ArrowRight" ? 20 : -20)); + persistLeftSidebarWidth(leftSidebarWidth); + } + + function buildLeftSidebarRows(branchCollapsed: boolean, stashCollapsed: boolean, explorerCollapsed: boolean): string { + const branchRow = branchCollapsed + ? "auto" + : `minmax(${LEFT_BRANCH_PANEL_MIN_HEIGHT}px, var(--branch-panel-height, ${LEFT_BRANCH_PANEL_DEFAULT_HEIGHT}px))`; + const branchHandleRow = branchCollapsed ? "0" : "14px"; + const stashRow = stashCollapsed + ? "auto" + : `minmax(${LEFT_STASH_PANEL_MIN_HEIGHT}px, var(--stash-panel-height, ${LEFT_STASH_PANEL_DEFAULT_HEIGHT}px))`; + const stashHandleRow = explorerCollapsed || (stashCollapsed && branchCollapsed) ? "0" : "14px"; + const explorerRow = explorerCollapsed ? "auto" : `minmax(${LEFT_EXPLORER_PANEL_MIN_HEIGHT}px, 1fr)`; + + return `${branchRow} ${branchHandleRow} ${stashRow} ${stashHandleRow} ${explorerRow}`; + } + + function startLeftBranchPanelResize(event: PointerEvent) { + if (branchPanelCollapsed) return; + event.preventDefault(); + resizingLeftBranchPanel = true; + leftBranchResizeStartY = event.clientY; + leftBranchResizeStartHeight = leftBranchPanelHeight; + (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId); + } + + function onLeftBranchPanelResizeMove(event: PointerEvent) { + if (!resizingLeftBranchPanel) return; + leftBranchPanelHeight = clampLeftBranchPanelHeight(leftBranchResizeStartHeight + (event.clientY - leftBranchResizeStartY)); + } + + function endLeftBranchPanelResize(event: PointerEvent) { + if (!resizingLeftBranchPanel) return; + resizingLeftBranchPanel = false; + persistLeftBranchPanelHeight(leftBranchPanelHeight); + const target = event.currentTarget as HTMLElement; + if (target.hasPointerCapture(event.pointerId)) target.releasePointerCapture(event.pointerId); + } + + function onLeftBranchPanelResizeKeydown(event: KeyboardEvent) { + if (branchPanelCollapsed || (event.key !== "ArrowUp" && event.key !== "ArrowDown")) return; + event.preventDefault(); + leftBranchPanelHeight = clampLeftBranchPanelHeight(leftBranchPanelHeight + (event.key === "ArrowDown" ? 20 : -20)); + persistLeftBranchPanelHeight(leftBranchPanelHeight); + } + + function startLeftStashPanelResize(event: PointerEvent) { + if (stashPanelCollapsed && branchPanelCollapsed) return; + event.preventDefault(); + resizingLeftStashPanel = true; + leftStashResizeStartY = event.clientY; + leftStashResizeStartHeight = stashPanelCollapsed ? leftBranchPanelHeight : leftStashPanelHeight; + (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId); + } + + function onLeftStashPanelResizeMove(event: PointerEvent) { + if (!resizingLeftStashPanel) return; + if (stashPanelCollapsed) { + leftBranchPanelHeight = clampLeftBranchPanelHeight(leftStashResizeStartHeight + (event.clientY - leftStashResizeStartY)); + } else { + leftStashPanelHeight = clampLeftStashPanelHeight(leftStashResizeStartHeight + (event.clientY - leftStashResizeStartY)); + } + } + + function endLeftStashPanelResize(event: PointerEvent) { + if (!resizingLeftStashPanel) return; + resizingLeftStashPanel = false; + if (stashPanelCollapsed) persistLeftBranchPanelHeight(leftBranchPanelHeight); + else persistLeftStashPanelHeight(leftStashPanelHeight); + const target = event.currentTarget as HTMLElement; + if (target.hasPointerCapture(event.pointerId)) target.releasePointerCapture(event.pointerId); + } + + function onLeftStashPanelResizeKeydown(event: KeyboardEvent) { + if ((stashPanelCollapsed && branchPanelCollapsed) || (event.key !== "ArrowUp" && event.key !== "ArrowDown")) return; + event.preventDefault(); + if (stashPanelCollapsed) { + leftBranchPanelHeight = clampLeftBranchPanelHeight(leftBranchPanelHeight + (event.key === "ArrowDown" ? 20 : -20)); + persistLeftBranchPanelHeight(leftBranchPanelHeight); + } else { + leftStashPanelHeight = clampLeftStashPanelHeight(leftStashPanelHeight + (event.key === "ArrowDown" ? 20 : -20)); + persistLeftStashPanelHeight(leftStashPanelHeight); + } + } + + function toggleBranchPanelCollapsed() { + branchPanelCollapsed = !branchPanelCollapsed; + persistStoredBoolean(BRANCH_PANEL_COLLAPSED_KEY, branchPanelCollapsed); + } + + function toggleStashPanelCollapsed() { + stashPanelCollapsed = !stashPanelCollapsed; + persistStoredBoolean(STASH_PANEL_COLLAPSED_KEY, stashPanelCollapsed); + } + + function toggleExplorerPanelCollapsed() { + explorerPanelCollapsed = !explorerPanelCollapsed; + persistStoredBoolean(EXPLORER_PANEL_COLLAPSED_KEY, explorerPanelCollapsed); + } + function startHistoryAsideResize(event: PointerEvent) { event.preventDefault(); resizingHistoryAside = true; @@ -1124,6 +1398,43 @@ persistHistoryAsideWidth(historyAsideWidth); } + function startFileHistoryResize(event: PointerEvent) { + if (fileHistoryCollapsed) return; + event.preventDefault(); + resizingFileHistory = true; + fileHistoryResizeStartX = event.clientX; + fileHistoryResizeStartWidth = fileHistoryWidth; + (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId); + } + + function onFileHistoryResizeMove(event: PointerEvent) { + if (!resizingFileHistory) return; + fileHistoryWidth = clampFileHistoryWidth(fileHistoryResizeStartWidth + (fileHistoryResizeStartX - event.clientX)); + } + + function endFileHistoryResize(event: PointerEvent) { + if (!resizingFileHistory) return; + resizingFileHistory = false; + persistFileHistoryWidth(fileHistoryWidth); + const target = event.currentTarget as HTMLElement; + if (target.hasPointerCapture(event.pointerId)) target.releasePointerCapture(event.pointerId); + } + + function onFileHistoryResizeKeydown(event: KeyboardEvent) { + if (fileHistoryCollapsed || (event.key !== "ArrowLeft" && event.key !== "ArrowRight")) return; + event.preventDefault(); + fileHistoryWidth = clampFileHistoryWidth(fileHistoryWidth + (event.key === "ArrowLeft" ? 20 : -20)); + persistFileHistoryWidth(fileHistoryWidth); + } + + function toggleFileHistoryCollapsed() { + fileHistoryCollapsed = !fileHistoryCollapsed; + } + + function revealFileHistory() { + fileHistoryCollapsed = false; + } + function rememberRecentRepo(path: string) { recentRepoPaths = uniqueRepoPaths([path, ...recentRepoPaths]).slice(0, 40); persistRepoLists(); @@ -2608,6 +2919,7 @@ if (!activeRepoPath || (selectedExplorerPath === node.path && selectedExplorerKind === node.kind)) return; selectedExplorerPath = node.path; selectedExplorerKind = node.kind; + revealFileHistory(); void loadSelectedFileHistory(node.path); trackEvent("explorer_node_selected", { kind: node.kind, @@ -2620,6 +2932,7 @@ selectedExplorerPath = file.path; selectedExplorerKind = "file"; expandedExplorerPaths = new Set([...expandedExplorerPaths, ...explorerParentFolders(file.path)]); + revealFileHistory(); void loadSelectedFileHistory(file.path); trackEvent("explorer_file_selected", { @@ -2633,6 +2946,7 @@ selectedExplorerPath = file.path; selectedExplorerKind = "file"; expandedExplorerPaths = new Set([...expandedExplorerPaths, ...explorerParentFolders(file.path)]); + revealFileHistory(); void loadSelectedFileHistory(file.path); trackEvent("explorer_file_selected", { @@ -2645,6 +2959,7 @@ if (!activeRepoPath || node.kind !== "file") return; selectedExplorerPath = node.path; selectedExplorerKind = "file"; + revealFileHistory(); void loadSelectedFileHistory(node.path); try { @@ -3231,10 +3546,22 @@ {:else} -
+
-
diff --git a/src/app.css b/src/app.css index f59a20d..1b31446 100644 --- a/src/app.css +++ b/src/app.css @@ -1096,63 +1096,120 @@ .workspace { display: grid; - grid-template-columns: clamp(220px, 18vw, 280px) minmax(0, 1fr) minmax(560px, var(--history-aside-width, 620px)); + grid-template-columns: minmax(220px, var(--left-sidebar-width, 280px)) 8px minmax(0, 1fr) 8px minmax(560px, var(--history-aside-width, 620px)); flex: 1 1 0; min-width: 0; min-height: 0; - gap: 8px; + column-gap: 0; + row-gap: 8px; } .history-aside { position: relative; display: grid; - grid-template-columns: minmax(0, 1fr) 300px; + grid-template-columns: minmax(0, 1fr) 8px minmax(240px, var(--file-history-width, 300px)); min-width: 0; min-height: 0; - gap: 8px; + column-gap: 0; + row-gap: 8px; } - .history-resize-handle { - position: absolute; - z-index: 20; - top: 0; - bottom: 0; - left: -7px; - width: 12px; + .history-aside.file-history-collapsed { + grid-template-columns: minmax(0, 1fr) 0 42px; + } + + .left-sidebar-resize-handle, + .history-resize-handle, + .file-history-resize-handle { + position: relative; + z-index: 10; + display: flex; + align-items: center; + justify-content: center; + min-width: 8px; cursor: col-resize; touch-action: none; } - .history-resize-handle::before { + .left-sidebar-resize-handle::before, + .history-resize-handle::before, + .file-history-resize-handle::before { content: ""; - position: absolute; - top: 12px; - bottom: 12px; - left: 5px; - width: 2px; + width: 3px; + height: 40px; border-radius: 999px; - background: transparent; - transition: background 120ms ease, box-shadow 120ms ease; + background: var(--color-border); + transition: background-color 0.15s ease, box-shadow 0.15s ease; } + .left-sidebar-resize-handle:hover::before, + .left-sidebar-resize-handle.resizing::before, .history-resize-handle:hover::before, - .history-resize-handle.resizing::before { - background: rgba(65,209,255,0.62); + .history-resize-handle.resizing::before, + .file-history-resize-handle:hover::before, + .file-history-resize-handle.resizing::before { + background: var(--color-accent); box-shadow: 0 0 14px rgba(65,209,255,0.3); } - .history-resize-handle:focus-visible { + .left-sidebar-resize-handle:focus-visible, + .history-resize-handle:focus-visible, + .file-history-resize-handle:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; } + .history-aside.file-history-collapsed .file-history-resize-handle { + pointer-events: none; + } + .history-aside.file-history-collapsed .file-history-resize-handle::before { + display: none; + } + .left-sidebar { display: grid; - grid-template-rows: minmax(170px, 0.75fr) minmax(150px, 0.55fr) minmax(220px, 1fr); + grid-template-rows: minmax(180px, var(--branch-panel-height, 260px)) 14px minmax(150px, var(--stash-panel-height, 190px)) 14px minmax(220px, 1fr); + align-content: start; min-width: 0; min-height: 0; - gap: 8px; + gap: 0; + } + + .left-panel-resize-handle { + min-height: 14px; + margin: 0; + } + + .left-panel-resize-placeholder { + display: block; + min-height: 0; + min-width: 0; + } + + .left-sidebar.branch-collapsed { + grid-template-rows: auto 0 minmax(150px, var(--stash-panel-height, 190px)) 14px minmax(220px, 1fr); + } + + .left-sidebar.explorer-collapsed { + grid-template-rows: minmax(180px, var(--branch-panel-height, 260px)) 14px minmax(150px, var(--stash-panel-height, 190px)) 0 auto; + } + + .left-sidebar.branch-collapsed.explorer-collapsed { + grid-template-rows: auto 0 minmax(150px, var(--stash-panel-height, 190px)) 0 auto; } .left-sidebar:has(.stash-panel.collapsed) { - grid-template-rows: minmax(170px, 0.85fr) auto minmax(220px, 1.15fr); + grid-template-rows: minmax(180px, var(--branch-panel-height, 260px)) 14px auto 0 minmax(220px, 1fr); + } + + .left-sidebar.branch-collapsed:has(.stash-panel.collapsed) { + grid-template-rows: auto 0 auto 0 minmax(220px, 1fr); + } + + .left-sidebar.explorer-collapsed:has(.stash-panel.collapsed) { + grid-template-rows: minmax(180px, var(--branch-panel-height, 260px)) 14px auto 0 auto; + } + + .left-sidebar.branch-collapsed.explorer-collapsed:has(.stash-panel.collapsed) { + grid-template-rows: auto 0 auto 0 auto; + align-content: start; } /* --- Main panel --- */ @@ -1474,6 +1531,9 @@ gap: 6px; } .branch-panel { position: relative; } + .branch-panel.collapsed { + grid-template-rows: auto; + } .branch-create-toggle { width: 26px; @@ -1747,6 +1807,9 @@ /* --- Explorer --- */ .explorer-panel { position: relative; } + .explorer-panel.collapsed { + grid-template-rows: auto; + } .explorer-head-actions { display: flex; align-items: center; gap: 5px; flex: 0 0 auto; } .explorer-bulk-button { @@ -2127,8 +2190,75 @@ background: rgba(65,209,255,0.08); color: var(--color-ink); } + .file-history-panel.collapsed { + display: grid; + grid-template-rows: 1fr; + overflow: hidden; + } + .file-history-rail-toggle { + display: grid; + grid-template-rows: auto minmax(0, 1fr) auto; + justify-items: center; + gap: 8px; + width: 100%; + height: 100%; + min-width: 0; + min-height: 0; + padding: 12px 0; + border: 0; + border-radius: 0; + color: var(--color-ink-dim); + background: linear-gradient(180deg, rgba(100,108,255,0.12), rgba(65,209,255,0.04)); + } + .file-history-rail-toggle:hover:not(:disabled) { + color: var(--color-ink); + background: linear-gradient(180deg, rgba(100,108,255,0.18), rgba(65,209,255,0.08)); + } + .file-history-rail-toggle span { + writing-mode: vertical-rl; + text-orientation: mixed; + overflow: hidden; + color: currentColor; + font-size: 11px; + font-weight: 800; + letter-spacing: 0.04em; + text-transform: uppercase; + white-space: nowrap; + } + .file-history-rail-toggle small { + display: inline-grid; + place-items: center; + min-width: 22px; + min-height: 22px; + border-radius: 999px; + color: var(--color-ink); + background: rgba(94,110,156,0.18); + font-size: 11px; + font-weight: 800; + } .file-history-head { align-items: flex-start; } .file-history-heading { min-width: 0; flex: 1 1 auto; overflow: hidden; } + .file-history-head-actions { + display: flex; + align-items: center; + flex: 0 0 auto; + gap: 6px; + } + .file-history-toggle { + width: 26px; + min-width: 26px; + min-height: 26px; + padding: 0; + border-color: rgba(65,209,255,0.2); + border-radius: 7px; + color: var(--color-ink-dim); + background: rgba(65,209,255,0.06); + } + .file-history-toggle:hover:not(:disabled) { + border-color: rgba(65,209,255,0.45); + color: #ffffff; + background: rgba(65,209,255,0.13); + } .section-head .file-history-name { display: -webkit-box; max-height: 2.4em; @@ -4261,27 +4391,52 @@ /* --- Responsive breakpoints --- */ @media (min-width: 1800px) { - .workspace { grid-template-columns: 320px minmax(0, 1fr) minmax(560px, var(--history-aside-width, 680px)); } + .workspace { grid-template-columns: minmax(220px, var(--left-sidebar-width, 320px)) 8px minmax(0, 1fr) 8px minmax(560px, var(--history-aside-width, 680px)); } } @media (max-width: 1400px) { - .workspace { grid-template-columns: clamp(200px, 17vw, 265px) minmax(0, 1fr) minmax(540px, var(--history-aside-width, 580px)); } + .workspace { grid-template-columns: minmax(200px, var(--left-sidebar-width, 265px)) 8px minmax(0, 1fr) 8px minmax(540px, var(--history-aside-width, 580px)); } } /* Stack CommitPanel below StatusPanel; history panels stay side by side */ @media (max-width: 1100px) { - .workspace { grid-template-columns: clamp(185px, 16vw, 220px) minmax(0, 1fr) minmax(500px, var(--history-aside-width, 560px)); } + .workspace { grid-template-columns: minmax(185px, var(--left-sidebar-width, 220px)) 8px minmax(0, 1fr) 8px minmax(500px, var(--history-aside-width, 560px)); } .top-section { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) 14px var(--commit-panel-height, 190px); } } /* Compact: stack history panels vertically, narrow sidebars */ @media (max-width: 960px) { - .workspace { grid-template-columns: 180px minmax(0, 1fr) 300px; gap: 6px; } - .history-aside { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1.3fr) minmax(0, 0.7fr); } - .history-resize-handle { display: none; } + .workspace { grid-template-columns: 180px 8px minmax(0, 1fr) 0 300px; row-gap: 6px; } + .history-aside { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1.3fr) minmax(0, 0.7fr); row-gap: 6px; } + .history-aside.file-history-collapsed { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr) 42px; } + .history-resize-handle, + .file-history-resize-handle { display: none; } .shell-body { gap: 6px; } - .left-sidebar { gap: 6px; grid-template-rows: minmax(150px, 0.7fr) minmax(145px, 0.55fr) minmax(190px, 1fr); } - .left-sidebar:has(.stash-panel.collapsed) { grid-template-rows: minmax(150px, 0.8fr) auto minmax(190px, 1.1fr); } + .left-sidebar { + grid-template-rows: minmax(180px, var(--branch-panel-height, 230px)) 14px minmax(150px, var(--stash-panel-height, 170px)) 14px minmax(220px, 1fr); + } + .left-sidebar:has(.stash-panel.collapsed) { + grid-template-rows: minmax(180px, var(--branch-panel-height, 230px)) 14px auto 0 minmax(220px, 1fr); + } + .left-sidebar.branch-collapsed { + grid-template-rows: auto 0 minmax(150px, var(--stash-panel-height, 170px)) 14px minmax(220px, 1fr); + } + .left-sidebar.explorer-collapsed { + grid-template-rows: minmax(180px, var(--branch-panel-height, 230px)) 14px minmax(150px, var(--stash-panel-height, 170px)) 0 auto; + } + .left-sidebar.branch-collapsed:has(.stash-panel.collapsed) { + grid-template-rows: auto 0 auto 0 minmax(220px, 1fr); + } + .left-sidebar.explorer-collapsed:has(.stash-panel.collapsed) { + grid-template-rows: minmax(180px, var(--branch-panel-height, 230px)) 14px auto 0 auto; + } + .left-sidebar.branch-collapsed.explorer-collapsed { + grid-template-rows: auto 0 minmax(150px, var(--stash-panel-height, 170px)) 0 auto; + } + .left-sidebar.branch-collapsed.explorer-collapsed:has(.stash-panel.collapsed) { + grid-template-rows: auto 0 auto 0 auto; + align-content: start; + } .section-head { min-height: 40px; padding: 6px 10px; } .repo-summary { height: 40px; padding: 0 10px; } .repo-branch { max-width: 160px; } @@ -4297,9 +4452,35 @@ body { overflow: auto; } .shell-body { min-height: 100%; gap: 6px; } .workspace { grid-template-columns: 1fr; gap: 6px; } + .left-sidebar-resize-handle { display: none; } .history-aside { grid-template-columns: 1fr; grid-template-rows: minmax(200px, 1fr) minmax(150px, 0.5fr); min-height: 380px; } - .left-sidebar { grid-template-rows: minmax(180px, 0.9fr) minmax(150px, 0.55fr) minmax(220px, 1fr); min-height: 560px; } - .left-sidebar:has(.stash-panel.collapsed) { grid-template-rows: minmax(180px, 1fr) auto minmax(220px, 1.1fr); } + .history-aside.file-history-collapsed { grid-template-rows: minmax(200px, 1fr) 42px; } + .left-sidebar { + grid-template-rows: minmax(180px, var(--branch-panel-height, 240px)) 14px minmax(150px, var(--stash-panel-height, 180px)) 14px minmax(220px, 1fr); + min-height: 560px; + } + .left-sidebar:has(.stash-panel.collapsed) { + grid-template-rows: minmax(180px, var(--branch-panel-height, 240px)) 14px auto 0 minmax(220px, 1fr); + } + .left-sidebar.branch-collapsed { + grid-template-rows: auto 0 minmax(150px, var(--stash-panel-height, 180px)) 14px minmax(220px, 1fr); + } + .left-sidebar.explorer-collapsed { + grid-template-rows: minmax(120px, var(--branch-panel-height, 240px)) 14px minmax(150px, var(--stash-panel-height, 180px)) 0 auto; + } + .left-sidebar.branch-collapsed:has(.stash-panel.collapsed) { + grid-template-rows: auto 0 auto 0 minmax(0, 1fr); + } + .left-sidebar.explorer-collapsed:has(.stash-panel.collapsed) { + grid-template-rows: minmax(120px, var(--branch-panel-height, 240px)) 14px auto 0 auto; + } + .left-sidebar.branch-collapsed.explorer-collapsed { + grid-template-rows: auto 0 minmax(150px, var(--stash-panel-height, 180px)) 0 auto; + } + .left-sidebar.branch-collapsed.explorer-collapsed:has(.stash-panel.collapsed) { + grid-template-rows: auto 0 auto 0 auto; + align-content: start; + } .top-section { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) 14px var(--commit-panel-height, 190px); } .repo-form { grid-template-columns: 1fr; } .repo-tabbar { grid-template-columns: minmax(0, 1fr) auto; } @@ -4322,7 +4503,7 @@ .dialog-body { grid-template-columns: 1fr; grid-template-rows: minmax(120px, 0.4fr) minmax(0, 1fr); } .compare-dialog .dialog-body { grid-template-columns: 1fr; grid-template-rows: minmax(120px, 0.4fr) minmax(0, 1fr); } .global-search-options { grid-template-columns: 1fr; } - .file-search-split { grid-template-columns: 1fr; grid-template-rows: minmax(150px, 0.9fr) minmax(220px, 1fr); } + .file-search-split { grid-template-columns: 1fr; grid-template-rows: minmax(150px, 0.9fr) minmax(0, 1fr); } .file-search-column { border-right: none; border-bottom: 1px solid var(--color-border-subtle); } .file-search-hit { grid-template-columns: auto minmax(0, 1fr); align-items: start; } .file-search-hit .status-badge, diff --git a/src/lib/components/BranchPanel.svelte b/src/lib/components/BranchPanel.svelte index dcec2f0..c57db06 100644 --- a/src/lib/components/BranchPanel.svelte +++ b/src/lib/components/BranchPanel.svelte @@ -57,6 +57,8 @@ onCreateTag: (name: string, message: string) => void | Promise; onDeleteTag: (tag: GitTag) => void | Promise; onPushTag: (tag: GitTag) => void | Promise; + collapsed?: boolean; + onToggleCollapsed?: () => void; } let { @@ -75,6 +77,8 @@ onCreateTag = () => {}, onDeleteTag = () => {}, onPushTag = () => {}, + collapsed = false, + onToggleCollapsed = () => {}, }: Props = $props(); let localOpen = $state(true); @@ -355,7 +359,7 @@ -
+
Branches @@ -373,10 +377,26 @@
- {#if !hasRepository} + {#if collapsed} + + {:else if !hasRepository}

Open a repository to list branches.

{:else if branches.length === 0 && tags.length === 0}

No branches returned.

diff --git a/src/lib/components/ExplorerPanel.svelte b/src/lib/components/ExplorerPanel.svelte index 5c6f9bc..377c5a6 100644 --- a/src/lib/components/ExplorerPanel.svelte +++ b/src/lib/components/ExplorerPanel.svelte @@ -38,6 +38,8 @@ onSelectNode: (node: ExplorerNode) => void; onOpenFile: (node: ExplorerNode) => void; onBlame: (node: ExplorerNode) => void; + collapsed?: boolean; + onToggleCollapsed?: () => void; } let { @@ -53,6 +55,8 @@ onSelectNode = () => {}, onOpenFile = () => {}, onBlame = () => {}, + collapsed = false, + onToggleCollapsed = () => {}, }: Props = $props(); let contextNode = $state(null); @@ -203,7 +207,7 @@ -
+
Explorer @@ -231,10 +235,26 @@
- {#if !hasRepository} + {#if collapsed} + + {:else if !hasRepository}

Open a repository to browse files.

{:else if repoFiles.length === 0}

No files returned.

diff --git a/src/lib/components/FileHistoryPanel.svelte b/src/lib/components/FileHistoryPanel.svelte index dc4c8d4..5cf3b1d 100644 --- a/src/lib/components/FileHistoryPanel.svelte +++ b/src/lib/components/FileHistoryPanel.svelte @@ -1,5 +1,5 @@ -
+
+ {#if collapsed} + + {:else}
{selectedExplorerLabel} @@ -85,7 +105,19 @@ use:pathTooltip={selectedExplorerPath} >{selectedExplorerPath ? fileName(selectedExplorerPath) : "No file"}
- {fileHistory.length} +
+ {fileHistory.length} + +
{#if !hasRepository} @@ -136,4 +168,5 @@ {/each} {/if} + {/if}
diff --git a/src/lib/components/StashPanel.svelte b/src/lib/components/StashPanel.svelte index de3f690..a61c827 100644 --- a/src/lib/components/StashPanel.svelte +++ b/src/lib/components/StashPanel.svelte @@ -11,6 +11,8 @@ onApply: (stash: GitStash) => void; onPop: (stash: GitStash) => void; onDrop: (stash: GitStash) => void; + collapsed?: boolean; + onToggleCollapsed?: () => void; } let { @@ -22,11 +24,12 @@ onApply = () => {}, onPop = () => {}, onDrop = () => {}, + collapsed = false, + onToggleCollapsed = () => {}, }: Props = $props(); let message = $state(""); let includeUntracked = $state(true); - let open = $state(false); function submitPush() { onPush(message, includeUntracked); @@ -38,31 +41,32 @@ } -
+
Stash

Shelved changes

+ {stashes.length} - {stashes.length}
- {#if !open} + {#if collapsed} {:else if !hasRepository}
No repository loaded.