refactor(diff): enchance file list display
This commit is contained in:
@@ -74,7 +74,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@openchamber/desktop",
|
||||
"version": "1.4.6",
|
||||
"version": "1.4.8",
|
||||
"dependencies": {
|
||||
"@openchamber/ui": "workspace:*",
|
||||
"@tauri-apps/plugin-notification": "^2.3.3",
|
||||
@@ -97,7 +97,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@openchamber/ui",
|
||||
"version": "1.4.6",
|
||||
"version": "1.4.8",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
@@ -106,7 +106,7 @@
|
||||
"@fontsource/ibm-plex-sans": "^5.1.1",
|
||||
"@ibm/plex": "^6.4.1",
|
||||
"@opencode-ai/sdk": "^1.1.8",
|
||||
"@pierre/diffs": "^1.0.0",
|
||||
"@pierre/diffs": "^1.0.5",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
@@ -169,7 +169,7 @@
|
||||
},
|
||||
"packages/vscode": {
|
||||
"name": "openchamber",
|
||||
"version": "1.4.6",
|
||||
"version": "1.4.8",
|
||||
"dependencies": {
|
||||
"@openchamber/ui": "workspace:*",
|
||||
"@opencode-ai/sdk": "^1.1.8",
|
||||
@@ -192,7 +192,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@openchamber/web",
|
||||
"version": "1.4.6",
|
||||
"version": "1.4.8",
|
||||
"bin": {
|
||||
"openchamber": "./bin/cli.js",
|
||||
},
|
||||
@@ -539,7 +539,7 @@
|
||||
|
||||
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.1.8", "", {}, "sha512-N/02945tafTFN1FjlGCWO++dErtNyWaYOdARPifuL4nSsxTk2XtqZ2naZ28rj8kekjYR8JX1MA5upNEqwztOZA=="],
|
||||
|
||||
"@pierre/diffs": ["@pierre/diffs@1.0.2", "", { "dependencies": { "@shikijs/core": "^3.0.0", "@shikijs/engine-javascript": "3.19.0", "@shikijs/transformers": "3.19.0", "diff": "8.0.2", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "3.19.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-RkFSDD5X/U+8QjyilPViYGJfmJNWXR17zTL8zw48+DcVC1Ujbh6I1edyuRnFfgRzpft05x2DSCkz2cjoIAxPvQ=="],
|
||||
"@pierre/diffs": ["@pierre/diffs@1.0.5", "", { "dependencies": { "@shikijs/core": "^3.0.0", "@shikijs/engine-javascript": "^3.0.0", "@shikijs/transformers": "^3.0.0", "diff": "8.0.2", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-QcFhO6BW1Zz3BP+WFuH1tO2DjFJY5Sb6NmRjmEoVeEu1AVOd3HoUEFTnztxgxn+2c2ZFyFZP+6T4X/g8LDuZLw=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -2977,7 +2977,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openchamber-desktop"
|
||||
version = "1.4.7"
|
||||
version = "1.4.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@fontsource/ibm-plex-sans": "^5.1.1",
|
||||
"@ibm/plex": "^6.4.1",
|
||||
"@opencode-ai/sdk": "^1.1.8",
|
||||
"@pierre/diffs": "^1.0.0",
|
||||
"@pierre/diffs": "^1.0.5",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useSessionStore } from '@/stores/useSessionStore';
|
||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useGitStore, useGitStatus, useIsGitRepo, useGitFileCount } from '@/stores/useGitStore';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { GitStatus } from '@/lib/api/types';
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||
import { getLanguageFromExtension, isImageFile } from '@/lib/toolHelpers';
|
||||
@@ -240,21 +242,13 @@ interface FileListProps {
|
||||
changedFiles: FileEntry[];
|
||||
selectedFile: string | null;
|
||||
onSelectFile: (path: string) => void;
|
||||
isCompact: boolean;
|
||||
}
|
||||
|
||||
const FileList = React.memo<FileListProps>(({
|
||||
changedFiles,
|
||||
selectedFile,
|
||||
onSelectFile,
|
||||
isCompact,
|
||||
}) => {
|
||||
const getLabel = React.useCallback((path: string) => {
|
||||
if (!isCompact) return path;
|
||||
const lastSlash = path.lastIndexOf('/');
|
||||
return lastSlash >= 0 ? path.slice(lastSlash + 1) : path;
|
||||
}, [isCompact]);
|
||||
|
||||
if (changedFiles.length === 0) return null;
|
||||
|
||||
return (
|
||||
@@ -269,11 +263,12 @@ const FileList = React.memo<FileListProps>(({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSelectFile(file.path)}
|
||||
className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors ${
|
||||
className={cn(
|
||||
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors',
|
||||
isActive
|
||||
? 'bg-accent/70 text-foreground'
|
||||
: 'text-muted-foreground hover:bg-accent/40 hover:text-foreground'
|
||||
}`}
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className="typography-micro font-semibold w-4 text-center uppercase"
|
||||
@@ -285,9 +280,10 @@ const FileList = React.memo<FileListProps>(({
|
||||
</span>
|
||||
<span
|
||||
className="min-w-0 flex-1 truncate typography-meta"
|
||||
style={{ direction: 'rtl', textAlign: 'left' }}
|
||||
title={file.path}
|
||||
>
|
||||
{getLabel(file.path)}
|
||||
{file.path}
|
||||
</span>
|
||||
{formatDiffTotals(file.insertions, file.deletions)}
|
||||
</button>
|
||||
@@ -701,50 +697,64 @@ const MultiFileDiffEntry = React.memo<MultiFileDiffEntryProps>(({
|
||||
<Collapsible
|
||||
open={isExpanded}
|
||||
onOpenChange={handleOpenChange}
|
||||
className="rounded-xl border border-border/60 bg-background/70 overflow-hidden"
|
||||
className="group/collapsible"
|
||||
>
|
||||
<CollapsibleTrigger
|
||||
onClick={handleSelect}
|
||||
className={`group gap-2 ${isSelected ? 'bg-accent/60' : ''}`}
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||
<span className="flex size-5 items-center justify-center text-muted-foreground">
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className="size-4" />
|
||||
) : (
|
||||
<RiArrowRightSLine className="size-4" />
|
||||
)}
|
||||
</span>
|
||||
<span
|
||||
className="typography-micro font-semibold w-4 text-center uppercase"
|
||||
style={{ color: descriptor.color }}
|
||||
title={descriptor.description}
|
||||
aria-label={descriptor.description}
|
||||
>
|
||||
{descriptor.code}
|
||||
</span>
|
||||
<span
|
||||
className="min-w-0 flex-1 truncate typography-ui-label"
|
||||
title={file.path}
|
||||
>
|
||||
{file.path}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{formatDiffTotals(file.insertions, file.deletions)}
|
||||
<DiffViewToggle
|
||||
mode={renderSideBySide ? 'side-by-side' : 'unified'}
|
||||
onModeChange={(mode: DiffViewMode) => {
|
||||
const nextLayout: 'inline' | 'side-by-side' =
|
||||
mode === 'side-by-side' ? 'side-by-side' : 'inline';
|
||||
setDiffFileLayout(file.path, nextLayout);
|
||||
}}
|
||||
className="opacity-70"
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="border-t border-border/60 bg-background">
|
||||
<div className="relative">
|
||||
<div className="sticky top-0 z-10 bg-background">
|
||||
<CollapsibleTrigger
|
||||
onClick={handleSelect}
|
||||
className={cn(
|
||||
'relative flex w-full items-center gap-2 px-3 py-1.5 transition-colors rounded-t-xl border border-border/60 overflow-hidden',
|
||||
'bg-background hover:bg-background',
|
||||
isExpanded ? 'rounded-b-none' : 'rounded-b-xl',
|
||||
isSelected
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<div className={cn(
|
||||
'absolute inset-0 pointer-events-none transition-colors',
|
||||
isSelected ? 'bg-primary/10' : 'group-hover:bg-accent/40'
|
||||
)} />
|
||||
<div className="relative flex min-w-0 flex-1 items-center gap-2">
|
||||
<span className="flex size-5 items-center justify-center opacity-70 group-hover:opacity-100 transition-opacity">
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className="size-4" />
|
||||
) : (
|
||||
<RiArrowRightSLine className="size-4" />
|
||||
)}
|
||||
</span>
|
||||
<span
|
||||
className="typography-micro font-semibold w-4 text-center uppercase"
|
||||
style={{ color: descriptor.color }}
|
||||
title={descriptor.description}
|
||||
aria-label={descriptor.description}
|
||||
>
|
||||
{descriptor.code}
|
||||
</span>
|
||||
<span
|
||||
className="min-w-0 flex-1 truncate typography-ui-label"
|
||||
style={{ direction: 'rtl', textAlign: 'left' }}
|
||||
title={file.path}
|
||||
>
|
||||
{file.path}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative flex items-center gap-2">
|
||||
{formatDiffTotals(file.insertions, file.deletions)}
|
||||
<DiffViewToggle
|
||||
mode={renderSideBySide ? 'side-by-side' : 'unified'}
|
||||
onModeChange={(mode: DiffViewMode) => {
|
||||
const nextLayout: 'inline' | 'side-by-side' =
|
||||
mode === 'side-by-side' ? 'side-by-side' : 'inline';
|
||||
setDiffFileLayout(file.path, nextLayout);
|
||||
}}
|
||||
className="opacity-70"
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
</div>
|
||||
<CollapsibleContent>
|
||||
<div className="relative border border-t-0 border-border/60 bg-background rounded-b-xl overflow-hidden">
|
||||
{diffLoadError ? (
|
||||
<div className="flex flex-col items-center gap-2 px-4 py-8 text-sm text-muted-foreground">
|
||||
<div className="typography-ui-label font-semibold text-foreground">
|
||||
@@ -1065,7 +1075,7 @@ export const DiffView: React.FC = () => {
|
||||
<div className="flex flex-1 min-h-0 gap-3 px-3 pb-3 pt-2">
|
||||
{showFileSidebar && (
|
||||
<section className="hidden lg:flex w-72 flex-col rounded-xl border border-border/60 bg-background/70 overflow-hidden">
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b border-border/40">
|
||||
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border/40">
|
||||
<span className="typography-ui-header font-semibold text-foreground">Files</span>
|
||||
<span className="typography-meta text-muted-foreground">{changedFiles.length}</span>
|
||||
</div>
|
||||
@@ -1073,7 +1083,6 @@ export const DiffView: React.FC = () => {
|
||||
changedFiles={changedFiles}
|
||||
selectedFile={selectedFile}
|
||||
onSelectFile={handleSelectFileAndScroll}
|
||||
isCompact={screenWidth < 1280}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
@@ -1082,7 +1091,7 @@ export const DiffView: React.FC = () => {
|
||||
outerClassName="flex-1 min-h-0"
|
||||
className="pr-2"
|
||||
>
|
||||
<div className="flex flex-col gap-3 py-1">
|
||||
<div className="flex flex-col gap-3">
|
||||
{changedFiles.map((file) => (
|
||||
<MultiFileDiffEntry
|
||||
key={file.path}
|
||||
@@ -1207,18 +1216,18 @@ export const DiffView: React.FC = () => {
|
||||
)}
|
||||
<div className="flex-1" />
|
||||
{selectedFileEntry && (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setDiffWrapLines(!diffWrapLinesStore)}
|
||||
className={`flex items-center justify-center size-5 rounded-sm transition-opacity ${
|
||||
diffWrapLines
|
||||
? 'text-foreground opacity-100'
|
||||
: 'text-muted-foreground opacity-60 hover:opacity-100'
|
||||
}`}
|
||||
className={cn(
|
||||
'h-5 w-5 p-0 transition-opacity',
|
||||
diffWrapLines ? 'text-foreground opacity-100' : 'text-muted-foreground opacity-60 hover:opacity-100'
|
||||
)}
|
||||
title={diffWrapLines ? 'Disable line wrap' : 'Enable line wrap'}
|
||||
>
|
||||
<RiTextWrap className="size-4" />
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
{selectedFileEntry && currentLayoutForSelectedFile && (
|
||||
<DiffViewToggle
|
||||
|
||||
Reference in New Issue
Block a user