add new Context Menu for file Explorer
fix blocking UI when select the file in Status also select in the file History
This commit is contained in:
@@ -252,7 +252,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:click={closeBranchContextMenu} on:keydown={handleWindowKeydown} />
|
||||
<svelte:window on:click={closeBranchContextMenu} on:keydown={handleWindowKeydown} on:contextmenu|capture={closeBranchContextMenu} />
|
||||
|
||||
<section bind:this={panelElement} class="panel branch-panel grid grid-rows-[auto_1fr] overflow-hidden" aria-label="Branches">
|
||||
<div class="section-head">
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
FileVideo,
|
||||
Folder,
|
||||
FolderOpen,
|
||||
ExternalLink,
|
||||
Terminal,
|
||||
} from "@lucide/svelte";
|
||||
import { languageIconForPath } from "../languageIcons";
|
||||
@@ -34,6 +35,7 @@
|
||||
onExpandAllFolders: () => void;
|
||||
onCollapseAllFolders: () => void;
|
||||
onSelectNode: (node: ExplorerNode) => void;
|
||||
onOpenFile: (node: ExplorerNode) => void;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -47,8 +49,13 @@
|
||||
onExpandAllFolders = () => {},
|
||||
onCollapseAllFolders = () => {},
|
||||
onSelectNode = () => {},
|
||||
onOpenFile = () => {},
|
||||
}: Props = $props();
|
||||
|
||||
let contextNode = $state<ExplorerNode | null>(null);
|
||||
let contextMenuX = $state(0);
|
||||
let contextMenuY = $state(0);
|
||||
|
||||
function mergeExplorerStatus(current: FileStatusKind | null, next: FileStatusKind | null): FileStatusKind | null {
|
||||
if (!next) return current;
|
||||
if (!current) return next;
|
||||
@@ -154,12 +161,39 @@
|
||||
return "text";
|
||||
}
|
||||
|
||||
function openFileContextMenu(event: MouseEvent, node: ExplorerNode) {
|
||||
if (node.kind !== "file") return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
contextNode = node;
|
||||
contextMenuX = Math.max(8, Math.min(event.clientX + 2, window.innerWidth - 192));
|
||||
contextMenuY = Math.max(8, Math.min(event.clientY + 2, window.innerHeight - 56));
|
||||
}
|
||||
|
||||
function closeFileContextMenu() {
|
||||
contextNode = null;
|
||||
}
|
||||
|
||||
function openContextFile() {
|
||||
const node = contextNode;
|
||||
if (!node || node.kind !== "file") return;
|
||||
closeFileContextMenu();
|
||||
onOpenFile(node);
|
||||
}
|
||||
|
||||
function handleWindowKeydown(event: KeyboardEvent) {
|
||||
if (event.key === "Escape") closeFileContextMenu();
|
||||
}
|
||||
|
||||
let explorerTree = $derived(buildExplorerTree(repoFiles));
|
||||
let visibleNodes = $derived(flattenExplorerTree(explorerTree, expandedExplorerPaths));
|
||||
let hasFolders = $derived(explorerTree.some((node) => node.kind === "folder"));
|
||||
</script>
|
||||
|
||||
<section class="panel grid grid-rows-[auto_1fr] overflow-hidden" aria-label="File explorer">
|
||||
<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">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<span class="eyebrow">Explorer</span>
|
||||
@@ -264,6 +298,7 @@
|
||||
class="explorer-select"
|
||||
type="button"
|
||||
onclick={() => onSelectNode(node)}
|
||||
oncontextmenu={(event) => openFileContextMenu(event, node)}
|
||||
disabled={isBusy}
|
||||
title={`Show history for ${node.path}`}
|
||||
>
|
||||
@@ -279,4 +314,26 @@
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</section>
|
||||
|
||||
{#if contextNode}
|
||||
<div
|
||||
class="explorer-context-menu"
|
||||
style={`left: ${contextMenuX}px; top: ${contextMenuY}px;`}
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
aria-label={`Actions for ${contextNode.path}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
onclick={openContextFile}
|
||||
disabled={contextNode.status === "deleted"}
|
||||
title={contextNode.status === "deleted" ? "Deleted files cannot be revealed in Explorer" : "Reveal this file in Explorer"}
|
||||
>
|
||||
<ExternalLink size={14} aria-hidden="true" />
|
||||
Open in Explorer
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
hasRepository: boolean;
|
||||
isBusy: boolean;
|
||||
status: GitStatus | null;
|
||||
selectedFilePath: string;
|
||||
onSelectFile: (file: GitFileStatus) => void;
|
||||
onStage: (file: GitFileStatus) => void;
|
||||
onUnstage: (file: GitFileStatus) => void;
|
||||
onDiscard: (file: GitFileStatus, staged: boolean) => void;
|
||||
@@ -24,6 +26,8 @@
|
||||
hasRepository = false,
|
||||
isBusy = false,
|
||||
status = null,
|
||||
selectedFilePath = "",
|
||||
onSelectFile = () => {},
|
||||
onStage = () => {},
|
||||
onUnstage = () => {},
|
||||
onDiscard = () => {},
|
||||
@@ -102,9 +106,19 @@
|
||||
{:else}
|
||||
<div class="overflow-auto p-2">
|
||||
{#each changedFiles as file (`${file.old_path ?? ""}:${file.path}`)}
|
||||
<article class="file-row">
|
||||
<article
|
||||
class="file-row"
|
||||
class:selected={selectedFilePath === file.path}
|
||||
>
|
||||
<div class="file-title">
|
||||
<strong title={displayPath(file)}>{fileName(file)}</strong>
|
||||
<button
|
||||
class="file-title-button"
|
||||
type="button"
|
||||
onclick={() => onSelectFile(file)}
|
||||
title={`Select ${displayPath(file)} in Explorer`}
|
||||
>
|
||||
<strong>{fileName(file)}</strong>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="change-lanes">
|
||||
|
||||
Reference in New Issue
Block a user