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