Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface CommitItemProps {
selectedCommitHash: string | null;
onFileSelect: (file: ChangedFile, commitHash: string) => void;
viewMode: ChangesViewMode;
worktreePath?: string;
worktreePath: string;
isExpandedView?: boolean;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import {
LuTrash2,
LuUndo2,
} from "react-icons/lu";
import { electronTrpc } from "renderer/lib/electron-trpc";
import type { ChangeCategory, ChangedFile } from "shared/changes-types";
import { createFileKey, useScrollContext } from "../../../../ChangesContent";
import { usePathActions } from "../../hooks";
import { getStatusColor, getStatusIndicator } from "../../utils";

interface FileItemProps {
Expand Down Expand Up @@ -97,33 +97,14 @@ export function FileItem({
category && activeFileKey === createFileKey(file, category, commitHash);
const isHighlighted = isExpandedView ? isScrollSyncActive : isSelected;

const openInFinderMutation = electronTrpc.external.openInFinder.useMutation();
const openInEditorMutation =
electronTrpc.external.openFileInEditor.useMutation();

const absolutePath = worktreePath ? `${worktreePath}/${file.path}` : null;

const handleCopyPath = async () => {
if (absolutePath) {
await navigator.clipboard.writeText(absolutePath);
}
};

const handleCopyRelativePath = async () => {
await navigator.clipboard.writeText(file.path);
};

const handleRevealInFinder = () => {
if (absolutePath) {
openInFinderMutation.mutate(absolutePath);
}
};

const handleOpenInEditor = useCallback(() => {
if (absolutePath && worktreePath) {
openInEditorMutation.mutate({ path: absolutePath, cwd: worktreePath });
}
}, [absolutePath, worktreePath, openInEditorMutation]);
const { copyPath, copyRelativePath, revealInFinder, openInEditor } =
usePathActions({
absolutePath,
relativePath: file.path,
cwd: worktreePath,
});

const handleClick = useCallback(() => {
// Clear any pending single-click timeout
Expand All @@ -149,10 +130,9 @@ export function FileItem({
clickTimeoutRef.current = null;
}

// Execute double-click action (open in editor)
handleOpenInEditor();
openInEditor();
},
[handleOpenInEditor],
[openInEditor],
);

// Cleanup timeout on unmount
Expand Down Expand Up @@ -285,20 +265,20 @@ export function FileItem({
<ContextMenu>
<ContextMenuTrigger asChild>{fileContent}</ContextMenuTrigger>
<ContextMenuContent className="w-48">
<ContextMenuItem onClick={handleCopyPath}>
<ContextMenuItem onClick={copyPath}>
<LuClipboard className="mr-2 size-4" />
Copy Path
</ContextMenuItem>
<ContextMenuItem onClick={handleCopyRelativePath}>
<ContextMenuItem onClick={copyRelativePath}>
<LuClipboard className="mr-2 size-4" />
Copy Relative Path
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem onClick={handleRevealInFinder}>
<ContextMenuItem onClick={revealInFinder}>
<LuFolderOpen className="mr-2 size-4" />
Reveal in Finder
</ContextMenuItem>
<ContextMenuItem onClick={handleOpenInEditor}>
<ContextMenuItem onClick={openInEditor}>
<LuExternalLink className="mr-2 size-4" />
Open in Editor
</ContextMenuItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface FileListProps {
onStage?: (file: ChangedFile) => void;
onUnstage?: (file: ChangedFile) => void;
isActioning?: boolean;
worktreePath?: string;
worktreePath: string;
onDiscard?: (file: ChangedFile) => void;
category?: ChangeCategory;
commitHash?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useCallback, useState } from "react";
import type { ChangeCategory, ChangedFile } from "shared/changes-types";
import { FileItem } from "../FileItem";
import { FolderRow } from "../FolderRow";
Expand All @@ -12,7 +12,7 @@ interface FileListGroupedProps {
onStage?: (file: ChangedFile) => void;
onUnstage?: (file: ChangedFile) => void;
isActioning?: boolean;
worktreePath?: string;
worktreePath: string;
onDiscard?: (file: ChangedFile) => void;
category?: ChangeCategory;
commitHash?: string;
Expand Down Expand Up @@ -66,7 +66,7 @@ interface FolderGroupItemProps {
onStage?: (file: ChangedFile) => void;
onUnstage?: (file: ChangedFile) => void;
isActioning?: boolean;
worktreePath?: string;
worktreePath: string;
onDiscard?: (file: ChangedFile) => void;
category?: ChangeCategory;
commitHash?: string;
Expand All @@ -88,8 +88,28 @@ function FolderGroupItem({
isExpandedView,
}: FolderGroupItemProps) {
const [isExpanded, setIsExpanded] = useState(true);
const isRoot = group.folderPath === "";
const displayName = isRoot ? "Root Path" : group.folderPath;
const displayName = group.folderPath || "Root Path";

const handleStageAll = useCallback(() => {
if (!onStage) return;
for (const file of group.files) {
onStage(file);
}
}, [group.files, onStage]);

const handleUnstageAll = useCallback(() => {
if (!onUnstage) return;
for (const file of group.files) {
onUnstage(file);
}
}, [group.files, onUnstage]);

const handleDiscardAll = useCallback(() => {
if (!onDiscard) return;
for (const file of group.files) {
onDiscard(file);
}
}, [group.files, onDiscard]);

return (
<FolderRow
Expand All @@ -98,6 +118,12 @@ function FolderGroupItem({
onToggle={setIsExpanded}
fileCount={group.files.length}
variant="grouped"
folderPath={group.folderPath}
worktreePath={worktreePath}
onStageAll={onStage ? handleStageAll : undefined}
onUnstageAll={onUnstage ? handleUnstageAll : undefined}
onDiscardAll={onDiscard ? handleDiscardAll : undefined}
isActioning={isActioning}
>
{group.files.map((file) => (
<FileItem
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@
import { useState } from "react";
import { useCallback, useState } from "react";
import type { ChangeCategory, ChangedFile } from "shared/changes-types";
import { FileItem } from "../FileItem";
import { FolderRow } from "../FolderRow";

interface FileTreeNode {
id: string;
name: string;
type: "file" | "folder";
path: string;
file?: ChangedFile;
children?: FileTreeNode[];
}

function collectFilesFromNode(node: FileTreeNode): ChangedFile[] {
const files: ChangedFile[] = [];

if (node.type === "file" && node.file) {
files.push(node.file);
}

if (node.children) {
for (const child of node.children) {
files.push(...collectFilesFromNode(child));
}
}

return files;
}

interface FileListTreeProps {
files: ChangedFile[];
selectedFile: ChangedFile | null;
Expand All @@ -12,22 +37,13 @@ interface FileListTreeProps {
onStage?: (file: ChangedFile) => void;
onUnstage?: (file: ChangedFile) => void;
isActioning?: boolean;
worktreePath?: string;
worktreePath: string;
onDiscard?: (file: ChangedFile) => void;
category?: ChangeCategory;
commitHash?: string;
isExpandedView?: boolean;
}

interface FileTreeNode {
id: string;
name: string;
type: "file" | "folder";
path: string;
file?: ChangedFile;
children?: FileTreeNode[];
}

function buildFileTree(files: ChangedFile[]): FileTreeNode[] {
type TreeNodeInternal = Omit<FileTreeNode, "children"> & {
children?: Record<string, TreeNodeInternal>;
Expand Down Expand Up @@ -90,7 +106,7 @@ interface TreeNodeComponentProps {
onStage?: (file: ChangedFile) => void;
onUnstage?: (file: ChangedFile) => void;
isActioning?: boolean;
worktreePath?: string;
worktreePath: string;
onDiscard?: (file: ChangedFile) => void;
category?: ChangeCategory;
commitHash?: string;
Expand Down Expand Up @@ -118,6 +134,30 @@ function TreeNodeComponent({
const isFile = node.type === "file";
const isSelected = selectedPath === node.path && !selectedCommitHash;

const handleStageAll = useCallback(() => {
if (!onStage) return;
const files = collectFilesFromNode(node);
for (const file of files) {
onStage(file);
}
}, [node, onStage]);

const handleUnstageAll = useCallback(() => {
if (!onUnstage) return;
const files = collectFilesFromNode(node);
for (const file of files) {
onUnstage(file);
}
}, [node, onUnstage]);

const handleDiscardAll = useCallback(() => {
if (!onDiscard) return;
const files = collectFilesFromNode(node);
for (const file of files) {
onDiscard(file);
}
}, [node, onDiscard]);

if (hasChildren) {
return (
<FolderRow
Expand All @@ -126,6 +166,12 @@ function TreeNodeComponent({
onToggle={setIsExpanded}
level={level}
variant="tree"
folderPath={node.path}
worktreePath={worktreePath}
onStageAll={onStage ? handleStageAll : undefined}
onUnstageAll={onUnstage ? handleUnstageAll : undefined}
onDiscardAll={onDiscard ? handleDiscardAll : undefined}
isActioning={isActioning}
>
{node.children?.map((child) => (
<TreeNodeComponent
Expand Down
Loading
Loading