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
15 changes: 15 additions & 0 deletions apps/desktop/src/renderer/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@
--sidebar-ring: #3a3837;
--highlight-match: rgba(224, 120, 80, 0.2);
--highlight-active: rgba(224, 120, 80, 0.5);
--diff-added: oklch(0.77 0.2 155);
--diff-modified: oklch(0.82 0.2 95);
--diff-deleted: oklch(0.65 0.2 25);
--diff-renamed: oklch(0.7 0.17 260);
--diff-copied: oklch(0.7 0.2 300);
}

/* Light theme fallback values - applied before hydration if user has light theme saved */
Expand Down Expand Up @@ -93,6 +98,11 @@
--sidebar-ring: oklch(0.708 0 0);
--highlight-match: rgba(255, 211, 61, 0.35);
--highlight-active: rgba(255, 150, 50, 0.55);
--diff-added: oklch(0.55 0.18 155);
--diff-modified: oklch(0.65 0.18 85);
--diff-deleted: oklch(0.52 0.22 25);
--diff-renamed: oklch(0.52 0.2 260);
--diff-copied: oklch(0.5 0.22 300);
}

@theme inline {
Expand Down Expand Up @@ -134,6 +144,11 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--color-diff-added: var(--diff-added);
--color-diff-modified: var(--diff-modified);
--color-diff-deleted: var(--diff-deleted);
--color-diff-renamed: var(--diff-renamed);
--color-diff-copied: var(--diff-copied);
}

@layer base {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ export function BaseBranchSelector({
<ChevronDown className="size-3" />
</button>
</PopoverTrigger>
<PopoverContent className="w-64 p-0" align="start">
<PopoverContent
className="flex w-64 max-h-96 flex-col p-0 overflow-hidden"
align="start"
>
<div className="border-b px-3 py-2">
<input
placeholder="Search branches..."
Expand All @@ -48,7 +51,7 @@ export function BaseBranchSelector({
className="w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground"
/>
</div>
<ScrollArea className="max-h-[200px]">
<ScrollArea className="flex-1 overflow-y-auto">
<div className="p-1">
{filtered.map((branch) => (
<button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import type { AppRouter } from "@superset/host-service";
import type { inferRouterOutputs } from "@trpc/server";
import { ChevronDown, ChevronRight } from "lucide-react";
import { useMemo, useState } from "react";
import { memo, type ReactNode, useMemo, useState } from "react";
import {
VscCopy,
VscDiffAdded,
VscDiffModified,
VscDiffRemoved,
VscDiffRenamed,
} from "react-icons/vsc";
import { FileIcon } from "renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/utils";

type ChangedFile =
Expand All @@ -10,24 +17,34 @@ type FileStatus = ChangedFile["status"];
type ChangeCategory = "against-base" | "staged" | "unstaged";

const STATUS_COLORS: Record<FileStatus, string> = {
added: "text-green-400",
copied: "text-purple-400",
changed: "text-yellow-400",
deleted: "text-red-400",
modified: "text-yellow-400",
renamed: "text-blue-400",
untracked: "text-green-400",
added: "text-diff-added",
copied: "text-diff-copied",
changed: "text-diff-modified",
deleted: "text-diff-deleted",
modified: "text-diff-modified",
renamed: "text-diff-renamed",
untracked: "text-diff-added",
};

const STATUS_LETTERS: Record<FileStatus, string> = {
added: "A",
copied: "C",
changed: "T",
deleted: "D",
modified: "M",
renamed: "R",
untracked: "U",
};
function getStatusIcon(status: FileStatus): ReactNode {
const iconClass = "w-3 h-3";
switch (status) {
case "added":
case "untracked":
return <VscDiffAdded className={iconClass} />;
case "modified":
case "changed":
return <VscDiffModified className={iconClass} />;
case "deleted":
return <VscDiffRemoved className={iconClass} />;
case "renamed":
return <VscDiffRenamed className={iconClass} />;
case "copied":
return <VscCopy className={iconClass} />;
default:
return null;
}
}

function groupByFolder(
files: ChangedFile[],
Expand All @@ -48,13 +65,13 @@ function groupByFolder(

function StatusIndicator({ status }: { status: FileStatus }) {
return (
<span className={`shrink-0 text-[10px] font-bold ${STATUS_COLORS[status]}`}>
{STATUS_LETTERS[status]}
<span className={`shrink-0 flex items-center ${STATUS_COLORS[status]}`}>
{getStatusIcon(status)}
</span>
);
}

function FileRow({
const FileRow = memo(function FileRow({
file,
category,
onSelect,
Expand Down Expand Up @@ -89,9 +106,9 @@ function FileRow({
</span>
</button>
);
}
});

function FolderGroup({
const FolderGroup = memo(function FolderGroup({
folder,
files,
category,
Expand Down Expand Up @@ -124,7 +141,7 @@ function FolderGroup({
))}
</div>
);
}
});

function Section({
title,
Expand Down Expand Up @@ -185,7 +202,7 @@ interface ChangesFileListProps {
onSelectFile?: (path: string, category: ChangeCategory) => void;
}

export function ChangesFileList({
export const ChangesFileList = memo(function ChangesFileList({
files,
staged,
unstaged,
Expand All @@ -194,6 +211,8 @@ export function ChangesFileList({
category = "against-base",
onSelectFile,
}: ChangesFileListProps) {
const groups = useMemo(() => groupByFolder(files), [files]);

if (isLoading) {
return (
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
Expand All @@ -213,7 +232,6 @@ export function ChangesFileList({
);
}

// If staged/unstaged are provided, show three sections
if (staged !== undefined && unstaged !== undefined) {
return (
<div className="min-h-0 flex-1 overflow-y-auto">
Expand Down Expand Up @@ -242,8 +260,6 @@ export function ChangesFileList({
);
}

// Single list (filtered by commit or uncommitted)
const groups = groupByFolder(files);
return (
<div className="min-h-0 flex-1 overflow-y-auto">
{groups.map((group) => (
Expand All @@ -257,4 +273,4 @@ export function ChangesFileList({
))}
</div>
);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { GitBranch, Pencil } from "lucide-react";
import { useRef, useState } from "react";
import type { ChangesFilter } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema";
import type { Branch, Commit } from "../../types";
import { BaseBranchSelector } from "../BaseBranchSelector";
import { CommitFilterDropdown } from "../CommitFilterDropdown";

interface ChangesHeaderProps {
currentBranch: { name: string; aheadCount: number; behindCount: number };
defaultBranchName: string;
commitCount: number;
totalFiles: number;
totalAdditions: number;
totalDeletions: number;
filter: ChangesFilter;
onFilterChange: (filter: ChangesFilter) => void;
commits: Commit[];
uncommittedCount: number;
branches: Branch[];
onBaseBranchChange: (branchName: string) => void;
onRenameBranch: (newName: string) => void;
canRename: boolean;
}

export function ChangesHeader({
currentBranch,
defaultBranchName,
commitCount,
totalFiles,
totalAdditions,
totalDeletions,
onRenameBranch,
canRename,
filter,
onFilterChange,
commits,
uncommittedCount,
branches,
onBaseBranchChange,
}: ChangesHeaderProps) {
const [isEditing, setIsEditing] = useState(false);
const [editValue, setEditValue] = useState(currentBranch.name);
const inputRef = useRef<HTMLInputElement>(null);
const skipBlurRef = useRef(false);

const startEditing = () => {
setEditValue(currentBranch.name);
setIsEditing(true);
skipBlurRef.current = false;
requestAnimationFrame(() => inputRef.current?.select());
};

const handleSubmit = () => {
const trimmed = editValue.trim();
if (trimmed && trimmed !== currentBranch.name) {
onRenameBranch(trimmed);
}
setIsEditing(false);
};

return (
<div className="border-b border-border bg-muted/30 px-3 py-2.5 space-y-1.5">
<div className="group flex items-center gap-1.5 text-xs">
<GitBranch className="size-3.5 shrink-0 text-muted-foreground" />
{isEditing ? (
<input
ref={inputRef}
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
skipBlurRef.current = true;
handleSubmit();
}
if (e.key === "Escape") {
skipBlurRef.current = true;
setIsEditing(false);
}
}}
onBlur={() => {
if (skipBlurRef.current) return;
handleSubmit();
}}
className="min-w-0 flex-1 truncate bg-transparent font-medium outline-none ring-1 ring-ring rounded-sm px-1"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
/>
) : (
<>
<span className="truncate font-medium">{currentBranch.name}</span>
{canRename && (
<button
type="button"
onClick={startEditing}
className="shrink-0 opacity-0 group-hover:opacity-100 transition-opacity text-muted-foreground hover:text-foreground"
>
<Pencil className="size-3" />
</button>
Comment thread
Kitenite marked this conversation as resolved.
)}
</>
)}
</div>

<div className="text-[11px] text-muted-foreground">
{commitCount} {commitCount === 1 ? "commit" : "commits"} from{" "}
<BaseBranchSelector
branches={branches}
currentValue={defaultBranchName}
onChange={onBaseBranchChange}
/>
</div>

{currentBranch.aheadCount > 0 && currentBranch.behindCount > 0 && (
<div className="text-[11px] text-muted-foreground">
<div>Your branch and</div>
<div className="font-medium text-foreground">
origin/{currentBranch.name}
</div>
<div>have diverged</div>
<div>
{currentBranch.aheadCount} local not pushed,{" "}
{currentBranch.behindCount} remote to pull
</div>
</div>
)}
{currentBranch.aheadCount > 0 && currentBranch.behindCount === 0 && (
<div className="text-[11px] text-muted-foreground">
<div>
{currentBranch.aheadCount}{" "}
{currentBranch.aheadCount === 1 ? "commit" : "commits"} ahead of
</div>
<div className="font-medium text-foreground">
origin/{currentBranch.name}
</div>
</div>
)}
{currentBranch.behindCount > 0 && currentBranch.aheadCount === 0 && (
<div className="text-[11px] text-muted-foreground">
<div>
{currentBranch.behindCount}{" "}
{currentBranch.behindCount === 1 ? "commit" : "commits"} behind
</div>
<div className="font-medium text-foreground">
origin/{currentBranch.name}
</div>
</div>
)}

<div className="flex items-center justify-between pt-0.5">
<CommitFilterDropdown
filter={filter}
onFilterChange={onFilterChange}
commits={commits}
uncommittedCount={uncommittedCount}
/>
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
<span>{totalFiles} files changed</span>
{(totalAdditions > 0 || totalDeletions > 0) && (
<span>
{totalAdditions > 0 && (
<span className="text-green-400">+{totalAdditions}</span>
)}
{totalAdditions > 0 && totalDeletions > 0 && " "}
{totalDeletions > 0 && (
<span className="text-red-400">-{totalDeletions}</span>
)}
</span>
)}
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ChangesHeader } from "./ChangesHeader";
Loading
Loading