feat(ui): enhance panel resizing and state persistence
This update introduces functionality for resizing various panels in the UI, including the left sidebar, branch panel, stash panel, and file history. It also adds state persistence for panel sizes and collapsed states using local storage, improving user experience by maintaining preferences across sessions. - Implemented resizing functionality for multiple UI panels - Added local storage support for panel dimensions and collapsed states - Enhanced accessibility features for panel controls
This commit is contained in:
+437
-21
@@ -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 @@
|
||||
</section>
|
||||
{:else}
|
||||
<!-- Workspace -->
|
||||
<section class="workspace" aria-label="Git workspace" style="--history-aside-width: {historyAsideWidth}px;">
|
||||
<section
|
||||
class="workspace"
|
||||
aria-label="Git workspace"
|
||||
style="--left-sidebar-width: {leftSidebarWidth}px; --history-aside-width: {historyAsideWidth}px; --file-history-width: {fileHistoryWidth}px;"
|
||||
>
|
||||
|
||||
<!-- Left sidebar: branches + explorer -->
|
||||
<aside class="left-sidebar" aria-label="Repository navigation">
|
||||
<aside
|
||||
class="left-sidebar"
|
||||
class:branch-collapsed={branchPanelCollapsed}
|
||||
class:stash-collapsed={stashPanelCollapsed}
|
||||
class:explorer-collapsed={explorerPanelCollapsed}
|
||||
class:all-collapsed={allLeftPanelsCollapsed}
|
||||
aria-label="Repository navigation"
|
||||
style="--branch-panel-height: {leftBranchPanelHeight}px; --stash-panel-height: {leftStashPanelHeight}px; grid-template-rows: {leftSidebarRows};"
|
||||
>
|
||||
<BranchPanel
|
||||
{branches}
|
||||
{localBranches}
|
||||
@@ -3251,7 +3578,31 @@
|
||||
onCreateTag={createNewTag}
|
||||
onDeleteTag={deleteLocalTag}
|
||||
onPushTag={pushLocalTag}
|
||||
collapsed={branchPanelCollapsed}
|
||||
onToggleCollapsed={toggleBranchPanelCollapsed}
|
||||
/>
|
||||
{#if !branchPanelCollapsed}
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<div
|
||||
class="panel-resize-handle left-panel-resize-handle branch-panel-resize-handle"
|
||||
class:resizing={resizingLeftBranchPanel}
|
||||
role="separator"
|
||||
aria-orientation="horizontal"
|
||||
aria-label="Resize branches panel height"
|
||||
aria-valuenow={leftBranchPanelHeight}
|
||||
aria-valuemin={LEFT_BRANCH_PANEL_MIN_HEIGHT}
|
||||
aria-valuemax={LEFT_BRANCH_PANEL_MAX_HEIGHT}
|
||||
tabindex="0"
|
||||
onpointerdown={startLeftBranchPanelResize}
|
||||
onpointermove={onLeftBranchPanelResizeMove}
|
||||
onpointerup={endLeftBranchPanelResize}
|
||||
onpointercancel={endLeftBranchPanelResize}
|
||||
onkeydown={onLeftBranchPanelResizeKeydown}
|
||||
></div>
|
||||
{:else}
|
||||
<span class="left-panel-resize-placeholder" aria-hidden="true"></span>
|
||||
{/if}
|
||||
<StashPanel
|
||||
{stashes}
|
||||
changedCount={changedFiles.length}
|
||||
@@ -3261,7 +3612,31 @@
|
||||
onApply={applyStashEntry}
|
||||
onPop={popStashEntry}
|
||||
onDrop={dropStashEntry}
|
||||
collapsed={stashPanelCollapsed}
|
||||
onToggleCollapsed={toggleStashPanelCollapsed}
|
||||
/>
|
||||
{#if !explorerPanelCollapsed && (!stashPanelCollapsed || !branchPanelCollapsed)}
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<div
|
||||
class="panel-resize-handle left-panel-resize-handle stash-panel-resize-handle"
|
||||
class:resizing={resizingLeftStashPanel}
|
||||
role="separator"
|
||||
aria-orientation="horizontal"
|
||||
aria-label={stashPanelCollapsed ? "Resize files panel space" : "Resize stash and explorer panels"}
|
||||
aria-valuenow={stashPanelCollapsed ? leftBranchPanelHeight : leftStashPanelHeight}
|
||||
aria-valuemin={stashPanelCollapsed ? LEFT_BRANCH_PANEL_MIN_HEIGHT : LEFT_STASH_PANEL_MIN_HEIGHT}
|
||||
aria-valuemax={stashPanelCollapsed ? LEFT_BRANCH_PANEL_MAX_HEIGHT : LEFT_STASH_PANEL_MAX_HEIGHT}
|
||||
tabindex="0"
|
||||
onpointerdown={startLeftStashPanelResize}
|
||||
onpointermove={onLeftStashPanelResizeMove}
|
||||
onpointerup={endLeftStashPanelResize}
|
||||
onpointercancel={endLeftStashPanelResize}
|
||||
onkeydown={onLeftStashPanelResizeKeydown}
|
||||
></div>
|
||||
{:else}
|
||||
<span class="left-panel-resize-placeholder" aria-hidden="true"></span>
|
||||
{/if}
|
||||
<ExplorerPanel
|
||||
{repoFiles}
|
||||
{expandedExplorerPaths}
|
||||
@@ -3275,8 +3650,28 @@
|
||||
onSelectNode={selectExplorerNode}
|
||||
onOpenFile={openFileFromExplorer}
|
||||
onBlame={openBlame}
|
||||
collapsed={explorerPanelCollapsed}
|
||||
onToggleCollapsed={toggleExplorerPanelCollapsed}
|
||||
/>
|
||||
</aside>
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<div
|
||||
class="left-sidebar-resize-handle"
|
||||
class:resizing={resizingLeftSidebar}
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label="Resize navigation sidebar width"
|
||||
aria-valuenow={leftSidebarWidth}
|
||||
aria-valuemin={LEFT_SIDEBAR_MIN_WIDTH}
|
||||
aria-valuemax={LEFT_SIDEBAR_MAX_WIDTH}
|
||||
tabindex="0"
|
||||
onpointerdown={startLeftSidebarResize}
|
||||
onpointermove={onLeftSidebarResizeMove}
|
||||
onpointerup={endLeftSidebarResize}
|
||||
onpointercancel={endLeftSidebarResize}
|
||||
onkeydown={onLeftSidebarResizeKeydown}
|
||||
></div>
|
||||
|
||||
<!-- Center: summary + status + commit -->
|
||||
<section class="main-panel" aria-label="Repository status">
|
||||
@@ -3353,26 +3748,27 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<div
|
||||
class="history-resize-handle"
|
||||
class:resizing={resizingHistoryAside}
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label="Resize commit history width"
|
||||
aria-valuenow={historyAsideWidth}
|
||||
aria-valuemin={HISTORY_ASIDE_MIN_WIDTH}
|
||||
aria-valuemax={HISTORY_ASIDE_MAX_WIDTH}
|
||||
tabindex="0"
|
||||
onpointerdown={startHistoryAsideResize}
|
||||
onpointermove={onHistoryAsideResizeMove}
|
||||
onpointerup={endHistoryAsideResize}
|
||||
onpointercancel={endHistoryAsideResize}
|
||||
onkeydown={onHistoryAsideResizeKeydown}
|
||||
></div>
|
||||
|
||||
<!-- Right sidebar: commit graph + file history -->
|
||||
<aside class="history-aside" aria-label="Commit history">
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<div
|
||||
class="history-resize-handle"
|
||||
class:resizing={resizingHistoryAside}
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label="Resize history panel width"
|
||||
aria-valuenow={historyAsideWidth}
|
||||
aria-valuemin={HISTORY_ASIDE_MIN_WIDTH}
|
||||
aria-valuemax={HISTORY_ASIDE_MAX_WIDTH}
|
||||
tabindex="0"
|
||||
onpointerdown={startHistoryAsideResize}
|
||||
onpointermove={onHistoryAsideResizeMove}
|
||||
onpointerup={endHistoryAsideResize}
|
||||
onpointercancel={endHistoryAsideResize}
|
||||
onkeydown={onHistoryAsideResizeKeydown}
|
||||
></div>
|
||||
<aside class="history-aside" class:file-history-collapsed={fileHistoryCollapsed} aria-label="Commit history">
|
||||
<HistoryPanel
|
||||
{commits}
|
||||
{localBranchNames}
|
||||
@@ -3392,6 +3788,24 @@
|
||||
expandedCommitHashes = next;
|
||||
}}
|
||||
/>
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<div
|
||||
class="file-history-resize-handle"
|
||||
class:resizing={resizingFileHistory}
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label="Resize file history width"
|
||||
aria-valuenow={fileHistoryWidth}
|
||||
aria-valuemin={FILE_HISTORY_MIN_WIDTH}
|
||||
aria-valuemax={FILE_HISTORY_MAX_WIDTH}
|
||||
tabindex={fileHistoryCollapsed ? -1 : 0}
|
||||
onpointerdown={startFileHistoryResize}
|
||||
onpointermove={onFileHistoryResizeMove}
|
||||
onpointerup={endFileHistoryResize}
|
||||
onpointercancel={endFileHistoryResize}
|
||||
onkeydown={onFileHistoryResizeKeydown}
|
||||
></div>
|
||||
<FileHistoryPanel
|
||||
{fileHistory}
|
||||
{selectedExplorerPath}
|
||||
@@ -3401,6 +3815,8 @@
|
||||
isLoading={fileHistoryLoading}
|
||||
onDiff={diffSelectedFileFromCommit}
|
||||
onRestore={restoreSelectedFileFromCommit}
|
||||
collapsed={fileHistoryCollapsed}
|
||||
onToggleCollapsed={toggleFileHistoryCollapsed}
|
||||
/>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
+217
-36
@@ -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,
|
||||
|
||||
@@ -57,6 +57,8 @@
|
||||
onCreateTag: (name: string, message: string) => void | Promise<void>;
|
||||
onDeleteTag: (tag: GitTag) => void | Promise<void>;
|
||||
onPushTag: (tag: GitTag) => void | Promise<void>;
|
||||
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 @@
|
||||
|
||||
<svelte:window on:click={closeAllContextMenus} on:keydown={handleWindowKeydown} on:contextmenu|capture={closeAllContextMenus} />
|
||||
|
||||
<section bind:this={panelElement} class="panel branch-panel grid grid-rows-[auto_1fr] overflow-hidden" aria-label="Branches">
|
||||
<section bind:this={panelElement} class="panel branch-panel grid grid-rows-[auto_1fr] overflow-hidden" class:collapsed aria-label="Branches">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<span class="eyebrow">Branches</span>
|
||||
@@ -373,10 +377,26 @@
|
||||
<Plus size={14} aria-hidden="true" />
|
||||
</button>
|
||||
<span class="pill pill-count">{branches.length}</span>
|
||||
<button
|
||||
class="branch-create-toggle panel-collapse-toggle"
|
||||
type="button"
|
||||
onclick={onToggleCollapsed}
|
||||
aria-expanded={!collapsed}
|
||||
title={collapsed ? "Expand branches" : "Collapse branches"}
|
||||
aria-label={collapsed ? "Expand branches panel" : "Collapse branches panel"}
|
||||
>
|
||||
{#if collapsed}
|
||||
<ChevronRight size={14} aria-hidden="true" />
|
||||
{:else}
|
||||
<ChevronDown size={14} aria-hidden="true" />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !hasRepository}
|
||||
{#if collapsed}
|
||||
<!-- collapsed -->
|
||||
{:else if !hasRepository}
|
||||
<p class="blank-state">Open a repository to list branches.</p>
|
||||
{:else if branches.length === 0 && tags.length === 0}
|
||||
<p class="blank-state">No branches returned.</p>
|
||||
|
||||
@@ -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<ExplorerNode | null>(null);
|
||||
@@ -203,7 +207,7 @@
|
||||
|
||||
<svelte:window on:click={closeFileContextMenu} on:keydown={handleWindowKeydown} on:contextmenu|capture={closeFileContextMenu} />
|
||||
|
||||
<section class="panel explorer-panel grid grid-rows-[auto_1fr] overflow-hidden" aria-label="File explorer">
|
||||
<section class="panel explorer-panel grid grid-rows-[auto_1fr] overflow-hidden" class:collapsed aria-label="File explorer">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<span class="eyebrow">Explorer</span>
|
||||
@@ -231,10 +235,26 @@
|
||||
<Folder size={14} aria-hidden="true" />
|
||||
</button>
|
||||
<span class="pill pill-count">{repoFiles.length}</span>
|
||||
<button
|
||||
class="explorer-bulk-button panel-collapse-toggle"
|
||||
type="button"
|
||||
onclick={onToggleCollapsed}
|
||||
aria-expanded={!collapsed}
|
||||
title={collapsed ? "Expand file explorer" : "Collapse file explorer"}
|
||||
aria-label={collapsed ? "Expand file explorer panel" : "Collapse file explorer panel"}
|
||||
>
|
||||
{#if collapsed}
|
||||
<ChevronRight size={14} aria-hidden="true" />
|
||||
{:else}
|
||||
<ChevronDown size={14} aria-hidden="true" />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !hasRepository}
|
||||
{#if collapsed}
|
||||
<!-- collapsed -->
|
||||
{:else if !hasRepository}
|
||||
<p class="blank-state">Open a repository to browse files.</p>
|
||||
{:else if repoFiles.length === 0}
|
||||
<p class="blank-state">No files returned.</p>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { GitCompare, History, RotateCcw } from "@lucide/svelte";
|
||||
import { ChevronLeft, ChevronRight, GitCompare, History, RotateCcw } from "@lucide/svelte";
|
||||
import iconUrl from "../../../src-tauri/icons/icon.png";
|
||||
import type { GitCommit } from "../types";
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
isLoading?: boolean;
|
||||
onDiff: (commit: GitCommit) => void;
|
||||
onRestore: (commit: GitCommit) => void;
|
||||
collapsed?: boolean;
|
||||
onToggleCollapsed?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -23,6 +25,8 @@
|
||||
isLoading = false,
|
||||
onDiff = () => {},
|
||||
onRestore = () => {},
|
||||
collapsed = false,
|
||||
onToggleCollapsed = () => {},
|
||||
}: Props = $props();
|
||||
|
||||
function formatCommitDate(value: string): string {
|
||||
@@ -76,7 +80,23 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="panel grid grid-rows-[auto_1fr] overflow-hidden" aria-label="Selected file history">
|
||||
<section class="panel file-history-panel grid grid-rows-[auto_1fr] overflow-hidden" class:collapsed aria-label="Selected file history">
|
||||
{#if collapsed}
|
||||
<button
|
||||
class="file-history-rail-toggle"
|
||||
type="button"
|
||||
onclick={onToggleCollapsed}
|
||||
aria-expanded={!collapsed}
|
||||
title="Expand file history"
|
||||
aria-label="Expand file history panel"
|
||||
>
|
||||
<ChevronLeft size={16} aria-hidden="true" />
|
||||
<span>File history</span>
|
||||
{#if selectedExplorerPath}
|
||||
<small>{fileHistory.length}</small>
|
||||
{/if}
|
||||
</button>
|
||||
{:else}
|
||||
<div class="section-head file-history-head">
|
||||
<div class="file-history-heading">
|
||||
<span class="eyebrow">{selectedExplorerLabel}</span>
|
||||
@@ -85,7 +105,19 @@
|
||||
use:pathTooltip={selectedExplorerPath}
|
||||
>{selectedExplorerPath ? fileName(selectedExplorerPath) : "No file"}</h2>
|
||||
</div>
|
||||
<span class="pill pill-count file-history-count">{fileHistory.length}</span>
|
||||
<div class="file-history-head-actions">
|
||||
<span class="pill pill-count file-history-count">{fileHistory.length}</span>
|
||||
<button
|
||||
class="file-history-toggle panel-collapse-toggle"
|
||||
type="button"
|
||||
onclick={onToggleCollapsed}
|
||||
aria-expanded={!collapsed}
|
||||
title="Collapse file history"
|
||||
aria-label="Collapse file history panel"
|
||||
>
|
||||
<ChevronRight size={14} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !hasRepository}
|
||||
@@ -136,4 +168,5 @@
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
@@ -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 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="panel stash-panel overflow-hidden" class:collapsed={!open} aria-label="Git stash">
|
||||
<section class="panel stash-panel overflow-hidden" class:collapsed aria-label="Git stash">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<span class="eyebrow">Stash</span>
|
||||
<h2 class="mt-0.5 text-ink text-base font-bold leading-tight">Shelved changes</h2>
|
||||
</div>
|
||||
<div class="stash-head-actions">
|
||||
<span class="pill pill-count">{stashes.length}</span>
|
||||
<button
|
||||
class="stash-toggle"
|
||||
class="stash-toggle panel-collapse-toggle"
|
||||
type="button"
|
||||
onclick={() => { open = !open; }}
|
||||
aria-expanded={open}
|
||||
title={open ? "Collapse stash panel" : "Expand stash panel"}
|
||||
onclick={onToggleCollapsed}
|
||||
aria-expanded={!collapsed}
|
||||
title={collapsed ? "Expand stash panel" : "Collapse stash panel"}
|
||||
aria-label={collapsed ? "Expand stash panel" : "Collapse stash panel"}
|
||||
>
|
||||
{#if open}
|
||||
<ChevronDown size={14} aria-hidden="true" />
|
||||
{:else}
|
||||
{#if collapsed}
|
||||
<ChevronRight size={14} aria-hidden="true" />
|
||||
{:else}
|
||||
<ChevronDown size={14} aria-hidden="true" />
|
||||
{/if}
|
||||
</button>
|
||||
<span class="pill pill-count">{stashes.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !open}
|
||||
{#if collapsed}
|
||||
<!-- collapsed -->
|
||||
{:else if !hasRepository}
|
||||
<div class="blank-state">No repository loaded.</div>
|
||||
|
||||
Reference in New Issue
Block a user