Opt/app layout #18
+25
-2
@@ -1639,6 +1639,7 @@
|
||||
.branch-actions .btn-sm { min-height: 24px; padding: 0 6px; font-size: 11px; }
|
||||
|
||||
.branch-context-menu,
|
||||
.history-context-menu,
|
||||
.explorer-context-menu {
|
||||
z-index: 120;
|
||||
display: grid;
|
||||
@@ -1651,10 +1652,12 @@
|
||||
box-shadow: 0 18px 50px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.branch-context-menu { position: absolute; }
|
||||
.branch-context-menu,
|
||||
.history-context-menu { position: absolute; }
|
||||
.explorer-context-menu { position: fixed; }
|
||||
|
||||
.branch-context-menu button,
|
||||
.history-context-menu button,
|
||||
.explorer-context-menu button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1673,13 +1676,15 @@
|
||||
}
|
||||
|
||||
.branch-context-menu button:hover:not(:disabled),
|
||||
.history-context-menu button:hover:not(:disabled),
|
||||
.explorer-context-menu button:hover:not(:disabled) {
|
||||
border-color: var(--color-border-subtle);
|
||||
background: rgba(255,255,255,0.06);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.branch-context-menu .menu-separator {
|
||||
.branch-context-menu .menu-separator,
|
||||
.history-context-menu .menu-separator {
|
||||
height: 1px;
|
||||
margin: 4px 3px;
|
||||
background: var(--color-border-subtle);
|
||||
@@ -1696,6 +1701,7 @@
|
||||
}
|
||||
|
||||
.branch-context-menu button:disabled,
|
||||
.history-context-menu button:disabled,
|
||||
.explorer-context-menu button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.48;
|
||||
@@ -1817,6 +1823,7 @@
|
||||
|
||||
/* --- Commit history --- */
|
||||
|
||||
.history-panel { position: relative; }
|
||||
.history-list { min-width: 0; padding: 6px; overflow: auto; }
|
||||
|
||||
.commit-row { display: grid; min-width: 0; gap: 8px; padding: 10px; border: 1px solid var(--color-border-subtle); border-radius: 8px; background: var(--color-surface-raised); transition: border-color 120ms; }
|
||||
@@ -2017,6 +2024,22 @@
|
||||
}
|
||||
.commit-action-buttons { display: flex; flex: 0 0 auto; align-items: center; gap: 5px; }
|
||||
.commit-action-buttons button { flex: 0 0 auto; white-space: nowrap; }
|
||||
.commit-menu-button {
|
||||
width: 28px;
|
||||
min-width: 28px;
|
||||
min-height: 26px;
|
||||
padding: 0;
|
||||
border-color: rgba(94,110,156,0.16);
|
||||
border-radius: 7px;
|
||||
background: rgba(255,255,255,0.035);
|
||||
color: var(--color-ink-dim);
|
||||
}
|
||||
.commit-menu-button:hover:not(:disabled),
|
||||
.commit-menu-button[aria-expanded="true"] {
|
||||
border-color: rgba(65,209,255,0.28);
|
||||
background: rgba(65,209,255,0.08);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.file-history-head { align-items: flex-start; }
|
||||
.file-history-heading { min-width: 0; flex: 1 1 auto; overflow: hidden; }
|
||||
.section-head .file-history-name {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ChevronDown, ChevronRight, GitBranch, GitMerge, RotateCcw, X } from "@lucide/svelte";
|
||||
import { ChevronDown, ChevronRight, EllipsisVertical, GitBranch, GitMerge, RotateCcw, X } from "@lucide/svelte";
|
||||
import type { FileStatusKind, GitCommit, GitCommitFile } from "../types";
|
||||
|
||||
interface GraphSegment {
|
||||
@@ -60,6 +60,10 @@
|
||||
let branchDialogOpen = $state(false);
|
||||
let userAdjustedBranchFilter = $state(false);
|
||||
let lastDefaultFilterKey = $state("");
|
||||
let panelElement = $state<HTMLElement | null>(null);
|
||||
let contextCommit = $state<GitCommit | null>(null);
|
||||
let contextMenuX = $state(0);
|
||||
let contextMenuY = $state(0);
|
||||
|
||||
function laneColor(col: number): string {
|
||||
return GRAPH_COLORS[((col % GRAPH_COLORS.length) + GRAPH_COLORS.length) % GRAPH_COLORS.length];
|
||||
@@ -316,6 +320,54 @@
|
||||
}
|
||||
}
|
||||
|
||||
function openCommitActionMenu(event: MouseEvent, commit: GitCommit) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (isBusy) return;
|
||||
|
||||
if (contextCommit?.hash === commit.hash) {
|
||||
closeCommitContextMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
const panelRect = panelElement?.getBoundingClientRect();
|
||||
const buttonRect = event.currentTarget instanceof HTMLElement
|
||||
? event.currentTarget.getBoundingClientRect()
|
||||
: null;
|
||||
const rawX = panelRect && buttonRect ? buttonRect.right - panelRect.left - 184 : event.offsetX;
|
||||
const rawY = panelRect && buttonRect ? buttonRect.bottom - panelRect.top + 4 : event.offsetY;
|
||||
const maxX = Math.max(8, (panelRect?.width ?? window.innerWidth) - 192);
|
||||
const maxY = Math.max(8, (panelRect?.height ?? window.innerHeight) - 96);
|
||||
|
||||
contextCommit = commit;
|
||||
contextMenuX = Math.max(8, Math.min(rawX, maxX));
|
||||
contextMenuY = Math.max(8, Math.min(rawY, maxY));
|
||||
}
|
||||
|
||||
function closeCommitContextMenu() {
|
||||
contextCommit = null;
|
||||
}
|
||||
|
||||
async function createBranchFromContextCommit() {
|
||||
const commit = contextCommit;
|
||||
if (!commit || isBusy) return;
|
||||
closeCommitContextMenu();
|
||||
await onCreateBranchFromCommit(commit);
|
||||
}
|
||||
|
||||
async function restoreContextCommit() {
|
||||
const commit = contextCommit;
|
||||
if (!commit || isBusy) return;
|
||||
closeCommitContextMenu();
|
||||
await onRestoreCommit(commit);
|
||||
}
|
||||
|
||||
function handleWindowKeydown(event: KeyboardEvent) {
|
||||
if (event.key !== "Escape") return;
|
||||
closeCommitContextMenu();
|
||||
handleBranchDialogKeydown(event);
|
||||
}
|
||||
|
||||
function localBranchRefs(commit: GitCommit): string[] {
|
||||
const seen = new Set<string>();
|
||||
const labels: string[] = [];
|
||||
@@ -393,9 +445,9 @@
|
||||
let graphWidth = $derived(Math.max(Math.max(graph.columns, 1) * GRAPH_LANE + 18, 42));
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={handleBranchDialogKeydown} />
|
||||
<svelte:window onclick={closeCommitContextMenu} onkeydown={handleWindowKeydown} on:contextmenu|capture={closeCommitContextMenu} />
|
||||
|
||||
<section class="panel grid grid-rows-[auto_1fr] overflow-hidden" aria-label="Commit history">
|
||||
<section bind:this={panelElement} class="panel history-panel grid grid-rows-[auto_1fr] overflow-hidden" aria-label="Commit history">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<span class="eyebrow">History</span>
|
||||
@@ -433,7 +485,12 @@
|
||||
{@const row = graphRows[rowIndex]}
|
||||
{@const hoverBranchRefs = visibleBranchLabels(row?.branchLabels ?? [])}
|
||||
{@const otherRefs = visibleRefs(item)}
|
||||
<article class="commit-row graph-row" class:merge-row={item.parents.length > 1} class:root-row={item.parents.length === 0} class:tip-row={item.refs.length > 0}>
|
||||
<article
|
||||
class="commit-row graph-row"
|
||||
class:merge-row={item.parents.length > 1}
|
||||
class:root-row={item.parents.length === 0}
|
||||
class:tip-row={item.refs.length > 0}
|
||||
>
|
||||
<div class="graph-gutter" style={`width:${graphWidth}px`} aria-hidden="true">
|
||||
{#if row}
|
||||
<svg class="graph-svg" viewBox={`0 0 ${graphWidth} 100`} preserveAspectRatio="none">
|
||||
@@ -545,13 +602,17 @@
|
||||
<div class="commit-actions">
|
||||
<time class="commit-time" datetime={item.date}>{formatCommitDate(item.date)}</time>
|
||||
<div class="commit-action-buttons">
|
||||
<button class="btn-sm" type="button" onclick={() => onCreateBranchFromCommit(item)} disabled={isBusy} title="Create a new branch from this commit">
|
||||
<GitBranch size={15} aria-hidden="true" />
|
||||
Branch
|
||||
</button>
|
||||
<button class="btn-sm" type="button" onclick={() => onRestoreCommit(item)} disabled={isBusy} title="Bring this commit's files into your working tree as unstaged changes (no history is changed)">
|
||||
<RotateCcw size={15} aria-hidden="true" />
|
||||
Restore
|
||||
<button
|
||||
class="commit-menu-button"
|
||||
type="button"
|
||||
onclick={(event) => openCommitActionMenu(event, item)}
|
||||
disabled={isBusy}
|
||||
title="Commit actions"
|
||||
aria-label={`Actions for ${item.short_hash}`}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={contextCommit?.hash === item.hash}
|
||||
>
|
||||
<EllipsisVertical size={15} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -560,6 +621,25 @@
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if contextCommit}
|
||||
<div
|
||||
class="history-context-menu"
|
||||
style={`left: ${contextMenuX}px; top: ${contextMenuY}px;`}
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
aria-label={`Actions for ${contextCommit.short_hash}`}
|
||||
>
|
||||
<button type="button" role="menuitem" onclick={createBranchFromContextCommit} disabled={isBusy}>
|
||||
<GitBranch size={14} aria-hidden="true" />
|
||||
Branch
|
||||
</button>
|
||||
<button type="button" role="menuitem" onclick={restoreContextCommit} disabled={isBusy}>
|
||||
<RotateCcw size={14} aria-hidden="true" />
|
||||
Restore
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
{#if branchDialogOpen}
|
||||
|
||||
Reference in New Issue
Block a user