diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx index 84167cdfd0f..dd6f1d61907 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx @@ -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"; @@ -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(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); @@ -67,42 +102,99 @@ export function WorkspaceSidebar({ const reviewTab = useReviewTab({ workspaceId, onOpenComment }); - const filesTab: SidebarTabDefinition = useMemo( - () => ({ - id: "files", - label: "All files", - actions: , - content: ( - - ), - }), - [ - gitStatus.data, - onSearch, - onSelectFile, - selectedFilePath, - workspaceId, - workspaceName, - ], - ); + const filesTab: SidebarTabDefinition = { + id: "files", + label: "Files", + icon: LuFile, + actions: , + content: ( + + ), + }; + + const combinedChangesTab: SidebarTabDefinition = { + id: "changes", + label: "Changes", + icon: LuGitCompareArrows, + badge: changesTab.badge, + actions: changesSubtab === "diffs" ? changesTab.actions : reviewTab.actions, + content: ( + setChangesSubtab(v as "diffs" | "review")} + className="flex min-h-0 flex-1 flex-col gap-0" + > +
+ + + Diffs + {changesTab.badge != null && ( + + {changesTab.badge} + + )} + + + Review + {reviewTab.badge != null && reviewTab.badge > 0 && ( + + {reviewTab.badge} + + )} + + +
+ + {changesTab.content} + + + {reviewTab.content} + +
+ ), + }; - const tabs = [filesTab, changesTab, reviewTab]; + const tabs = [combinedChangesTab, filesTab]; const activeTabDef = tabs.find((t) => t.id === activeTab); return ( -
+
-
{activeTabDef?.content}
+
+ {activeTabDef?.content} +
); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/SidebarHeader/SidebarHeader.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/SidebarHeader/SidebarHeader.tsx index 0ca94f1a1ac..a30d54783a6 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/SidebarHeader/SidebarHeader.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/SidebarHeader/SidebarHeader.tsx @@ -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 (
-
- {tabs.map((tab) => ( - - ))} +
+ {tabs.map((tab) => { + const isActive = activeTab === tab.id; + const btn = ( + + ); + + if (compact) { + return ( + + {btn} + + {tab.label} + + + ); + } + + return ( + + ); + })}
+
{actions && ( -
{actions}
+
{actions}
)}
); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/ChangesFileList.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/ChangesFileList.tsx index a5c3bb18c06..27eddd2062e 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/ChangesFileList.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/ChangesFileList.tsx @@ -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; - 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 (
@@ -41,13 +31,11 @@ export const ChangesFileList = memo(function ChangesFileList({ return (
- {sortedFiles.map((file) => ( + {files.map((file) => ( ))}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/components/FileRow/FileRow.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/components/FileRow/FileRow.tsx index 39adbeb74be..67edb291ff5 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/components/FileRow/FileRow.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/components/FileRow/FileRow.tsx @@ -1,4 +1,3 @@ -import { Checkbox } from "@superset/ui/checkbox"; import { memo } from "react"; import { StatusIndicator } from "renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/StatusIndicator"; import { FileIcon } from "renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/utils"; @@ -16,57 +15,38 @@ function splitPath(path: string): { dir: string; basename: string } { interface FileRowProps { file: ChangesetFile; onSelect?: (path: string) => void; - viewed: boolean; - onSetViewed: (path: string, next: boolean) => void; } -export const FileRow = memo(function FileRow({ - file, - onSelect, - viewed, - onSetViewed, -}: FileRowProps) { +export const FileRow = memo(function FileRow({ file, onSelect }: FileRowProps) { const { dir, basename } = splitPath(file.path); return ( -
onSelect?.(file.path)} > - onSetViewed(file.path, checked === true)} - className="size-3.5 shrink-0 border-muted-foreground/50" - aria-label={viewed ? "Mark as not viewed" : "Mark as viewed"} - /> - -
+ + + {(file.additions > 0 || file.deletions > 0) && ( + + {file.additions > 0 && ( + +{file.additions} + )} + {file.additions > 0 && file.deletions > 0 && " "} + {file.deletions > 0 && ( + -{file.deletions} + )} + + )} + + + ); }); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/utils/partitionByViewed/index.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/utils/partitionByViewed/index.ts deleted file mode 100644 index afe616cabed..00000000000 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/utils/partitionByViewed/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { partitionByViewed } from "./partitionByViewed"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/utils/partitionByViewed/partitionByViewed.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/utils/partitionByViewed/partitionByViewed.ts deleted file mode 100644 index d7f66863733..00000000000 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/utils/partitionByViewed/partitionByViewed.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { ChangesetFile } from "../../../../../../../../hooks/useChangeset"; - -export function partitionByViewed( - files: ChangesetFile[], - viewedSet: Set, -): ChangesetFile[] { - if (viewedSet.size === 0) return files; - const unviewed: ChangesetFile[] = []; - const viewed: ChangesetFile[] = []; - for (const file of files) { - if (viewedSet.has(file.path)) viewed.push(file); - else unviewed.push(file); - } - return [...unviewed, ...viewed]; -} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx index 8cd63eba17f..af9beca5e3c 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx @@ -27,8 +27,6 @@ interface ChangesTabContentProps { onBaseBranchChange: (branchName: string) => void; onRenameBranch: (newName: string) => void; canRenameBranch: boolean; - viewedSet: Set; - onSetViewed: (path: string, next: boolean) => void; } export const ChangesTabContent = memo(function ChangesTabContent({ @@ -47,8 +45,6 @@ export const ChangesTabContent = memo(function ChangesTabContent({ onBaseBranchChange, onRenameBranch, canRenameBranch, - viewedSet, - onSetViewed, }: ChangesTabContentProps) { if (status.isLoading) { return ( @@ -92,8 +88,6 @@ export const ChangesTabContent = memo(function ChangesTabContent({ files={files} isLoading={isLoading} onSelectFile={onSelectFile} - viewedSet={viewedSet} - onSetViewed={onSetViewed} />
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx index 6e7670441ea..7c4c5c6e8e1 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx @@ -6,7 +6,6 @@ import { useCollections } from "renderer/routes/_authenticated/providers/Collect import type { ChangesFilter } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema"; import { useChangeset } from "../../../../hooks/useChangeset"; import { useSidebarDiffRef } from "../../../../hooks/useSidebarDiffRef"; -import { useViewedFiles } from "../../../../hooks/useViewedFiles"; import type { SidebarTabDefinition } from "../../types"; import { ChangesTabContent } from "./components/ChangesTabContent"; @@ -36,8 +35,6 @@ export function useChangesTab({ ); const baseBranch = baseBranchQuery.data?.baseBranch ?? null; - const { viewedSet, setViewed } = useViewedFiles(workspaceId); - const ref = useSidebarDiffRef(workspaceId); const { files, isLoading } = useChangeset({ workspaceId, ref }); @@ -123,8 +120,6 @@ export function useChangesTab({ onBaseBranchChange={setBaseBranch} onRenameBranch={handleRenameBranch} canRenameBranch={canRenameBranch} - viewedSet={viewedSet} - onSetViewed={setViewed} /> ); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/types.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/types.ts index d771451be48..01f1ed5d73b 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/types.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/types.ts @@ -1,8 +1,9 @@ -import type { ReactNode } from "react"; +import type { ComponentType, ReactNode } from "react"; export interface SidebarTabDefinition { id: string; label: string; + icon?: ComponentType<{ className?: string }>; badge?: number; actions?: ReactNode; content: ReactNode; diff --git a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts index 46d85d00295..84142a7c5d4 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts @@ -36,6 +36,8 @@ export const workspaceLocalStateSchema = z.object({ tabOrder: z.number().int().default(0), sectionId: z.string().uuid().nullable().default(null), changesFilter: changesFilterSchema.default({ kind: "all" }), + activeTab: z.enum(["changes", "files"]).default("changes"), + changesSubtab: z.enum(["diffs", "review"]).default("diffs"), }), paneLayout: paneWorkspaceStateSchema, rightSidebarOpen: z.boolean().default(false),