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
@@ -1,8 +1,13 @@
import { Button } from "@superset/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@superset/ui/tabs";
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
import { cn } from "@superset/ui/utils";
import { Search } from "lucide-react";
import { useMemo, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { LuFile, LuGitCompareArrows } from "react-icons/lu";
import { useGitStatus } from "renderer/hooks/host-service/useGitStatus";
import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider";
import { sidebarHeaderTabTriggerClassName } from "renderer/screens/main/components/WorkspaceView/RightSidebar/headerTabStyles";
import type { CommentPaneData } from "../../types";
import { FilesTab } from "./components/FilesTab";
import { SidebarHeader } from "./components/SidebarHeader";
Expand Down Expand Up @@ -55,7 +60,37 @@ export function WorkspaceSidebar({
workspaceId,
workspaceName,
}: WorkspaceSidebarProps) {
const [activeTab, setActiveTab] = useState("files");
const collections = useCollections();
const localState = collections.v2WorkspaceLocalState.get(workspaceId);
const activeTab = localState?.sidebarState?.activeTab ?? "changes";
const changesSubtab = localState?.sidebarState?.changesSubtab ?? "diffs";

function setActiveTab(tab: string) {
if (tab !== "changes" && tab !== "files") return;
if (!collections.v2WorkspaceLocalState.get(workspaceId)) return;
collections.v2WorkspaceLocalState.update(workspaceId, (draft) => {
draft.sidebarState.activeTab = tab;
});
}

function setChangesSubtab(subtab: "diffs" | "review") {
if (!collections.v2WorkspaceLocalState.get(workspaceId)) return;
collections.v2WorkspaceLocalState.update(workspaceId, (draft) => {
draft.sidebarState.changesSubtab = subtab;
});
}

const containerRef = useRef<HTMLDivElement>(null);
const [compact, setCompact] = useState(false);
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const ro = new ResizeObserver(([entry]) => {
if (entry) setCompact(entry.contentRect.width < 200);
});
ro.observe(el);
return () => ro.disconnect();
}, []);

const gitStatus = useGitStatus(workspaceId);

Expand All @@ -67,42 +102,99 @@ export function WorkspaceSidebar({

const reviewTab = useReviewTab({ workspaceId, onOpenComment });

const filesTab: SidebarTabDefinition = useMemo(
() => ({
id: "files",
label: "All files",
actions: <IconButton icon={Search} tooltip="Search" onClick={onSearch} />,
content: (
<FilesTab
onSelectFile={onSelectFile}
selectedFilePath={selectedFilePath}
workspaceId={workspaceId}
workspaceName={workspaceName}
gitStatus={gitStatus.data}
/>
),
}),
[
gitStatus.data,
onSearch,
onSelectFile,
selectedFilePath,
workspaceId,
workspaceName,
],
);
const filesTab: SidebarTabDefinition = {
id: "files",
label: "Files",
icon: LuFile,
actions: <IconButton icon={Search} tooltip="Search" onClick={onSearch} />,
content: (
<FilesTab
onSelectFile={onSelectFile}
selectedFilePath={selectedFilePath}
workspaceId={workspaceId}
workspaceName={workspaceName}
gitStatus={gitStatus.data}
/>
),
};

const combinedChangesTab: SidebarTabDefinition = {
id: "changes",
label: "Changes",
icon: LuGitCompareArrows,
badge: changesTab.badge,
actions: changesSubtab === "diffs" ? changesTab.actions : reviewTab.actions,
content: (
<Tabs
value={changesSubtab}
onValueChange={(v) => setChangesSubtab(v as "diffs" | "review")}
className="flex min-h-0 flex-1 flex-col gap-0"
>
<div className="h-8 shrink-0 border-b bg-background">
<TabsList className="grid h-full w-full grid-cols-2 items-stretch gap-0 rounded-none bg-transparent p-0">
<TabsTrigger
value="diffs"
className={cn(
sidebarHeaderTabTriggerClassName,
"min-w-0 w-full justify-center",
)}
>
<span>Diffs</span>
{changesTab.badge != null && (
<span className="text-[11px] text-muted-foreground/60 tabular-nums">
{changesTab.badge}
</span>
)}
</TabsTrigger>
<TabsTrigger
value="review"
className={cn(
sidebarHeaderTabTriggerClassName,
"min-w-0 w-full justify-center",
)}
>
<span>Review</span>
{reviewTab.badge != null && reviewTab.badge > 0 && (
<span className="text-[11px] text-muted-foreground/60 tabular-nums">
{reviewTab.badge}
</span>
)}
</TabsTrigger>
</TabsList>
</div>
<TabsContent
value="diffs"
className="mt-0 flex min-h-0 flex-1 flex-col outline-none"
>
{changesTab.content}
</TabsContent>
<TabsContent
value="review"
className="mt-0 flex min-h-0 flex-1 flex-col outline-none"
>
{reviewTab.content}
</TabsContent>
</Tabs>
),
};

const tabs = [filesTab, changesTab, reviewTab];
const tabs = [combinedChangesTab, filesTab];
const activeTabDef = tabs.find((t) => t.id === activeTab);

return (
<div className="flex h-full min-h-0 flex-col overflow-hidden border-l border-border bg-background">
<div
ref={containerRef}
className="flex h-full min-h-0 flex-col overflow-hidden border-l border-border bg-background"
>
<SidebarHeader
tabs={tabs}
activeTab={activeTab}
onTabChange={setActiveTab}
compact={compact}
/>
<div className="min-h-0 flex-1">{activeTabDef?.content}</div>
<div className="flex min-h-0 flex-1 flex-col">
{activeTabDef?.content}
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,43 +1,68 @@
import { cn } from "@superset/ui/utils";
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
import { getSidebarHeaderTabButtonClassName } from "renderer/screens/main/components/WorkspaceView/RightSidebar/headerTabStyles";
import type { SidebarTabDefinition } from "../../types";

interface SidebarHeaderProps {
tabs: SidebarTabDefinition[];
activeTab: string;
onTabChange: (id: string) => void;
compact?: boolean;
}

export function SidebarHeader({
tabs,
activeTab,
onTabChange,
compact,
}: SidebarHeaderProps) {
const actions = tabs.find((t) => t.id === activeTab)?.actions;

return (
<div className="flex h-10 shrink-0 items-stretch border-b border-border">
<div className="flex flex-1 items-stretch">
{tabs.map((tab) => (
<button
key={tab.id}
type="button"
onClick={() => onTabChange(tab.id)}
className={cn(
"flex h-full shrink-0 items-center gap-2 px-3 text-sm transition-all",
activeTab === tab.id
? "bg-border/30 text-foreground"
: "text-muted-foreground/70 hover:text-muted-foreground hover:bg-tertiary/20",
)}
>
{tab.label}
{tab.badge != null && (
<span className="text-xs tabular-nums">{tab.badge}</span>
)}
</button>
))}
<div className="flex items-center h-full">
{tabs.map((tab) => {
const isActive = activeTab === tab.id;
const btn = (
<button
type="button"
onClick={() => onTabChange(tab.id)}
className={getSidebarHeaderTabButtonClassName({
isActive,
compact,
})}
>
{tab.icon && <tab.icon className="size-3.5" />}
{!compact && tab.label}
</button>
);

if (compact) {
return (
<Tooltip key={tab.id}>
<TooltipTrigger asChild>{btn}</TooltipTrigger>
<TooltipContent side="bottom" showArrow={false}>
{tab.label}
</TooltipContent>
</Tooltip>
);
}

return (
<button
key={tab.id}
type="button"
onClick={() => onTabChange(tab.id)}
className={getSidebarHeaderTabButtonClassName({ isActive })}
>
{tab.icon && <tab.icon className="size-3.5" />}
{tab.label}
</button>
);
})}
</div>
<div className="flex-1" />
{actions && (
<div className="flex items-center gap-0.5 px-1">{actions}</div>
<div className="flex items-center h-10 pr-2 gap-0.5">{actions}</div>
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
import { memo, useMemo } from "react";
import { memo } from "react";
import type { ChangesetFile } from "../../../../../../hooks/useChangeset";
import { FileRow } from "./components/FileRow";
import { partitionByViewed } from "./utils/partitionByViewed";

interface ChangesFileListProps {
files: ChangesetFile[];
isLoading?: boolean;
onSelectFile?: (path: string) => void;
viewedSet: Set<string>;
onSetViewed: (path: string, next: boolean) => void;
}

export const ChangesFileList = memo(function ChangesFileList({
files,
isLoading,
onSelectFile,
viewedSet,
onSetViewed,
}: ChangesFileListProps) {
const sortedFiles = useMemo(
() => partitionByViewed(files, viewedSet),
[files, viewedSet],
);

if (isLoading) {
return (
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
Expand All @@ -41,13 +31,11 @@ export const ChangesFileList = memo(function ChangesFileList({

return (
<div className="min-h-0 flex-1 overflow-y-auto">
{sortedFiles.map((file) => (
{files.map((file) => (
<FileRow
key={`${file.source.kind}:${file.path}`}
file={file}
onSelect={onSelectFile}
viewed={viewedSet.has(file.path)}
onSetViewed={onSetViewed}
/>
))}
</div>
Expand Down
Loading
Loading