From 120a516b5cee57dd7f30f57f5a009a373a18274c Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 19 Nov 2025 15:02:35 -0800 Subject: [PATCH 01/21] format --- .../main/components/TabView/Sidebar/ModeCarousel/ModeHeader.tsx | 2 +- .../screens/main/components/TopBar/Tabs/AddTabButton.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeHeader.tsx b/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeHeader.tsx index 4be166e86b2..f0eca75113c 100644 --- a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeHeader.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeHeader.tsx @@ -8,7 +8,7 @@ interface ModeHeaderProps { export function ModeHeader({ mode }: ModeHeaderProps) { return (
- + {modeLabels[mode]}
diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx index 51f9946bb72..bf1560c99e4 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx @@ -10,7 +10,7 @@ export function AddTabButton() { size="icon" onClick={addTab} aria-label="Add new tab" - className="mt-2" + className="" > + From 0848bd65a8f3521fa323b8a4f76e300f7ff7eb91 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 19 Nov 2025 15:11:38 -0800 Subject: [PATCH 02/21] workspace refactor --- apps/desktop/src/renderer/routes.tsx | 1 - .../Sidebar/ModeCarousel/ModeContent.tsx | 2 +- .../screens/main/components/TabView/index.tsx | 10 ++- .../components/TopBar/Tabs/AddTabButton.tsx | 18 ---- .../TopBar/Workspaces/AddWorkspaceButton.tsx | 18 ++++ .../WorkspaceItem.tsx} | 29 +++--- .../TopBar/{Tabs => Workspaces}/index.tsx | 66 +++++++------- .../screens/main/components/TopBar/index.tsx | 4 +- .../src/renderer/screens/main/index.ts | 1 - .../main/{MainScreen.tsx => index.tsx} | 0 apps/desktop/src/renderer/stores/index.ts | 2 +- apps/desktop/src/renderer/stores/tabs.ts | 82 ----------------- .../desktop/src/renderer/stores/workspaces.ts | 90 +++++++++++++++++++ 13 files changed, 167 insertions(+), 156 deletions(-) delete mode 100644 apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/AddWorkspaceButton.tsx rename apps/desktop/src/renderer/screens/main/components/TopBar/{Tabs/TabItem.tsx => Workspaces/WorkspaceItem.tsx} (77%) rename apps/desktop/src/renderer/screens/main/components/TopBar/{Tabs => Workspaces}/index.tsx (60%) delete mode 100644 apps/desktop/src/renderer/screens/main/index.ts rename apps/desktop/src/renderer/screens/main/{MainScreen.tsx => index.tsx} (100%) delete mode 100644 apps/desktop/src/renderer/stores/tabs.ts create mode 100644 apps/desktop/src/renderer/stores/workspaces.ts diff --git a/apps/desktop/src/renderer/routes.tsx b/apps/desktop/src/renderer/routes.tsx index f539e2ba0b9..56d47c5b063 100644 --- a/apps/desktop/src/renderer/routes.tsx +++ b/apps/desktop/src/renderer/routes.tsx @@ -1,6 +1,5 @@ import { Router } from "lib/electron-router-dom"; import { Route, useRouteError } from "react-router-dom"; - import { MainScreen } from "./screens/main"; function ErrorPage() { diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeContent.tsx b/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeContent.tsx index 758b3a90cc2..c0d750d3a7b 100644 --- a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeContent.tsx @@ -1,5 +1,5 @@ import type { ReactNode } from "react"; -import type { SidebarMode } from "../types"; +import type { SidebarMode } from "./types"; import { ModeHeader } from "./ModeHeader"; interface ModeContentProps { diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/index.tsx b/apps/desktop/src/renderer/screens/main/components/TabView/index.tsx index 54bb07c0dc6..d204b16be56 100644 --- a/apps/desktop/src/renderer/screens/main/components/TabView/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TabView/index.tsx @@ -1,13 +1,15 @@ -import { useTabsStore } from "renderer/stores/tabs"; +import { useWorkspacesStore } from "renderer/stores/workspaces"; import { CenterView } from "./CenterView"; import { NewTabView } from "./NewTabView"; import { Sidebar } from "./Sidebar"; export function TabView() { - const { tabs, activeTabId } = useTabsStore(); - const activeTab = tabs.find((tab) => tab.id === activeTabId); + const { workspaces, activeWorkspaceId } = useWorkspacesStore(); + const activeWorkspace = workspaces.find( + (workspace) => workspace.id === activeWorkspaceId, + ); - if (activeTab?.isNew) { + if (activeWorkspace?.isNew) { return (
diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx deleted file mode 100644 index bf1560c99e4..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Button } from "@superset/ui/button"; -import { useTabsStore } from "renderer/stores/tabs"; - -export function AddTabButton() { - const { addTab } = useTabsStore(); - - return ( - - ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/AddWorkspaceButton.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/AddWorkspaceButton.tsx new file mode 100644 index 00000000000..78e81dd461e --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/AddWorkspaceButton.tsx @@ -0,0 +1,18 @@ +import { Button } from "@superset/ui/button"; +import { useWorkspacesStore } from "renderer/stores/workspaces"; + +export function AddWorkspaceButton() { + const { addWorkspace } = useWorkspacesStore(); + + return ( + + ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/TabItem.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/WorkspaceItem.tsx similarity index 77% rename from apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/TabItem.tsx rename to apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/WorkspaceItem.tsx index 064c74aa84c..df918f16438 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/TabItem.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/WorkspaceItem.tsx @@ -2,11 +2,11 @@ import { Button } from "@superset/ui/button"; import { cn } from "@superset/ui/utils"; import { useDrag, useDrop } from "react-dnd"; import { HiMiniXMark } from "react-icons/hi2"; -import { useTabsStore } from "renderer/stores/tabs"; +import { useWorkspacesStore } from "renderer/stores/workspaces"; -const TAB_TYPE = "TAB"; +const WORKSPACE_TYPE = "WORKSPACE"; -interface TabItemProps { +interface WorkspaceItemProps { id: string; title: string; isActive: boolean; @@ -16,7 +16,7 @@ interface TabItemProps { onMouseLeave?: () => void; } -export function TabItem({ +export function WorkspaceItem({ id, title, isActive, @@ -24,12 +24,13 @@ export function TabItem({ width, onMouseEnter, onMouseLeave, -}: TabItemProps) { - const { setActiveTab, removeTab, reorderTabs } = useTabsStore(); +}: WorkspaceItemProps) { + const { setActiveWorkspace, removeWorkspace, reorderWorkspaces } = + useWorkspacesStore(); const [{ isDragging }, drag] = useDrag( () => ({ - type: TAB_TYPE, + type: WORKSPACE_TYPE, item: { id, index }, collect: (monitor) => ({ isDragging: monitor.isDragging(), @@ -39,10 +40,10 @@ export function TabItem({ ); const [, drop] = useDrop({ - accept: TAB_TYPE, + accept: WORKSPACE_TYPE, hover: (item: { id: string; index: number }) => { if (item.index !== index) { - reorderTabs(item.index, index); + reorderWorkspaces(item.index, index); item.index = index; } }, @@ -53,16 +54,16 @@ export function TabItem({ className="group relative flex items-end shrink-0 h-full" style={{ width: `${width}px` }} > - {/* Active tab bottom border overlay */} + {/* Active workspace bottom border overlay */} {isActive &&
} - {/* Main tab button */} + {/* Main workspace button */} diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/index.tsx similarity index 60% rename from apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.tsx rename to apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/index.tsx index 39ef8a7de36..4d8ac95bea3 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/index.tsx @@ -1,21 +1,23 @@ import { Separator } from "@superset/ui/separator"; import { Fragment, useEffect, useRef, useState } from "react"; -import { useTabsStore } from "renderer/stores/tabs"; -import { AddTabButton } from "./AddTabButton"; -import { TabItem } from "./TabItem"; +import { useWorkspacesStore } from "renderer/stores/workspaces"; +import { AddWorkspaceButton } from "./AddWorkspaceButton"; +import { WorkspaceItem } from "./WorkspaceItem"; -const MIN_TAB_WIDTH = 60; -const MAX_TAB_WIDTH = 240; +const MIN_WORKSPACE_WIDTH = 60; +const MAX_WORKSPACE_WIDTH = 240; const ADD_BUTTON_WIDTH = 48; -export function Tabs() { - const { tabs, activeTabId } = useTabsStore(); +export function Workspaces() { + const { workspaces, activeWorkspaceId } = useWorkspacesStore(); const containerRef = useRef(null); const scrollRef = useRef(null); const [showStartFade, setShowStartFade] = useState(false); const [showEndFade, setShowEndFade] = useState(false); - const [tabWidth, setTabWidth] = useState(MAX_TAB_WIDTH); - const [hoveredTabId, setHoveredTabId] = useState(null); + const [workspaceWidth, setWorkspaceWidth] = useState(MAX_WORKSPACE_WIDTH); + const [hoveredWorkspaceId, setHoveredWorkspaceId] = useState( + null, + ); useEffect(() => { const checkScroll = () => { @@ -26,7 +28,7 @@ export function Tabs() { setShowEndFade(scrollLeft < scrollWidth - clientWidth - 1); }; - const updateTabWidth = () => { + const updateWorkspaceWidth = () => { if (!containerRef.current) return; const containerWidth = containerRef.current.offsetWidth; @@ -34,29 +36,29 @@ export function Tabs() { // Calculate width: fill available space but respect min/max const calculatedWidth = Math.max( - MIN_TAB_WIDTH, - Math.min(MAX_TAB_WIDTH, availableWidth / tabs.length), + MIN_WORKSPACE_WIDTH, + Math.min(MAX_WORKSPACE_WIDTH, availableWidth / workspaces.length), ); - setTabWidth(calculatedWidth); + setWorkspaceWidth(calculatedWidth); }; checkScroll(); - updateTabWidth(); + updateWorkspaceWidth(); const scrollElement = scrollRef.current; if (scrollElement) { scrollElement.addEventListener("scroll", checkScroll); } - window.addEventListener("resize", updateTabWidth); + window.addEventListener("resize", updateWorkspaceWidth); return () => { if (scrollElement) { scrollElement.removeEventListener("scroll", checkScroll); } - window.removeEventListener("resize", updateTabWidth); + window.removeEventListener("resize", updateWorkspaceWidth); }; - }, [tabs]); + }, [workspaces]); return (
- {tabs.map((tab, index) => { - const nextTab = tabs[index + 1]; - const isActive = tab.id === activeTabId; - const isNextActive = nextTab?.id === activeTabId; - const isHovered = tab.id === hoveredTabId; - const isNextHovered = nextTab?.id === hoveredTabId; + {workspaces.map((workspace, index) => { + const nextWorkspace = workspaces[index + 1]; + const isActive = workspace.id === activeWorkspaceId; + const isNextActive = nextWorkspace?.id === activeWorkspaceId; + const isHovered = workspace.id === hoveredWorkspaceId; + const isNextHovered = nextWorkspace?.id === hoveredWorkspaceId; const separatorOpacity = !isActive && !isNextActive && !isHovered && !isNextHovered ? 100 : 0; return ( - +
- setHoveredTabId(tab.id)} - onMouseLeave={() => setHoveredTabId(null)} + width={workspaceWidth} + onMouseEnter={() => setHoveredWorkspaceId(workspace.id)} + onMouseLeave={() => setHoveredWorkspaceId(null)} />
- {index < tabs.length - 1 && ( + {index < workspaces.length - 1 && (
- +
); } diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx index 12814cf85c9..f5a8bec86e9 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx @@ -1,6 +1,6 @@ import { trpc } from "renderer/lib/trpc"; import { SidebarControl } from "./SidebarControl"; -import { Tabs } from "./Tabs"; +import { Workspaces } from "./Workspaces"; import { WindowControls } from "./WindowControls"; export function TopBar() { @@ -17,7 +17,7 @@ export function TopBar() {
- +
{!isMac && } diff --git a/apps/desktop/src/renderer/screens/main/index.ts b/apps/desktop/src/renderer/screens/main/index.ts deleted file mode 100644 index a20cf4519fe..00000000000 --- a/apps/desktop/src/renderer/screens/main/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { MainScreen } from "./MainScreen"; diff --git a/apps/desktop/src/renderer/screens/main/MainScreen.tsx b/apps/desktop/src/renderer/screens/main/index.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/MainScreen.tsx rename to apps/desktop/src/renderer/screens/main/index.tsx diff --git a/apps/desktop/src/renderer/stores/index.ts b/apps/desktop/src/renderer/stores/index.ts index 7443ba32acf..6ba32b6a101 100644 --- a/apps/desktop/src/renderer/stores/index.ts +++ b/apps/desktop/src/renderer/stores/index.ts @@ -5,4 +5,4 @@ */ export * from "./sidebar-state"; -export * from "./tabs"; +export * from "./workspaces"; diff --git a/apps/desktop/src/renderer/stores/tabs.ts b/apps/desktop/src/renderer/stores/tabs.ts deleted file mode 100644 index 050237a7a8f..00000000000 --- a/apps/desktop/src/renderer/stores/tabs.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { create } from "zustand"; -import { devtools } from "zustand/middleware"; - -export interface Tab { - id: string; - title: string; - isNew?: boolean; -} - -interface TabsState { - tabs: Tab[]; - activeTabId: string | null; - - addTab: () => void; - removeTab: (id: string) => void; - setActiveTab: (id: string) => void; - reorderTabs: (startIndex: number, endIndex: number) => void; - markTabAsUsed: (id: string) => void; -} - -const createNewTab = (): Tab => ({ - id: `tab-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, - title: "New Tab", - isNew: true, -}); - -export const useTabsStore = create()( - devtools( - (set) => ({ - tabs: [{ id: "tab-1", title: "Home" }], - activeTabId: "tab-1", - - addTab: () => { - const newTab = createNewTab(); - set((state) => ({ - tabs: [...state.tabs, newTab], - activeTabId: newTab.id, - })); - }, - - removeTab: (id) => { - set((state) => { - const tabs = state.tabs.filter((tab) => tab.id !== id); - if (tabs.length === 0) { - const newTab = createNewTab(); - return { tabs: [newTab], activeTabId: newTab.id }; - } - - if (id === state.activeTabId) { - const closedIndex = state.tabs.findIndex((tab) => tab.id === id); - const nextTab = tabs[closedIndex] || tabs[closedIndex - 1]; - return { tabs, activeTabId: nextTab.id }; - } - - return { tabs }; - }); - }, - - setActiveTab: (id) => { - set({ activeTabId: id }); - }, - - reorderTabs: (startIndex, endIndex) => { - set((state) => { - const tabs = [...state.tabs]; - const [removed] = tabs.splice(startIndex, 1); - tabs.splice(endIndex, 0, removed); - return { tabs }; - }); - }, - - markTabAsUsed: (id) => { - set((state) => ({ - tabs: state.tabs.map((tab) => - tab.id === id ? { ...tab, isNew: false } : tab, - ), - })); - }, - }), - { name: "TabsStore" }, - ), -); diff --git a/apps/desktop/src/renderer/stores/workspaces.ts b/apps/desktop/src/renderer/stores/workspaces.ts new file mode 100644 index 00000000000..f511778cf74 --- /dev/null +++ b/apps/desktop/src/renderer/stores/workspaces.ts @@ -0,0 +1,90 @@ +import { create } from "zustand"; +import { devtools } from "zustand/middleware"; + +export interface Workspace { + id: string; + title: string; + isNew?: boolean; +} + +interface WorkspacesState { + workspaces: Workspace[]; + activeWorkspaceId: string | null; + + addWorkspace: () => void; + removeWorkspace: (id: string) => void; + setActiveWorkspace: (id: string) => void; + reorderWorkspaces: (startIndex: number, endIndex: number) => void; + markWorkspaceAsUsed: (id: string) => void; +} + +const createNewWorkspace = (): Workspace => ({ + id: `workspace-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, + title: "New Workspace", + isNew: true, +}); + +export const useWorkspacesStore = create()( + devtools( + (set) => ({ + workspaces: [{ id: "workspace-1", title: "Home" }], + activeWorkspaceId: "workspace-1", + + addWorkspace: () => { + const newWorkspace = createNewWorkspace(); + set((state) => ({ + workspaces: [...state.workspaces, newWorkspace], + activeWorkspaceId: newWorkspace.id, + })); + }, + + removeWorkspace: (id) => { + set((state) => { + const workspaces = state.workspaces.filter( + (workspace) => workspace.id !== id, + ); + if (workspaces.length === 0) { + const newWorkspace = createNewWorkspace(); + return { + workspaces: [newWorkspace], + activeWorkspaceId: newWorkspace.id, + }; + } + + if (id === state.activeWorkspaceId) { + const closedIndex = state.workspaces.findIndex( + (workspace) => workspace.id === id, + ); + const nextWorkspace = + workspaces[closedIndex] || workspaces[closedIndex - 1]; + return { workspaces, activeWorkspaceId: nextWorkspace.id }; + } + + return { workspaces }; + }); + }, + + setActiveWorkspace: (id) => { + set({ activeWorkspaceId: id }); + }, + + reorderWorkspaces: (startIndex, endIndex) => { + set((state) => { + const workspaces = [...state.workspaces]; + const [removed] = workspaces.splice(startIndex, 1); + workspaces.splice(endIndex, 0, removed); + return { workspaces }; + }); + }, + + markWorkspaceAsUsed: (id) => { + set((state) => ({ + workspaces: state.workspaces.map((workspace) => + workspace.id === id ? { ...workspace, isNew: false } : workspace, + ), + })); + }, + }), + { name: "WorkspacesStore" }, + ), +); From 71fda0586787eff7dde5950bedb09128b1cbcddb Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 19 Nov 2025 15:12:44 -0800 Subject: [PATCH 03/21] workspace refactor --- .../TabView/{NewTabView.tsx => NewWorkspaceView.tsx} | 6 ++++-- .../src/renderer/screens/main/components/TabView/index.tsx | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) rename apps/desktop/src/renderer/screens/main/components/TabView/{NewTabView.tsx => NewWorkspaceView.tsx} (93%) diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/NewTabView.tsx b/apps/desktop/src/renderer/screens/main/components/TabView/NewWorkspaceView.tsx similarity index 93% rename from apps/desktop/src/renderer/screens/main/components/TabView/NewTabView.tsx rename to apps/desktop/src/renderer/screens/main/components/TabView/NewWorkspaceView.tsx index 576bff135ce..635937067c4 100644 --- a/apps/desktop/src/renderer/screens/main/components/TabView/NewTabView.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TabView/NewWorkspaceView.tsx @@ -1,8 +1,10 @@ -export function NewTabView() { +export function NewWorkspaceView() { return (
-

New Tab

+

+ New Workspace +

Start by selecting an action or creating something new

diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/index.tsx b/apps/desktop/src/renderer/screens/main/components/TabView/index.tsx index d204b16be56..007315a2958 100644 --- a/apps/desktop/src/renderer/screens/main/components/TabView/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TabView/index.tsx @@ -1,6 +1,6 @@ import { useWorkspacesStore } from "renderer/stores/workspaces"; import { CenterView } from "./CenterView"; -import { NewTabView } from "./NewTabView"; +import { NewWorkspaceView } from "./NewWorkspaceView"; import { Sidebar } from "./Sidebar"; export function TabView() { @@ -12,7 +12,7 @@ export function TabView() { if (activeWorkspace?.isNew) { return (
- +
); } From 157e9a51d028a25adf6aa163454e4660dd6ae3aa Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 19 Nov 2025 15:17:13 -0800 Subject: [PATCH 04/21] workspace refactor --- .../AddWorkspaceButton.tsx | 0 .../WorkspaceItem.tsx | 0 .../{Workspaces => WorkspaceTabs}/index.tsx | 2 +- .../screens/main/components/TopBar/index.tsx | 4 +-- .../{TabView => WorkspaceView}/CenterView.tsx | 0 .../NewWorkspaceView.tsx | 0 .../WorkspaceView/Sidebar/ChangesView.tsx | 8 +++++ .../ModeCarousel/AnimatedBackground.tsx | 0 .../Sidebar/ModeCarousel/ModeContent.tsx | 0 .../Sidebar/ModeCarousel/ModeHeader.tsx | 0 .../Sidebar/ModeCarousel/ModeNavigation.tsx | 0 .../Sidebar/ModeCarousel/constants.ts | 0 .../ModeCarousel/hooks/useModeDetection.ts | 0 .../ModeCarousel/hooks/useScrollProgress.ts | 0 .../ModeCarousel/hooks/useScrollSnap.ts | 0 .../Sidebar/ModeCarousel/index.tsx | 0 .../Sidebar/ModeCarousel/types.ts | 0 .../WorkspaceView/Sidebar/TabsView.tsx | 21 +++++++++++++ .../Sidebar/index.tsx | 30 ++++--------------- .../{TabView => WorkspaceView}/index.tsx | 2 +- .../src/renderer/screens/main/index.tsx | 4 +-- 21 files changed, 40 insertions(+), 31 deletions(-) rename apps/desktop/src/renderer/screens/main/components/TopBar/{Workspaces => WorkspaceTabs}/AddWorkspaceButton.tsx (100%) rename apps/desktop/src/renderer/screens/main/components/TopBar/{Workspaces => WorkspaceTabs}/WorkspaceItem.tsx (100%) rename apps/desktop/src/renderer/screens/main/components/TopBar/{Workspaces => WorkspaceTabs}/index.tsx (99%) rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/CenterView.tsx (100%) rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/NewWorkspaceView.tsx (100%) create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/Sidebar/ModeCarousel/AnimatedBackground.tsx (100%) rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/Sidebar/ModeCarousel/ModeContent.tsx (100%) rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/Sidebar/ModeCarousel/ModeHeader.tsx (100%) rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/Sidebar/ModeCarousel/ModeNavigation.tsx (100%) rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/Sidebar/ModeCarousel/constants.ts (100%) rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/Sidebar/ModeCarousel/hooks/useModeDetection.ts (100%) rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/Sidebar/ModeCarousel/hooks/useScrollProgress.ts (100%) rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/Sidebar/ModeCarousel/hooks/useScrollSnap.ts (100%) rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/Sidebar/ModeCarousel/index.tsx (100%) rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/Sidebar/ModeCarousel/types.ts (100%) create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView.tsx rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/Sidebar/index.tsx (56%) rename apps/desktop/src/renderer/screens/main/components/{TabView => WorkspaceView}/index.tsx (94%) diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/AddWorkspaceButton.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/AddWorkspaceButton.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/AddWorkspaceButton.tsx rename to apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/AddWorkspaceButton.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/WorkspaceItem.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/WorkspaceItem.tsx rename to apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/index.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/index.tsx similarity index 99% rename from apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/index.tsx rename to apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/index.tsx index 4d8ac95bea3..20c8d91559a 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/Workspaces/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/index.tsx @@ -8,7 +8,7 @@ const MIN_WORKSPACE_WIDTH = 60; const MAX_WORKSPACE_WIDTH = 240; const ADD_BUTTON_WIDTH = 48; -export function Workspaces() { +export function WorkspacesTabs() { const { workspaces, activeWorkspaceId } = useWorkspacesStore(); const containerRef = useRef(null); const scrollRef = useRef(null); diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx index f5a8bec86e9..ca954d4bbc6 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx @@ -1,6 +1,6 @@ import { trpc } from "renderer/lib/trpc"; import { SidebarControl } from "./SidebarControl"; -import { Workspaces } from "./Workspaces"; +import { WorkspacesTabs } from "./WorkspaceTabs"; import { WindowControls } from "./WindowControls"; export function TopBar() { @@ -17,7 +17,7 @@ export function TopBar() {
- +
{!isMac && } diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/CenterView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/CenterView.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TabView/CenterView.tsx rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/CenterView.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/NewWorkspaceView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/NewWorkspaceView.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TabView/NewWorkspaceView.tsx rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/NewWorkspaceView.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx new file mode 100644 index 00000000000..b672d93726f --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx @@ -0,0 +1,8 @@ +export function ChangesView() { + return ( +
+ Changes view coming soon... +
+ ); +} + diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/AnimatedBackground.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/AnimatedBackground.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/AnimatedBackground.tsx rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/AnimatedBackground.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeContent.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeContent.tsx rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeContent.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeHeader.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeHeader.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeHeader.tsx rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeHeader.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeNavigation.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeNavigation.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeNavigation.tsx rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeNavigation.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/constants.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/constants.ts similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/constants.ts rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/constants.ts diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/hooks/useModeDetection.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/hooks/useModeDetection.ts similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/hooks/useModeDetection.ts rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/hooks/useModeDetection.ts diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/hooks/useScrollProgress.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/hooks/useScrollProgress.ts similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/hooks/useScrollProgress.ts rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/hooks/useScrollProgress.ts diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/hooks/useScrollSnap.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/hooks/useScrollSnap.ts similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/hooks/useScrollSnap.ts rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/hooks/useScrollSnap.ts diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/index.tsx similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/index.tsx rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/index.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/types.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/types.ts similarity index 100% rename from apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/types.ts rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/types.ts diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView.tsx new file mode 100644 index 00000000000..63cdf81573b --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView.tsx @@ -0,0 +1,21 @@ +export function TabsView() { + return ( + + ); +} + diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx similarity index 56% rename from apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/index.tsx rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx index bcdb756af4a..ac30f54a278 100644 --- a/apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx @@ -1,7 +1,9 @@ import { motion } from "framer-motion"; import { useSidebarStore } from "renderer/stores"; import { SidebarMode } from "renderer/stores/sidebar-state"; +import { ChangesView } from "./ChangesView"; import { ModeCarousel } from "./ModeCarousel"; +import { TabsView } from "./TabsView"; export function Sidebar() { const { isSidebarOpen, currentMode, setMode } = useSidebarStore(); @@ -40,33 +42,11 @@ export function Sidebar() { onModeSelect={setMode} > {(mode) => { - if (mode === "changes") { - return ( -
- Changes view coming soon... -
- ); + if (mode === SidebarMode.Changes) { + return ; } - // Tabs mode - return ( - - ); + return ; }} diff --git a/apps/desktop/src/renderer/screens/main/components/TabView/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx similarity index 94% rename from apps/desktop/src/renderer/screens/main/components/TabView/index.tsx rename to apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx index 007315a2958..6f645b9c676 100644 --- a/apps/desktop/src/renderer/screens/main/components/TabView/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx @@ -3,7 +3,7 @@ import { CenterView } from "./CenterView"; import { NewWorkspaceView } from "./NewWorkspaceView"; import { Sidebar } from "./Sidebar"; -export function TabView() { +export function WorkspaceView() { const { workspaces, activeWorkspaceId } = useWorkspacesStore(); const activeWorkspace = workspaces.find( (workspace) => workspace.id === activeWorkspaceId, diff --git a/apps/desktop/src/renderer/screens/main/index.tsx b/apps/desktop/src/renderer/screens/main/index.tsx index b483bb29383..2ec926168d5 100644 --- a/apps/desktop/src/renderer/screens/main/index.tsx +++ b/apps/desktop/src/renderer/screens/main/index.tsx @@ -2,7 +2,7 @@ import { DndProvider } from "react-dnd"; import { dragDropManager } from "../../lib/dnd"; import { AppFrame } from "./components/AppFrame"; import { Background } from "./components/Background"; -import { TabView } from "./components/TabView"; +import { WorkspaceView } from "./components/WorkspaceView"; import { TopBar } from "./components/TopBar"; export function MainScreen() { @@ -13,7 +13,7 @@ export function MainScreen() {
- +
From 6dc7c34ec2f99c266ab54336d4208798ef6ea5e5 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 19 Nov 2025 15:20:38 -0800 Subject: [PATCH 05/21] content view --- .../components/WorkspaceView/CenterView.tsx | 26 ------------------- .../ContentView/ChangesContent.tsx | 19 ++++++++++++++ .../WorkspaceView/ContentView/TabsContent.tsx | 19 ++++++++++++++ .../WorkspaceView/ContentView/index.tsx | 13 ++++++++++ .../main/components/WorkspaceView/index.tsx | 4 +-- 5 files changed, 53 insertions(+), 28 deletions(-) delete mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/CenterView.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/index.tsx diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/CenterView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/CenterView.tsx deleted file mode 100644 index a1cd4361108..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/CenterView.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type { ReactNode } from "react"; - -interface CenterViewProps { - children?: ReactNode; -} - -export function CenterView({ children }: CenterViewProps) { - return ( -
-
- {children || ( -
-
-

- Welcome to Superset -

-

- Your content will appear here -

-
-
- )} -
-
- ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx new file mode 100644 index 00000000000..436638b6bcc --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx @@ -0,0 +1,19 @@ +export function ChangesContent() { + return ( +
+
+
+
+

+ Changes +

+

+ Changes content will appear here +

+
+
+
+
+ ); +} + diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx new file mode 100644 index 00000000000..29a5e101dfa --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx @@ -0,0 +1,19 @@ +export function TabsContent() { + return ( +
+
+
+
+

+ Tabs +

+

+ Tabs content will appear here +

+
+
+
+
+ ); +} + diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/index.tsx new file mode 100644 index 00000000000..5a42abde6be --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/index.tsx @@ -0,0 +1,13 @@ +import { SidebarMode, useSidebarStore } from "renderer/stores"; +import { ChangesContent } from "./ChangesContent"; +import { TabsContent } from "./TabsContent"; + +export function ContentView() { + const { currentMode } = useSidebarStore(); + + if (currentMode === SidebarMode.Changes) { + return ; + } + + return ; +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx index 6f645b9c676..4ccffc77521 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx @@ -1,5 +1,5 @@ import { useWorkspacesStore } from "renderer/stores/workspaces"; -import { CenterView } from "./CenterView"; +import { ContentView } from "./ContentView"; import { NewWorkspaceView } from "./NewWorkspaceView"; import { Sidebar } from "./Sidebar"; @@ -20,7 +20,7 @@ export function WorkspaceView() { return (
- +
); } From af52760fb8a3be5a3e006b6763f03072b13c9107 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 19 Nov 2025 15:21:41 -0800 Subject: [PATCH 06/21] states --- .../components/WorkspaceView/ContentView/ChangesContent.tsx | 5 +---- .../main/components/WorkspaceView/Sidebar/ChangesView.tsx | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx index 436638b6bcc..e61c3a4cb38 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx @@ -7,13 +7,10 @@ export function ChangesContent() {

Changes

-

- Changes content will appear here -

+

Coming soon...

); } - diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx index b672d93726f..178ceffedd4 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx @@ -1,8 +1,7 @@ export function ChangesView() { return (
- Changes view coming soon... + Coming soon...
); } - From 27edf10a2b6942daa27cfd31fc1d11872a8c5b4c Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 19 Nov 2025 15:58:42 -0800 Subject: [PATCH 07/21] tab dragging --- .../screens/main/components/TopBar/index.tsx | 2 +- .../WorkspaceView/ContentView/TabsContent.tsx | 87 +++++- .../Sidebar/ModeCarousel/ModeContent.tsx | 2 +- .../Sidebar/ModeCarousel/ModeNavigation.tsx | 2 +- .../Sidebar/ModeCarousel/index.tsx | 6 +- .../WorkspaceView/Sidebar/TabsView.tsx | 21 -- .../Sidebar/TabsView/TabItem.tsx | 108 ++++++++ .../WorkspaceView/Sidebar/TabsView/index.tsx | 61 +++++ .../src/renderer/screens/main/index.tsx | 2 +- apps/desktop/src/renderer/stores/index.ts | 1 + .../src/renderer/stores/tabs/drag-logic.ts | 118 ++++++++ .../desktop/src/renderer/stores/tabs/index.ts | 4 + .../desktop/src/renderer/stores/tabs/store.ts | 253 ++++++++++++++++++ .../desktop/src/renderer/stores/tabs/types.ts | 32 +++ .../desktop/src/renderer/stores/tabs/utils.ts | 40 +++ apps/old-desktop/src/main/index.ts | 2 +- .../src/main/lib/desktop-stores.ts | 6 +- .../src/main/lib/migration/migrator.ts | 10 +- .../old-desktop/src/main/lib/storage/index.ts | 2 +- .../src/main/lib/storage/lowdb-adapter.ts | 2 +- .../main/lib/storage/orchestrators/index.ts | 4 +- .../src/main/lib/types/cli-types.ts | 34 ++- .../src/main/lib/ui-store/index.ts | 2 +- .../src/main/lib/ui-store/store.ts | 10 +- .../src/main/lib/ui-store/types.ts | 2 +- .../lib/workspace-composition/composer.ts | 4 +- .../main/lib/workspace-composition/index.ts | 2 +- .../main/lib/workspace-composition/types.ts | 2 +- .../src/main/lib/workspace-ipcs.ts | 2 +- .../src/renderer/contexts/AppProviders.tsx | 6 +- .../src/renderer/contexts/TabContext.tsx | 2 +- .../src/renderer/contexts/TaskContext.tsx | 4 +- .../contexts/WorktreeOperationsContext.tsx | 2 +- .../src/renderer/contexts/index.ts | 10 +- .../src/renderer/screens/main/MainScreen.tsx | 20 +- .../Layout/AddTaskModal/CreatingView.tsx | 2 +- .../Layout/AddTaskModal/TaskList.tsx | 2 +- .../components/Layout/AddTaskModal/index.ts | 2 +- .../components/Layout/AddTaskModal/types.ts | 2 +- .../Layout/AddTaskModal/useTaskForm.ts | 2 +- .../components/Layout/AddTaskModal/utils.ts | 2 +- .../components/MainContent/TabContent.tsx | 2 +- .../main/components/MainContent/TabGroup.tsx | 4 +- .../MainContentArea/MainContentArea.tsx | 8 +- .../main/components/PlanView/PlanView.tsx | 4 +- .../main/components/Sidebar/Sidebar.tsx | 6 +- .../Sidebar/components/ModeCarousel/index.ts | 2 +- .../components/WorktreeItem/WorktreeItem.tsx | 2 +- .../WorktreeItem/WorktreeItemArborist.tsx | 15 +- .../renderer/screens/main/hooks/useTasks.ts | 4 +- .../src/renderer/screens/main/utils.ts | 2 +- .../src/shared/ipc-channels/index.ts | 2 +- .../old-desktop/src/shared/ipc-channels/ui.ts | 2 +- .../components/HeroSection/HeroSection.tsx | 2 +- .../LitBackground/LitBackground.tsx | 6 +- apps/website/src/app/page.tsx | 6 +- packages/ui/src/components/context-menu.tsx | 2 +- packages/ui/src/components/dialog.tsx | 2 +- packages/ui/src/components/dropdown-menu.tsx | 2 +- packages/ui/src/components/resizable.tsx | 2 +- packages/ui/src/components/select.tsx | 2 +- packages/ui/src/components/separator.tsx | 2 +- 62 files changed, 813 insertions(+), 145 deletions(-) delete mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx create mode 100644 apps/desktop/src/renderer/stores/tabs/drag-logic.ts create mode 100644 apps/desktop/src/renderer/stores/tabs/index.ts create mode 100644 apps/desktop/src/renderer/stores/tabs/store.ts create mode 100644 apps/desktop/src/renderer/stores/tabs/types.ts create mode 100644 apps/desktop/src/renderer/stores/tabs/utils.ts diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx index ca954d4bbc6..1e0f2b95928 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx @@ -1,7 +1,7 @@ import { trpc } from "renderer/lib/trpc"; import { SidebarControl } from "./SidebarControl"; -import { WorkspacesTabs } from "./WorkspaceTabs"; import { WindowControls } from "./WindowControls"; +import { WorkspacesTabs } from "./WorkspaceTabs"; export function TopBar() { const { data: platform } = trpc.window.getPlatform.useQuery(); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx index 29a5e101dfa..260ff372f57 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx @@ -1,19 +1,94 @@ +import { useMemo } from "react"; +import { + TabType, + useActiveTabIds, + useTabs, + useWorkspacesStore, +} from "renderer/stores"; + export function TabsContent() { + const activeWorkspaceId = useWorkspacesStore( + (state) => state.activeWorkspaceId, + ); + const allTabs = useTabs(); + const activeTabIds = useActiveTabIds(); + + const activeTab = useMemo(() => { + if (!activeWorkspaceId) return null; + const activeTabId = activeTabIds[activeWorkspaceId]; + if (!activeTabId) return null; + return allTabs.find((tab) => tab.id === activeTabId) || null; + }, [activeWorkspaceId, activeTabIds, allTabs]); + + if (!activeTab) { + return ( +
+
+
+
+

+ No Active Tab +

+

+ Create a new tab to get started +

+
+
+
+
+ ); + } + + // Render different content based on tab type + if (activeTab.type === TabType.Single) { + return ( +
+
+
+
+

+ {activeTab.title} +

+

Single tab view

+
+
+

+ Tab content will appear here +

+
+
+
+
+ ); + } + + // Tab group view with react-mosaic return (
-
-
-

- Tabs +
+
+

+ {activeTab.title}

+

+ Split view - {Object.keys(activeTab.panes).length} panes +

+
+

- Tabs content will appear here + React-mosaic split view will appear here

+
+ {Object.entries(activeTab.panes).map(([paneId, pane]) => ( +
+ - {pane.title} ({paneId}) +
+ ))} +

); } - diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeContent.tsx index c0d750d3a7b..dccfe037359 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeContent.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from "react"; -import type { SidebarMode } from "./types"; import { ModeHeader } from "./ModeHeader"; +import type { SidebarMode } from "./types"; interface ModeContentProps { mode: SidebarMode; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeNavigation.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeNavigation.tsx index e1b31261ae7..83c734e3770 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeNavigation.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeNavigation.tsx @@ -1,7 +1,7 @@ import type { MotionValue } from "framer-motion"; +import { AnimatedBackground } from "./AnimatedBackground"; import { modeIcons, modeLabels } from "./constants"; import type { SidebarMode } from "./types"; -import { AnimatedBackground } from "./AnimatedBackground"; interface ModeNavigationProps { modes: SidebarMode[]; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/index.tsx index 3eb29b4fe4d..a40de159b53 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/index.tsx @@ -1,10 +1,10 @@ import { useCallback, useRef, useState } from "react"; -import { ModeContent } from "./ModeContent"; -import { ModeHeader } from "./ModeHeader"; -import { ModeNavigation } from "./ModeNavigation"; import { useModeDetection } from "./hooks/useModeDetection"; import { useScrollProgress } from "./hooks/useScrollProgress"; import { useScrollSnap } from "./hooks/useScrollSnap"; +import { ModeContent } from "./ModeContent"; +import { ModeHeader } from "./ModeHeader"; +import { ModeNavigation } from "./ModeNavigation"; import type { ModeCarouselProps } from "./types"; export function ModeCarousel({ diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView.tsx deleted file mode 100644 index 63cdf81573b..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView.tsx +++ /dev/null @@ -1,21 +0,0 @@ -export function TabsView() { - return ( - - ); -} - diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem.tsx new file mode 100644 index 00000000000..de0d1235163 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem.tsx @@ -0,0 +1,108 @@ +import { Button } from "@superset/ui/button"; +import { useState } from "react"; +import { HiMiniXMark } from "react-icons/hi2"; +import { + useRemoveTab, + useSetActiveTab, + useTabsStore, + useWorkspacesStore, +} from "renderer/stores"; + +interface TabItemProps { + tabId: string; + title: string; + isActive: boolean; +} + +export function TabItem({ tabId, title, isActive }: TabItemProps) { + const activeWorkspaceId = useWorkspacesStore( + (state) => state.activeWorkspaceId, + ); + const removeTab = useRemoveTab(); + const setActiveTab = useSetActiveTab(); + const dragTabToTab = useTabsStore((state) => state.dragTabToTab); + + const [isDragging, setIsDragging] = useState(false); + const [isDragOver, setIsDragOver] = useState(false); + + const handleRemoveTab = (e: React.MouseEvent) => { + e.stopPropagation(); + removeTab(tabId); + }; + + const handleTabClick = () => { + if (activeWorkspaceId) { + setActiveTab(activeWorkspaceId, tabId); + } + }; + + const handleDragStart = (e: React.DragEvent) => { + setIsDragging(true); + e.dataTransfer.effectAllowed = "move"; + e.dataTransfer.setData("application/x-tab-id", tabId); + }; + + const handleDragEnd = () => { + setIsDragging(false); + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + }; + + const handleDragEnter = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(true); + }; + + const handleDragLeave = () => { + setIsDragOver(false); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(false); + + const draggedTabId = e.dataTransfer.getData("application/x-tab-id"); + if (draggedTabId && draggedTabId !== tabId) { + dragTabToTab(draggedTabId, tabId); + } + }; + + return ( + + + ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx new file mode 100644 index 00000000000..d57271734ad --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx @@ -0,0 +1,61 @@ +import { Button } from "@superset/ui/button"; +import { useMemo } from "react"; +import { HiMiniPlus } from "react-icons/hi2"; +import { + useActiveTabIds, + useAddTab, + useTabs, + useWorkspacesStore, +} from "renderer/stores"; +import { TabItem } from "./TabItem"; + +export function TabsView() { + const activeWorkspaceId = useWorkspacesStore( + (state) => state.activeWorkspaceId, + ); + const allTabs = useTabs(); + const activeTabIds = useActiveTabIds(); + const addTab = useAddTab(); + + const tabs = useMemo( + () => + activeWorkspaceId + ? allTabs.filter((tab) => tab.workspaceId === activeWorkspaceId) + : [], + [activeWorkspaceId, allTabs], + ); + + const activeTabId = activeWorkspaceId + ? activeTabIds[activeWorkspaceId] + : null; + + const handleAddTab = () => { + if (activeWorkspaceId) { + addTab(activeWorkspaceId); + } + }; + + return ( + + ); +} diff --git a/apps/desktop/src/renderer/screens/main/index.tsx b/apps/desktop/src/renderer/screens/main/index.tsx index 2ec926168d5..0c14dd80a27 100644 --- a/apps/desktop/src/renderer/screens/main/index.tsx +++ b/apps/desktop/src/renderer/screens/main/index.tsx @@ -2,8 +2,8 @@ import { DndProvider } from "react-dnd"; import { dragDropManager } from "../../lib/dnd"; import { AppFrame } from "./components/AppFrame"; import { Background } from "./components/Background"; -import { WorkspaceView } from "./components/WorkspaceView"; import { TopBar } from "./components/TopBar"; +import { WorkspaceView } from "./components/WorkspaceView"; export function MainScreen() { return ( diff --git a/apps/desktop/src/renderer/stores/index.ts b/apps/desktop/src/renderer/stores/index.ts index 6ba32b6a101..298fd3f1375 100644 --- a/apps/desktop/src/renderer/stores/index.ts +++ b/apps/desktop/src/renderer/stores/index.ts @@ -5,4 +5,5 @@ */ export * from "./sidebar-state"; +export * from "./tabs"; // Now exports from tabs/index.ts export * from "./workspaces"; diff --git a/apps/desktop/src/renderer/stores/tabs/drag-logic.ts b/apps/desktop/src/renderer/stores/tabs/drag-logic.ts new file mode 100644 index 00000000000..696d83ce092 --- /dev/null +++ b/apps/desktop/src/renderer/stores/tabs/drag-logic.ts @@ -0,0 +1,118 @@ +import { type Tab, type TabGroup, TabType } from "./types"; +import { createNewTab } from "./utils"; + +export interface DragTabToTabResult { + tabs: Tab[]; + activeTabIds: Record; + tabHistoryStacks: Record; +} + +export const handleDragTabToTab = ( + draggedTabId: string, + targetTabId: string, + state: { + tabs: Tab[]; + activeTabIds: Record; + tabHistoryStacks: Record; + }, +): DragTabToTabResult => { + const draggedTab = state.tabs.find((tab) => tab.id === draggedTabId); + const targetTab = state.tabs.find((tab) => tab.id === targetTabId); + + if (!draggedTab || !targetTab) return state; + + const workspaceId = draggedTab.workspaceId; + const historyStack = state.tabHistoryStacks[workspaceId] || []; + + // Rule 1: Dragging into itself - create new tab + if (draggedTabId === targetTabId) { + const newTab = createNewTab(workspaceId, TabType.Single); + return { + ...state, + tabs: [...state.tabs, newTab], + activeTabIds: { + ...state.activeTabIds, + [workspaceId]: newTab.id, + }, + }; + } + + // Rule 2: Dragging into an existing group - join the group + if (targetTab.type === TabType.Group && draggedTab.type === TabType.Single) { + const newPaneId = `pane-${Date.now()}`; + const updatedTargetTab: TabGroup = { + ...targetTab, + panes: { + ...targetTab.panes, + [newPaneId]: { title: draggedTab.title }, + }, + // Recompute layout - add new pane as split + layout: { + direction: "row", + first: targetTab.layout, + second: newPaneId, + splitPercentage: 50, + }, + }; + + return { + ...state, + tabs: state.tabs + .map((tab) => (tab.id === targetTabId ? updatedTargetTab : tab)) + .filter((tab) => tab.id !== draggedTabId), + activeTabIds: { + ...state.activeTabIds, + [workspaceId]: targetTabId, + }, + tabHistoryStacks: { + ...state.tabHistoryStacks, + [workspaceId]: historyStack.filter((id) => id !== draggedTabId), + }, + }; + } + + // Rule 3: Dragging into a different single tab - create new group + if (targetTab.type === TabType.Single && draggedTab.type === TabType.Single) { + const pane1 = `pane-${Date.now()}-1`; + const pane2 = `pane-${Date.now()}-2`; + + const newGroupTab: TabGroup = { + id: `tab-${Date.now()}-group`, + title: `${targetTab.title} + ${draggedTab.title}`, + workspaceId, + type: TabType.Group, + layout: { + direction: "row", + first: pane1, + second: pane2, + splitPercentage: 50, + }, + panes: { + [pane1]: { title: targetTab.title }, + [pane2]: { title: draggedTab.title }, + }, + }; + + return { + ...state, + tabs: [ + ...state.tabs.filter( + (tab) => tab.id !== targetTabId && tab.id !== draggedTabId, + ), + newGroupTab, + ], + activeTabIds: { + ...state.activeTabIds, + [workspaceId]: newGroupTab.id, + }, + tabHistoryStacks: { + ...state.tabHistoryStacks, + [workspaceId]: historyStack.filter( + (id) => id !== draggedTabId && id !== targetTabId, + ), + }, + }; + } + + return state; +}; diff --git a/apps/desktop/src/renderer/stores/tabs/index.ts b/apps/desktop/src/renderer/stores/tabs/index.ts new file mode 100644 index 00000000000..3d58be97d2c --- /dev/null +++ b/apps/desktop/src/renderer/stores/tabs/index.ts @@ -0,0 +1,4 @@ +export * from "./drag-logic"; +export * from "./store"; +export * from "./types"; +export * from "./utils"; diff --git a/apps/desktop/src/renderer/stores/tabs/store.ts b/apps/desktop/src/renderer/stores/tabs/store.ts new file mode 100644 index 00000000000..7113eef97a4 --- /dev/null +++ b/apps/desktop/src/renderer/stores/tabs/store.ts @@ -0,0 +1,253 @@ +import type { MosaicNode } from "react-mosaic-component"; +import { create } from "zustand"; +import { devtools } from "zustand/middleware"; +import { handleDragTabToTab } from "./drag-logic"; +import { type Tab, TabType } from "./types"; +import { createNewTab } from "./utils"; + +interface TabsState { + // All tabs across all workspaces + tabs: Tab[]; + // Active tab ID per workspace + activeTabIds: Record; + // Tab history stack per workspace (ordered set - most recent first, no duplicates) + tabHistoryStacks: Record; + + // Tab management + addTab: (workspaceId: string, type?: TabType) => void; + removeTab: (id: string) => void; + setActiveTab: (workspaceId: string, tabId: string) => void; + reorderTabs: ( + workspaceId: string, + startIndex: number, + endIndex: number, + ) => void; + markTabAsUsed: (id: string) => void; + + // Tab group specific actions + updateTabGroupLayout: (id: string, layout: MosaicNode) => void; + addPaneToTabGroup: (id: string, paneId: string, title: string) => void; + removePaneFromTabGroup: (id: string, paneId: string) => void; + + // Drag and drop actions + dragTabToTab: (draggedTabId: string, targetTabId: string) => void; + + // Helper to get tabs for a specific workspace + getTabsByWorkspace: (workspaceId: string) => Tab[]; + getActiveTab: (workspaceId: string) => Tab | null; + getLastActiveTabId: (workspaceId: string) => string | null; +} + +export const useTabsStore = create()( + devtools( + (set, get) => ({ + tabs: [], + activeTabIds: {}, + tabHistoryStacks: {}, + + addTab: (workspaceId, type = TabType.Single) => { + const newTab = createNewTab(workspaceId, type); + set((state) => { + // Push current active tab to history before switching + const currentActiveId = state.activeTabIds[workspaceId]; + const historyStack = state.tabHistoryStacks[workspaceId] || []; + const newHistoryStack = currentActiveId + ? [ + currentActiveId, + ...historyStack.filter((id) => id !== currentActiveId), + ] + : historyStack; + + return { + tabs: [...state.tabs, newTab], + activeTabIds: { + ...state.activeTabIds, + [workspaceId]: newTab.id, + }, + tabHistoryStacks: { + ...state.tabHistoryStacks, + [workspaceId]: newHistoryStack, + }, + }; + }); + }, + + removeTab: (id) => { + set((state) => { + const tabToRemove = state.tabs.find((tab) => tab.id === id); + if (!tabToRemove) return state; + + const workspaceId = tabToRemove.workspaceId; + const workspaceTabs = state.tabs.filter( + (tab) => tab.workspaceId === workspaceId && tab.id !== id, + ); + const tabs = state.tabs.filter((tab) => tab.id !== id); + + // Remove from history stack + const historyStack = state.tabHistoryStacks[workspaceId] || []; + const newHistoryStack = historyStack.filter((tabId) => tabId !== id); + + // If removing active tab, use history stack to determine next tab + const newActiveTabIds = { ...state.activeTabIds }; + if (state.activeTabIds[workspaceId] === id) { + if (workspaceTabs.length > 0) { + // Try to activate most recent tab from history + const nextTabFromHistory = newHistoryStack.find((tabId) => + workspaceTabs.some((tab) => tab.id === tabId), + ); + if (nextTabFromHistory) { + newActiveTabIds[workspaceId] = nextTabFromHistory; + } else { + // Fallback to positional logic + const closedIndex = state.tabs + .filter((tab) => tab.workspaceId === workspaceId) + .findIndex((tab) => tab.id === id); + const nextTab = + workspaceTabs[closedIndex] || workspaceTabs[closedIndex - 1]; + newActiveTabIds[workspaceId] = nextTab.id; + } + } else { + newActiveTabIds[workspaceId] = null; + } + } + + return { + tabs, + activeTabIds: newActiveTabIds, + tabHistoryStacks: { + ...state.tabHistoryStacks, + [workspaceId]: newHistoryStack, + }, + }; + }); + }, + + setActiveTab: (workspaceId, tabId) => { + set((state) => { + // Push current active tab to history before switching + const currentActiveId = state.activeTabIds[workspaceId]; + const historyStack = state.tabHistoryStacks[workspaceId] || []; + + // Create new history stack: remove tabId if exists, then add current to front + let newHistoryStack = historyStack.filter((id) => id !== tabId); + if (currentActiveId && currentActiveId !== tabId) { + newHistoryStack = [ + currentActiveId, + ...newHistoryStack.filter((id) => id !== currentActiveId), + ]; + } + + return { + activeTabIds: { + ...state.activeTabIds, + [workspaceId]: tabId, + }, + tabHistoryStacks: { + ...state.tabHistoryStacks, + [workspaceId]: newHistoryStack, + }, + }; + }); + }, + + reorderTabs: (workspaceId, startIndex, endIndex) => { + set((state) => { + const workspaceTabs = state.tabs.filter( + (tab) => tab.workspaceId === workspaceId, + ); + const otherTabs = state.tabs.filter( + (tab) => tab.workspaceId !== workspaceId, + ); + + const [removed] = workspaceTabs.splice(startIndex, 1); + workspaceTabs.splice(endIndex, 0, removed); + + return { tabs: [...otherTabs, ...workspaceTabs] }; + }); + }, + + markTabAsUsed: (id) => { + set((state) => ({ + tabs: state.tabs.map((tab) => + tab.id === id ? { ...tab, isNew: false } : tab, + ), + })); + }, + + updateTabGroupLayout: (id, layout) => { + set((state) => ({ + tabs: state.tabs.map((tab) => + tab.id === id && tab.type === TabType.Group + ? { ...tab, layout } + : tab, + ), + })); + }, + + addPaneToTabGroup: (id, paneId, title) => { + set((state) => ({ + tabs: state.tabs.map((tab) => + tab.id === id && tab.type === TabType.Group + ? { + ...tab, + panes: { + ...tab.panes, + [paneId]: { title }, + }, + } + : tab, + ), + })); + }, + + removePaneFromTabGroup: (id, paneId) => { + set((state) => ({ + tabs: state.tabs.map((tab) => { + if (tab.id === id && tab.type === TabType.Group) { + const { [paneId]: _removed, ...remainingPanes } = tab.panes; + return { + ...tab, + panes: remainingPanes, + }; + } + return tab; + }), + })); + }, + + dragTabToTab: (draggedTabId, targetTabId) => { + set((state) => handleDragTabToTab(draggedTabId, targetTabId, state)); + }, + + getTabsByWorkspace: (workspaceId) => { + return get().tabs.filter((tab) => tab.workspaceId === workspaceId); + }, + + getActiveTab: (workspaceId) => { + const activeTabId = get().activeTabIds[workspaceId]; + if (!activeTabId) return null; + return get().tabs.find((tab) => tab.id === activeTabId) || null; + }, + + getLastActiveTabId: (workspaceId) => { + const historyStack = get().tabHistoryStacks[workspaceId] || []; + return historyStack[0] || null; + }, + }), + { name: "TabsStore" }, + ), +); + +// Selector hooks +export const useTabs = () => useTabsStore((state) => state.tabs); +export const useActiveTabIds = () => + useTabsStore((state) => state.activeTabIds); + +// Action hooks +export const useAddTab = () => useTabsStore((state) => state.addTab); +export const useRemoveTab = () => useTabsStore((state) => state.removeTab); +export const useSetActiveTab = () => + useTabsStore((state) => state.setActiveTab); +export const useReorderTabs = () => useTabsStore((state) => state.reorderTabs); +export const useMarkTabAsUsed = () => + useTabsStore((state) => state.markTabAsUsed); diff --git a/apps/desktop/src/renderer/stores/tabs/types.ts b/apps/desktop/src/renderer/stores/tabs/types.ts new file mode 100644 index 00000000000..5a2e0708ec3 --- /dev/null +++ b/apps/desktop/src/renderer/stores/tabs/types.ts @@ -0,0 +1,32 @@ +import type { MosaicNode } from "react-mosaic-component"; + +// Tab types +export enum TabType { + Single = "single", + Group = "group", +} + +// Base tab interface +interface BaseTab { + id: string; + title: string; + workspaceId: string; + isNew?: boolean; +} + +// Single tab - single content view +export interface SingleTab extends BaseTab { + type: TabType.Single; +} + +// Tab group - split view using react-mosaic +export interface TabGroup extends BaseTab { + type: TabType.Group; + // MosaicNode describes the layout structure (split direction and children) + layout: MosaicNode; + // Map of pane IDs to their content/metadata + panes: Record; +} + +// Union type for all tab types +export type Tab = SingleTab | TabGroup; diff --git a/apps/desktop/src/renderer/stores/tabs/utils.ts b/apps/desktop/src/renderer/stores/tabs/utils.ts new file mode 100644 index 00000000000..3ff39a6732d --- /dev/null +++ b/apps/desktop/src/renderer/stores/tabs/utils.ts @@ -0,0 +1,40 @@ +import { type Tab, type TabGroup, TabType } from "./types"; + +export const createNewTab = ( + workspaceId: string, + type: TabType = TabType.Single, +): Tab => { + const id = `tab-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; + const baseTab = { + id, + title: type === TabType.Single ? "New Tab" : "New Split View", + workspaceId, + isNew: true, + }; + + if (type === TabType.Single) { + return { + ...baseTab, + type: TabType.Single, + }; + } + + // Create a default split view with two panes + const pane1 = `pane-${Date.now()}-1`; + const pane2 = `pane-${Date.now()}-2`; + + return { + ...baseTab, + type: TabType.Group, + layout: { + direction: "row", + first: pane1, + second: pane2, + splitPercentage: 50, + }, + panes: { + [pane1]: { title: "Pane 1" }, + [pane2]: { title: "Pane 2" }, + }, + }; +}; diff --git a/apps/old-desktop/src/main/index.ts b/apps/old-desktop/src/main/index.ts index a9af8a02959..bde490c3e6d 100644 --- a/apps/old-desktop/src/main/index.ts +++ b/apps/old-desktop/src/main/index.ts @@ -1,6 +1,6 @@ // Load .env from monorepo root before any other imports import { existsSync } from "node:fs"; -import { resolve, dirname } from "node:path"; +import { dirname, resolve } from "node:path"; import { config } from "dotenv"; // Find .env file by searching upward from __dirname diff --git a/apps/old-desktop/src/main/lib/desktop-stores.ts b/apps/old-desktop/src/main/lib/desktop-stores.ts index 77dc94e6620..faebc9e7d00 100644 --- a/apps/old-desktop/src/main/lib/desktop-stores.ts +++ b/apps/old-desktop/src/main/lib/desktop-stores.ts @@ -1,15 +1,15 @@ +import { LegacyMigrator } from "./migration/migrator"; import { ensureDesktopStorageDirs } from "./storage/config"; import { DesktopLowdbAdapter } from "./storage/lowdb-adapter"; import { + DesktopChangeOrchestrator, DesktopEnvironmentOrchestrator, - DesktopWorkspaceOrchestrator, DesktopProcessOrchestrator, - DesktopChangeOrchestrator, + DesktopWorkspaceOrchestrator, } from "./storage/orchestrators"; import { DomainVersion } from "./storage/version"; import { UiStore } from "./ui-store/store"; import { WorkspaceComposer } from "./workspace-composition/composer"; -import { LegacyMigrator } from "./migration/migrator"; /** * Desktop stores singleton diff --git a/apps/old-desktop/src/main/lib/migration/migrator.ts b/apps/old-desktop/src/main/lib/migration/migrator.ts index b921f69b19d..5dfea8ee44d 100644 --- a/apps/old-desktop/src/main/lib/migration/migrator.ts +++ b/apps/old-desktop/src/main/lib/migration/migrator.ts @@ -1,14 +1,14 @@ -import { existsSync, readFileSync, writeFileSync, copyFileSync } from "node:fs"; -import { join } from "node:path"; -import os from "node:os"; import { randomUUID } from "node:crypto"; +import { copyFileSync, existsSync, readFileSync, writeFileSync } from "node:fs"; +import os from "node:os"; +import { join } from "node:path"; import type { WorkspaceConfig } from "shared/types"; -import { DomainVersion } from "../storage/version"; -import { WorkspaceType } from "../types/cli-types"; import type { DesktopEnvironmentOrchestrator, DesktopWorkspaceOrchestrator, } from "../storage/orchestrators"; +import { DomainVersion } from "../storage/version"; +import { WorkspaceType } from "../types/cli-types"; import type { UiStore } from "../ui-store/store"; /** diff --git a/apps/old-desktop/src/main/lib/storage/index.ts b/apps/old-desktop/src/main/lib/storage/index.ts index 91adb8e6c92..caa074263a5 100644 --- a/apps/old-desktop/src/main/lib/storage/index.ts +++ b/apps/old-desktop/src/main/lib/storage/index.ts @@ -1,5 +1,5 @@ export * from "./adapter"; export * from "./config"; export * from "./lowdb-adapter"; -export * from "./types"; export * from "./orchestrators"; +export * from "./types"; diff --git a/apps/old-desktop/src/main/lib/storage/lowdb-adapter.ts b/apps/old-desktop/src/main/lib/storage/lowdb-adapter.ts index d37cad3238d..a0504d447b3 100644 --- a/apps/old-desktop/src/main/lib/storage/lowdb-adapter.ts +++ b/apps/old-desktop/src/main/lib/storage/lowdb-adapter.ts @@ -3,7 +3,7 @@ import { mkdir } from "node:fs/promises"; import { dirname } from "node:path"; import { JSONFilePreset } from "lowdb/node"; import type { DesktopStorageAdapter } from "./adapter"; -import { getDomainCollectionPath, getDesktopDbDir } from "./config"; +import { getDesktopDbDir, getDomainCollectionPath } from "./config"; import { createEmptyDesktopDatabase, type DesktopDatabase, diff --git a/apps/old-desktop/src/main/lib/storage/orchestrators/index.ts b/apps/old-desktop/src/main/lib/storage/orchestrators/index.ts index fc8d3039f60..3ebfedd176d 100644 --- a/apps/old-desktop/src/main/lib/storage/orchestrators/index.ts +++ b/apps/old-desktop/src/main/lib/storage/orchestrators/index.ts @@ -1,4 +1,4 @@ +export * from "./change-orchestrator"; export * from "./environment-orchestrator"; -export * from "./workspace-orchestrator"; export * from "./process-orchestrator"; -export * from "./change-orchestrator"; +export * from "./workspace-orchestrator"; diff --git a/apps/old-desktop/src/main/lib/types/cli-types.ts b/apps/old-desktop/src/main/lib/types/cli-types.ts index 3a722f15efe..a8a6978f1aa 100644 --- a/apps/old-desktop/src/main/lib/types/cli-types.ts +++ b/apps/old-desktop/src/main/lib/types/cli-types.ts @@ -2,33 +2,31 @@ * Re-export CLI types for Desktop app * Uses workspace import for proper monorepo resolution */ + +export type { + AgentSummary, + Change, + ChangeOrchestrator, + FileDiff, +} from "@superset/cli/types/change"; export type { Environment, EnvironmentOrchestrator, } from "@superset/cli/types/environment"; export type { - Workspace, - LocalWorkspace, - WorkspaceType, - WorkspaceOrchestrator, -} from "@superset/cli/types/workspace"; - -export type { - Process, - ProcessType, - Terminal, Agent, AgentType, + Process, ProcessOrchestrator, + ProcessType, + Terminal, } from "@superset/cli/types/process"; - +export { AgentType, ProcessType } from "@superset/cli/types/process"; export type { - Change, - FileDiff, - AgentSummary, - ChangeOrchestrator, -} from "@superset/cli/types/change"; - + LocalWorkspace, + Workspace, + WorkspaceOrchestrator, + WorkspaceType, +} from "@superset/cli/types/workspace"; export { WorkspaceType } from "@superset/cli/types/workspace"; -export { ProcessType, AgentType } from "@superset/cli/types/process"; diff --git a/apps/old-desktop/src/main/lib/ui-store/index.ts b/apps/old-desktop/src/main/lib/ui-store/index.ts index 3855748c736..d9d332b8527 100644 --- a/apps/old-desktop/src/main/lib/ui-store/index.ts +++ b/apps/old-desktop/src/main/lib/ui-store/index.ts @@ -1,2 +1,2 @@ -export * from "./types"; export * from "./store"; +export * from "./types"; diff --git a/apps/old-desktop/src/main/lib/ui-store/store.ts b/apps/old-desktop/src/main/lib/ui-store/store.ts index 4b203c2ed06..f3b5910bf6f 100644 --- a/apps/old-desktop/src/main/lib/ui-store/store.ts +++ b/apps/old-desktop/src/main/lib/ui-store/store.ts @@ -1,12 +1,12 @@ import { + closeSync, existsSync, - readFileSync, - writeFileSync, - mkdirSync, - renameSync, fsyncSync, + mkdirSync, openSync, - closeSync, + readFileSync, + renameSync, + writeFileSync, } from "node:fs"; import { join } from "node:path"; import { getDesktopUiDir, getUiVersionPath } from "../storage/config"; diff --git a/apps/old-desktop/src/main/lib/ui-store/types.ts b/apps/old-desktop/src/main/lib/ui-store/types.ts index 80d64d44db3..6096f748d0b 100644 --- a/apps/old-desktop/src/main/lib/ui-store/types.ts +++ b/apps/old-desktop/src/main/lib/ui-store/types.ts @@ -1,4 +1,4 @@ -import type { Tab, MosaicNode } from "shared/types"; +import type { MosaicNode, Tab } from "shared/types"; /** * UI state types for Desktop app diff --git a/apps/old-desktop/src/main/lib/workspace-composition/composer.ts b/apps/old-desktop/src/main/lib/workspace-composition/composer.ts index a13e7a8cc90..ae72275bff4 100644 --- a/apps/old-desktop/src/main/lib/workspace-composition/composer.ts +++ b/apps/old-desktop/src/main/lib/workspace-composition/composer.ts @@ -1,14 +1,14 @@ import { randomUUID } from "node:crypto"; import type { LocalWorkspace } from "../types/cli-types"; -import worktreeManager from "../worktree-manager"; import type { UiStore } from "../ui-store/store"; +import type { WorktreeUiMetadata } from "../ui-store/types"; +import worktreeManager from "../worktree-manager"; import type { ComposedWorkspaceState, ComposedWorktree, RescanResult, ScannedWorktree, } from "./types"; -import type { WorktreeUiMetadata } from "../ui-store/types"; /** * Workspace state composer diff --git a/apps/old-desktop/src/main/lib/workspace-composition/index.ts b/apps/old-desktop/src/main/lib/workspace-composition/index.ts index 5e7cd189916..183ad90ddd6 100644 --- a/apps/old-desktop/src/main/lib/workspace-composition/index.ts +++ b/apps/old-desktop/src/main/lib/workspace-composition/index.ts @@ -1,2 +1,2 @@ -export * from "./types"; export * from "./composer"; +export * from "./types"; diff --git a/apps/old-desktop/src/main/lib/workspace-composition/types.ts b/apps/old-desktop/src/main/lib/workspace-composition/types.ts index 5d8728a6b00..bde2c40c248 100644 --- a/apps/old-desktop/src/main/lib/workspace-composition/types.ts +++ b/apps/old-desktop/src/main/lib/workspace-composition/types.ts @@ -1,6 +1,6 @@ import type { LocalWorkspace } from "../types/cli-types"; -import type { WorktreeInfo } from "../worktree-manager"; import type { WorktreeUiMetadata } from "../ui-store/types"; +import type { WorktreeInfo } from "../worktree-manager"; /** * Scanned worktree with Git information diff --git a/apps/old-desktop/src/main/lib/workspace-ipcs.ts b/apps/old-desktop/src/main/lib/workspace-ipcs.ts index e00cc226bef..8022251c9a9 100644 --- a/apps/old-desktop/src/main/lib/workspace-ipcs.ts +++ b/apps/old-desktop/src/main/lib/workspace-ipcs.ts @@ -10,9 +10,9 @@ import type { } from "shared/types"; import configManager from "./config-manager"; +import windowManager from "./window-manager"; import workspaceManager from "./workspace-manager"; import worktreeManager from "./worktree-manager"; -import windowManager from "./window-manager"; export function registerWorkspaceIPCs() { // Open repository dialog diff --git a/apps/old-desktop/src/renderer/contexts/AppProviders.tsx b/apps/old-desktop/src/renderer/contexts/AppProviders.tsx index 120a74b2e50..e347d689441 100644 --- a/apps/old-desktop/src/renderer/contexts/AppProviders.tsx +++ b/apps/old-desktop/src/renderer/contexts/AppProviders.tsx @@ -1,11 +1,11 @@ import type React from "react"; import { useState } from "react"; import { - WorkspaceProvider, - TabProvider, SidebarProvider, - WorktreeOperationsProvider, + TabProvider, TaskProvider, + WorkspaceProvider, + WorktreeOperationsProvider, } from "./index"; interface AppProvidersProps { diff --git a/apps/old-desktop/src/renderer/contexts/TabContext.tsx b/apps/old-desktop/src/renderer/contexts/TabContext.tsx index 8559998b514..943f422ebd4 100644 --- a/apps/old-desktop/src/renderer/contexts/TabContext.tsx +++ b/apps/old-desktop/src/renderer/contexts/TabContext.tsx @@ -1,5 +1,5 @@ -import { createContext, useContext } from "react"; import type React from "react"; +import { createContext, useContext } from "react"; import type { Tab, Worktree } from "shared/types"; import { useTabs } from "../screens/main/hooks"; import { useWorkspaceContext } from "./WorkspaceContext"; diff --git a/apps/old-desktop/src/renderer/contexts/TaskContext.tsx b/apps/old-desktop/src/renderer/contexts/TaskContext.tsx index faf90dc0dc1..38c7cb91b00 100644 --- a/apps/old-desktop/src/renderer/contexts/TaskContext.tsx +++ b/apps/old-desktop/src/renderer/contexts/TaskContext.tsx @@ -1,10 +1,10 @@ import type React from "react"; import { createContext, useContext } from "react"; import type { TaskStatus } from "../screens/main/components/Layout/StatusIndicator"; -import type { PendingWorktree, UITask } from "../screens/main/types"; import { useTasks } from "../screens/main/hooks"; -import { useWorkspaceContext } from "./WorkspaceContext"; +import type { PendingWorktree, UITask } from "../screens/main/types"; import { useTabContext } from "./TabContext"; +import { useWorkspaceContext } from "./WorkspaceContext"; import { useWorktreeOperationsContext } from "./WorktreeOperationsContext"; interface TaskContextValue { diff --git a/apps/old-desktop/src/renderer/contexts/WorktreeOperationsContext.tsx b/apps/old-desktop/src/renderer/contexts/WorktreeOperationsContext.tsx index a41f814de5f..778273fd63d 100644 --- a/apps/old-desktop/src/renderer/contexts/WorktreeOperationsContext.tsx +++ b/apps/old-desktop/src/renderer/contexts/WorktreeOperationsContext.tsx @@ -2,8 +2,8 @@ import type React from "react"; import { createContext, useContext } from "react"; import type { Worktree } from "shared/types"; import { useWorktrees } from "../screens/main/hooks"; -import { useWorkspaceContext } from "./WorkspaceContext"; import { useTabContext } from "./TabContext"; +import { useWorkspaceContext } from "./WorkspaceContext"; interface WorktreeOperationsContextValue { handleWorktreeCreated: () => Promise; diff --git a/apps/old-desktop/src/renderer/contexts/index.ts b/apps/old-desktop/src/renderer/contexts/index.ts index c70a05fd1e8..4ffe34ebbb6 100644 --- a/apps/old-desktop/src/renderer/contexts/index.ts +++ b/apps/old-desktop/src/renderer/contexts/index.ts @@ -1,10 +1,10 @@ export { AppProviders } from "./AppProviders"; -export { WorkspaceProvider, useWorkspaceContext } from "./WorkspaceContext"; -export { TabProvider, useTabContext } from "./TabContext"; export { SidebarProvider, useSidebarContext } from "./SidebarContext"; +export { TabProvider, useTabContext } from "./TabContext"; +export { TaskProvider, useTaskContext } from "./TaskContext"; +export { useWorkspaceContext, WorkspaceProvider } from "./WorkspaceContext"; +export { useWorktree, WorktreeProvider } from "./WorktreeContext"; export { - WorktreeOperationsProvider, useWorktreeOperationsContext, + WorktreeOperationsProvider, } from "./WorktreeOperationsContext"; -export { TaskProvider, useTaskContext } from "./TaskContext"; -export { WorktreeProvider, useWorktree } from "./WorktreeContext"; diff --git a/apps/old-desktop/src/renderer/screens/main/MainScreen.tsx b/apps/old-desktop/src/renderer/screens/main/MainScreen.tsx index b5fae125c36..1aab351f221 100644 --- a/apps/old-desktop/src/renderer/screens/main/MainScreen.tsx +++ b/apps/old-desktop/src/renderer/screens/main/MainScreen.tsx @@ -1,6 +1,15 @@ -import { useState, useEffect } from "react"; +import { useEffect, useState } from "react"; import { DndProvider } from "react-dnd"; +import { + useSidebarContext, + useTabContext, + useTaskContext, + useWorkspaceContext, + useWorktreeOperationsContext, +} from "../../contexts"; import { dragDropManager } from "../../lib/dnd"; +import { createShortcutHandler } from "../../lib/keyboard-shortcuts"; +import { createTabShortcuts } from "../../lib/shortcuts"; import { AppFrame } from "./components/AppFrame"; import { Background } from "./components/Background"; import { AddTaskModal } from "./components/Layout/AddTaskModal"; @@ -8,17 +17,8 @@ import { TaskTabs } from "./components/Layout/TaskTabs"; import { MainContentArea } from "./components/MainContentArea"; import { SidebarOverlay } from "./components/SidebarOverlay"; import { WorkspaceSelectionModal } from "./components/WorkspaceSelectionModal"; -import { - useWorkspaceContext, - useTabContext, - useSidebarContext, - useWorktreeOperationsContext, - useTaskContext, -} from "../../contexts"; import type { AppMode } from "./types"; import { enrichWorktreesWithTasks } from "./utils"; -import { createShortcutHandler } from "../../lib/keyboard-shortcuts"; -import { createTabShortcuts } from "../../lib/shortcuts"; export function MainScreen() { const [mode, setMode] = useState("edit"); diff --git a/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/CreatingView.tsx b/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/CreatingView.tsx index ddc0a1f4675..4f6e995b77a 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/CreatingView.tsx +++ b/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/CreatingView.tsx @@ -1,7 +1,7 @@ import { Button } from "@superset/ui/button"; +import { ScrollArea } from "@superset/ui/scroll-area"; import { Loader2 } from "lucide-react"; import type React from "react"; -import { ScrollArea } from "@superset/ui/scroll-area"; import { TerminalOutput } from "../../Sidebar/components/CreateWorktreeModal/TerminalOutput"; interface CreatingViewProps { diff --git a/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/TaskList.tsx b/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/TaskList.tsx index 54bd4a84ec1..28a34f2e1b4 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/TaskList.tsx +++ b/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/TaskList.tsx @@ -8,9 +8,9 @@ import { import { ScrollArea } from "@superset/ui/scroll-area"; import { Loader2, Search, X } from "lucide-react"; import type React from "react"; -import type { Task } from "./types"; import { TaskListItem } from "../TaskListItem"; import { TaskPreview } from "../TaskPreview"; +import type { Task } from "./types"; interface TaskListProps { searchQuery: string; diff --git a/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/index.ts b/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/index.ts index 1a452bb0b36..bd3bfbbd38d 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/index.ts +++ b/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/index.ts @@ -1,5 +1,5 @@ export { AddTaskModal } from "../AddTaskModal"; -export type { AddTaskModalProps, Task, APITask } from "./types"; export { CreatingView } from "./CreatingView"; export { TaskForm } from "./TaskForm"; export { TaskList } from "./TaskList"; +export type { AddTaskModalProps, APITask, Task } from "./types"; diff --git a/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/types.ts b/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/types.ts index a208fab27d7..8a4caace32b 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/types.ts +++ b/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/types.ts @@ -1,5 +1,5 @@ -import type { TaskStatus } from "../StatusIndicator"; import type { Worktree } from "shared/types"; +import type { TaskStatus } from "../StatusIndicator"; export interface Task { id: string; diff --git a/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/useTaskForm.ts b/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/useTaskForm.ts index 9b304cebceb..65859aefd40 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/useTaskForm.ts +++ b/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/useTaskForm.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useState } from "react"; -import type { TaskStatus } from "../StatusIndicator"; import type { Worktree } from "shared/types"; +import type { TaskStatus } from "../StatusIndicator"; import { generateBranchNameWithCollisionAvoidance } from "./utils"; export function useTaskForm( diff --git a/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/utils.ts b/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/utils.ts index 7fc32efef3d..7a64ddf6812 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/utils.ts +++ b/apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/utils.ts @@ -1,6 +1,6 @@ import type { Worktree } from "shared/types"; -import type { APITask, Task } from "./types"; import type { TaskStatus } from "../StatusIndicator"; +import type { APITask, Task } from "./types"; export function formatRelativeTime(date: Date): string { const now = new Date(); diff --git a/apps/old-desktop/src/renderer/screens/main/components/MainContent/TabContent.tsx b/apps/old-desktop/src/renderer/screens/main/components/MainContent/TabContent.tsx index 67e94fee28d..4a3c10c0a7e 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/MainContent/TabContent.tsx +++ b/apps/old-desktop/src/renderer/screens/main/components/MainContent/TabContent.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from "react"; import type { Tab } from "shared/types"; -import { useWorkspaceContext, useTabContext } from "../../../../contexts"; +import { useTabContext, useWorkspaceContext } from "../../../../contexts"; import { PortTab } from "../TabContent/components/PortTab"; import { PreviewTab } from "../TabContent/components/PreviewTab"; import TabGroup from "./TabGroup"; diff --git a/apps/old-desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx b/apps/old-desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx index d94a7242711..0ad8901342b 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx +++ b/apps/old-desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx @@ -6,10 +6,10 @@ import { MosaicWindow, } from "react-mosaic-component"; import "react-mosaic-component/react-mosaic-component.css"; +import { dragDropManager } from "renderer/lib/dnd"; import type { Tab } from "shared/types"; -import { useWorkspaceContext, useTabContext } from "../../../../contexts"; +import { useTabContext, useWorkspaceContext } from "../../../../contexts"; import TabContent from "./TabContent"; -import { dragDropManager } from "renderer/lib/dnd"; interface ScreenLayoutProps { groupTab: Tab; // A tab with type: "group" diff --git a/apps/old-desktop/src/renderer/screens/main/components/MainContentArea/MainContentArea.tsx b/apps/old-desktop/src/renderer/screens/main/components/MainContentArea/MainContentArea.tsx index dda4e0dcb1a..469cf2bb02f 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/MainContentArea/MainContentArea.tsx +++ b/apps/old-desktop/src/renderer/screens/main/components/MainContentArea/MainContentArea.tsx @@ -4,14 +4,14 @@ import { ResizablePanelGroup, } from "@superset/ui/resizable"; import { useState } from "react"; -import { useDiffData } from "../../hooks"; -import type { AppMode } from "../../types"; import { - useWorkspaceContext, - useTabContext, useSidebarContext, + useTabContext, + useWorkspaceContext, useWorktreeOperationsContext, } from "../../../../contexts"; +import { useDiffData } from "../../hooks"; +import type { AppMode } from "../../types"; import { DiffContentArea } from "../DiffView"; import TabContent from "../MainContent/TabContent"; import TabGroup from "../MainContent/TabGroup"; diff --git a/apps/old-desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx b/apps/old-desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx index 1924695ff40..cf65fedcee6 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx +++ b/apps/old-desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx @@ -2,8 +2,8 @@ import type { RouterOutputs } from "@superset/api"; import { Plus } from "lucide-react"; import type React from "react"; import { useMemo, useState } from "react"; -import { useWorkspaceContext, useTabContext } from "../../../../contexts"; import { mockTasks, mockUsers } from "../../../../../lib/mock-data"; +import { useTabContext, useWorkspaceContext } from "../../../../contexts"; import { CreateTaskModal } from "./CreateTaskModal"; import { KanbanColumn } from "./KanbanColumn"; import { TaskPage } from "./TaskPage"; @@ -11,7 +11,7 @@ import { TaskPage } from "./TaskPage"; type Task = RouterOutputs["task"]["all"][number]; type User = RouterOutputs["user"]["all"][number]; -interface PlanViewProps {} +type PlanViewProps = {}; export const PlanView: React.FC = () => { const { currentWorkspace } = useWorkspaceContext(); diff --git a/apps/old-desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx b/apps/old-desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx index 42781caee6c..a5180b77864 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx +++ b/apps/old-desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx @@ -2,13 +2,13 @@ import { type MotionValue, useMotionValue } from "framer-motion"; import { File, FileEdit, FilePlus, FileX } from "lucide-react"; import { useEffect, useState } from "react"; import type { Tab } from "shared/types"; -import { useDiffData } from "../../hooks"; import { - useWorkspaceContext, + useSidebarContext, useTabContext, + useWorkspaceContext, useWorktreeOperationsContext, - useSidebarContext, } from "../../../../contexts"; +import { useDiffData } from "../../hooks"; import { FileTree } from "../DiffView"; import type { FileDiff } from "../DiffView/types"; import { CreateWorktreeModal, WorktreeList } from "./components"; diff --git a/apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/index.ts b/apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/index.ts index de0e8d8c3dc..7167b4075d3 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/index.ts +++ b/apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/index.ts @@ -1,2 +1,2 @@ export { ModeCarousel } from "./ModeCarousel"; -export type { SidebarMode, ModeCarouselProps } from "./types"; +export type { ModeCarouselProps, SidebarMode } from "./types"; diff --git a/apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItem.tsx b/apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItem.tsx index 06d4de75c62..5c4c6f0be45 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItem.tsx +++ b/apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItem.tsx @@ -18,11 +18,11 @@ import { DialogHeader, DialogTitle, } from "renderer/components/ui/dialog"; +import { dragDropManager } from "renderer/lib/dnd"; import type { Tab, Worktree } from "shared/types"; import { WorktreePortsList } from "../WorktreePortsList"; import { GitStatusDialog } from "./components/GitStatusDialog"; import { TabItem } from "./components/TabItem"; -import { dragDropManager } from "renderer/lib/dnd"; interface ProxyStatus { canonical: number; diff --git a/apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItemArborist.tsx b/apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItemArborist.tsx index c7c99702df3..e8ed60cbddc 100644 --- a/apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItemArborist.tsx +++ b/apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItemArborist.tsx @@ -1,6 +1,3 @@ -import { Tree } from "react-arborist"; -import type { NodeApi } from "react-arborist"; -import type { TreeApi } from "react-arborist"; import { Button } from "@superset/ui/button"; import { ContextMenu, @@ -10,11 +7,8 @@ import { } from "@superset/ui/context-menu"; import { ChevronRight, Edit2, FolderOpen } from "lucide-react"; import { useEffect, useId, useRef, useState } from "react"; -import type { Tab, Worktree } from "shared/types"; -import { WorktreePortsList } from "../WorktreePortsList"; -import { GitStatusDialog } from "./components/GitStatusDialog"; -import { TabItem } from "./components/TabItem"; -import { dragDropManager } from "renderer/lib/dnd"; +import type { NodeApi, TreeApi } from "react-arborist"; +import { Tree } from "react-arborist"; import { Dialog, DialogContent, @@ -23,6 +17,11 @@ import { DialogHeader, DialogTitle, } from "renderer/components/ui/dialog"; +import { dragDropManager } from "renderer/lib/dnd"; +import type { Tab, Worktree } from "shared/types"; +import { WorktreePortsList } from "../WorktreePortsList"; +import { GitStatusDialog } from "./components/GitStatusDialog"; +import { TabItem } from "./components/TabItem"; interface WorktreeItemProps { worktree: Worktree; diff --git a/apps/old-desktop/src/renderer/screens/main/hooks/useTasks.ts b/apps/old-desktop/src/renderer/screens/main/hooks/useTasks.ts index 4ba5386907a..223d9ca34d4 100644 --- a/apps/old-desktop/src/renderer/screens/main/hooks/useTasks.ts +++ b/apps/old-desktop/src/renderer/screens/main/hooks/useTasks.ts @@ -1,8 +1,8 @@ import { useEffect, useRef, useState } from "react"; import type { Worktree } from "shared/types"; -import type { TaskStatus } from "../components/Layout/StatusIndicator"; -import type { UITask, PendingWorktree } from "../types"; import { transformWorktreeToTask } from "../components/Layout/AddTaskModal/utils"; +import type { TaskStatus } from "../components/Layout/StatusIndicator"; +import type { PendingWorktree, UITask } from "../types"; interface UseTasksProps { currentWorkspace: { diff --git a/apps/old-desktop/src/renderer/screens/main/utils.ts b/apps/old-desktop/src/renderer/screens/main/utils.ts index c15ed98f5f4..9153d74eacf 100644 --- a/apps/old-desktop/src/renderer/screens/main/utils.ts +++ b/apps/old-desktop/src/renderer/screens/main/utils.ts @@ -1,8 +1,8 @@ import type { Tab, Worktree } from "shared/types"; +import { formatRelativeTime } from "./components/Layout/AddTaskModal/utils"; import type { TaskStatus } from "./components/Layout/StatusIndicator"; import type { WorktreeWithTask } from "./components/Layout/TaskTabs"; import type { PendingWorktree } from "./types"; -import { formatRelativeTime } from "./components/Layout/AddTaskModal/utils"; // Helper function to find a tab recursively (for finding sub-tabs inside groups) export function findTabRecursive( diff --git a/apps/old-desktop/src/shared/ipc-channels/index.ts b/apps/old-desktop/src/shared/ipc-channels/index.ts index 02d1ae0acd6..7eb575807cf 100644 --- a/apps/old-desktop/src/shared/ipc-channels/index.ts +++ b/apps/old-desktop/src/shared/ipc-channels/index.ts @@ -12,8 +12,8 @@ import type { TabChannels } from "./tab"; import type { TerminalChannels } from "./terminal"; import type { UiChannels } from "./ui"; import type { WindowChannels } from "./window"; -import type { WorktreeChannels } from "./worktree"; import type { WorkspaceChannels } from "./workspace"; +import type { WorktreeChannels } from "./worktree"; // Re-export shared types export type { diff --git a/apps/old-desktop/src/shared/ipc-channels/ui.ts b/apps/old-desktop/src/shared/ipc-channels/ui.ts index d25823a11e5..e580c7390f2 100644 --- a/apps/old-desktop/src/shared/ipc-channels/ui.ts +++ b/apps/old-desktop/src/shared/ipc-channels/ui.ts @@ -2,8 +2,8 @@ * UI-related IPC channels for Desktop app */ +import type { MosaicNode, Tab } from "../types"; import type { IpcResponse, NoRequest } from "./types"; -import type { Tab, MosaicNode } from "../types"; export interface UiChannels { // Workspace UI state diff --git a/apps/website/src/app/components/HeroSection/HeroSection.tsx b/apps/website/src/app/components/HeroSection/HeroSection.tsx index 154aaa61055..1e0ef36f5c0 100644 --- a/apps/website/src/app/components/HeroSection/HeroSection.tsx +++ b/apps/website/src/app/components/HeroSection/HeroSection.tsx @@ -1,6 +1,6 @@ import { FadeUp } from "./components/FadeUp"; -import { HeroParallax } from "./components/HeroParallax"; import { HeroCanvas } from "./components/HeroCanvas"; +import { HeroParallax } from "./components/HeroParallax"; export function HeroSection() { return ( diff --git a/apps/website/src/app/components/HeroSection/components/HeroCanvas/components/LitBackground/LitBackground.tsx b/apps/website/src/app/components/HeroSection/components/HeroCanvas/components/LitBackground/LitBackground.tsx index 56ae09a210f..8c64aafc683 100644 --- a/apps/website/src/app/components/HeroSection/components/HeroCanvas/components/LitBackground/LitBackground.tsx +++ b/apps/website/src/app/components/HeroSection/components/HeroCanvas/components/LitBackground/LitBackground.tsx @@ -6,15 +6,15 @@ import { useEffect, useMemo, useRef, useState } from "react"; import type { Mesh, PointLight } from "three"; import * as THREE from "three"; import { useHeroVisibility } from "../../../HeroParallax"; -import { waveVertexShader } from "../../shaders/vertex"; -import { waveFragmentShader } from "../../shaders/fragment"; import { + GEOMETRY_CONFIG, LIGHT_CONFIG, MATERIAL_CONFIG, TEXT_CONFIG, - GEOMETRY_CONFIG, } from "../../config"; import { calculateGlareProperties } from "../../helpers"; +import { waveFragmentShader } from "../../shaders/fragment"; +import { waveVertexShader } from "../../shaders/vertex"; export function LitBackground() { const meshRef = useRef(null); diff --git a/apps/website/src/app/page.tsx b/apps/website/src/app/page.tsx index a113662ae77..5020aa292a3 100644 --- a/apps/website/src/app/page.tsx +++ b/apps/website/src/app/page.tsx @@ -1,13 +1,13 @@ "use client"; import { useState } from "react"; +import { ClientLogosSection } from "./components/ClientLogosSection"; +import { FeaturesSection } from "./components/FeaturesSection"; import { Footer } from "./components/Footer"; import { Header } from "./components/Header"; -import { WaitlistModal } from "./components/WaitlistModal"; import { HeroSection } from "./components/HeroSection"; -import { ClientLogosSection } from "./components/ClientLogosSection"; -import { FeaturesSection } from "./components/FeaturesSection"; import { TestimonialsSection } from "./components/TestimonialsSection"; +import { WaitlistModal } from "./components/WaitlistModal"; export default function Home() { const [isWaitlistOpen, setIsWaitlistOpen] = useState(false); diff --git a/packages/ui/src/components/context-menu.tsx b/packages/ui/src/components/context-menu.tsx index 53a55fb9c57..ae05cb6cdac 100644 --- a/packages/ui/src/components/context-menu.tsx +++ b/packages/ui/src/components/context-menu.tsx @@ -1,6 +1,6 @@ import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"; -import { HiMiniCheck, HiMiniChevronRight, HiMiniStop } from "react-icons/hi2"; import type * as React from "react"; +import { HiMiniCheck, HiMiniChevronRight, HiMiniStop } from "react-icons/hi2"; import { cn } from "../lib/utils"; diff --git a/packages/ui/src/components/dialog.tsx b/packages/ui/src/components/dialog.tsx index 0680ed61b4f..be40094d25e 100644 --- a/packages/ui/src/components/dialog.tsx +++ b/packages/ui/src/components/dialog.tsx @@ -1,6 +1,6 @@ import * as DialogPrimitive from "@radix-ui/react-dialog"; -import { HiMiniXMark } from "react-icons/hi2"; import type * as React from "react"; +import { HiMiniXMark } from "react-icons/hi2"; import { cn } from "../lib/utils"; diff --git a/packages/ui/src/components/dropdown-menu.tsx b/packages/ui/src/components/dropdown-menu.tsx index 7efd658e3fb..c3e497c113d 100644 --- a/packages/ui/src/components/dropdown-menu.tsx +++ b/packages/ui/src/components/dropdown-menu.tsx @@ -1,6 +1,6 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; -import { HiMiniCheck, HiMiniChevronRight, HiMiniStop } from "react-icons/hi2"; import type * as React from "react"; +import { HiMiniCheck, HiMiniChevronRight, HiMiniStop } from "react-icons/hi2"; import { cn } from "../lib/utils"; diff --git a/packages/ui/src/components/resizable.tsx b/packages/ui/src/components/resizable.tsx index a4225e4cad4..881354405e4 100644 --- a/packages/ui/src/components/resizable.tsx +++ b/packages/ui/src/components/resizable.tsx @@ -1,5 +1,5 @@ -import { HiMiniEllipsisVertical } from "react-icons/hi2"; import type * as React from "react"; +import { HiMiniEllipsisVertical } from "react-icons/hi2"; import * as ResizablePrimitive from "react-resizable-panels"; import { cn } from "../lib/utils"; diff --git a/packages/ui/src/components/select.tsx b/packages/ui/src/components/select.tsx index 3498c2b21d8..5a1f82a10e9 100644 --- a/packages/ui/src/components/select.tsx +++ b/packages/ui/src/components/select.tsx @@ -1,12 +1,12 @@ "use client"; import * as SelectPrimitive from "@radix-ui/react-select"; +import type * as React from "react"; import { HiMiniCheck, HiMiniChevronDown, HiMiniChevronUp, } from "react-icons/hi2"; -import type * as React from "react"; import { cn } from "../lib/utils"; diff --git a/packages/ui/src/components/separator.tsx b/packages/ui/src/components/separator.tsx index d57e7e2272f..579541f0a1e 100644 --- a/packages/ui/src/components/separator.tsx +++ b/packages/ui/src/components/separator.tsx @@ -1,5 +1,5 @@ -import * as React from "react"; import * as SeparatorPrimitive from "@radix-ui/react-separator"; +import type * as React from "react"; import { cn } from "../lib/utils"; function Separator({ From c3dfd7e155be3b9c817825bcefa118272fd257c3 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 19 Nov 2025 16:07:12 -0800 Subject: [PATCH 08/21] tab dragging --- .../WorkspaceView/ContentView/TabsContent.tsx | 94 --------------- .../ContentView/TabsContent/EmptyTabView.tsx | 18 +++ .../ContentView/TabsContent/GroupTabView.tsx | 56 +++++++++ .../ContentView/TabsContent/SingleTabView.tsx | 46 ++++++++ .../ContentView/TabsContent/index.tsx | 35 ++++++ .../ContentView/TabsContent/types.ts | 6 + .../TabsContent/useDropTabTarget.ts | 34 ++++++ .../Sidebar/TabsView/TabItem.tsx | 108 ------------------ .../Sidebar/TabsView/TabItem/index.tsx | 66 +++++++++++ .../Sidebar/TabsView/TabItem/types.ts | 12 ++ .../Sidebar/TabsView/TabItem/useDragTab.ts | 42 +++++++ .../desktop/src/renderer/stores/tabs/store.ts | 50 +++++++- 12 files changed, 362 insertions(+), 205 deletions(-) delete mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/types.ts create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useDropTabTarget.ts delete mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx deleted file mode 100644 index 260ff372f57..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { useMemo } from "react"; -import { - TabType, - useActiveTabIds, - useTabs, - useWorkspacesStore, -} from "renderer/stores"; - -export function TabsContent() { - const activeWorkspaceId = useWorkspacesStore( - (state) => state.activeWorkspaceId, - ); - const allTabs = useTabs(); - const activeTabIds = useActiveTabIds(); - - const activeTab = useMemo(() => { - if (!activeWorkspaceId) return null; - const activeTabId = activeTabIds[activeWorkspaceId]; - if (!activeTabId) return null; - return allTabs.find((tab) => tab.id === activeTabId) || null; - }, [activeWorkspaceId, activeTabIds, allTabs]); - - if (!activeTab) { - return ( -
-
-
-
-

- No Active Tab -

-

- Create a new tab to get started -

-
-
-
-
- ); - } - - // Render different content based on tab type - if (activeTab.type === TabType.Single) { - return ( -
-
-
-
-

- {activeTab.title} -

-

Single tab view

-
-
-

- Tab content will appear here -

-
-
-
-
- ); - } - - // Tab group view with react-mosaic - return ( -
-
-
-
-

- {activeTab.title} -

-

- Split view - {Object.keys(activeTab.panes).length} panes -

-
-
-

- React-mosaic split view will appear here -

-
- {Object.entries(activeTab.panes).map(([paneId, pane]) => ( -
- - {pane.title} ({paneId}) -
- ))} -
-
-
-
-
- ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx new file mode 100644 index 00000000000..eadcdf75fe8 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx @@ -0,0 +1,18 @@ +export function EmptyTabView() { + return ( +
+
+
+
+

+ No Active Tab +

+

+ Create a new tab to get started +

+
+
+
+
+ ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx new file mode 100644 index 00000000000..b4ba7c8743c --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx @@ -0,0 +1,56 @@ +import type { TabGroup } from "renderer/stores"; +import { useDropTabTarget } from "./useDropTabTarget"; + +interface GroupTabViewProps { + tab: TabGroup; +} + +export function GroupTabView({ tab }: GroupTabViewProps) { + const { drop, isDropZone } = useDropTabTarget(tab); + + return ( +
} + className={`flex-1 h-full overflow-auto bg-background transition-colors ${ + isDropZone ? "bg-primary/10" : "" + }`} + > +
+
+
+

+ {tab.title} +

+

+ Split view - {Object.keys(tab.panes).length} panes{" "} + {isDropZone && "- Drop to add pane"} +

+
+
+

+ React-mosaic split view will appear here +

+
+ {Object.entries(tab.panes).map(([paneId, pane]) => ( +
+ - {pane.title} ({paneId}) +
+ ))} +
+ {isDropZone && ( +

+ Drop here to add to this split view +

+ )} +
+
+
+
+ ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx new file mode 100644 index 00000000000..8a8ceb7206d --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx @@ -0,0 +1,46 @@ +import type { SingleTab } from "renderer/stores"; +import { useDropTabTarget } from "./useDropTabTarget"; + +interface SingleTabViewProps { + tab: SingleTab; +} + +export function SingleTabView({ tab }: SingleTabViewProps) { + const { drop, isDropZone } = useDropTabTarget(tab); + + return ( +
} + className={`flex-1 h-full overflow-auto bg-background transition-colors ${ + isDropZone ? "bg-primary/10" : "" + }`} + > +
+
+
+

+ {tab.title} +

+

+ Single tab view {isDropZone && "- Drop to create split"} +

+
+
+

Tab content will appear here

+ {isDropZone && ( +

+ Drop here to create a split view +

+ )} +
+
+
+
+ ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx new file mode 100644 index 00000000000..02ddf67a67c --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx @@ -0,0 +1,35 @@ +import { useMemo } from "react"; +import { + TabType, + useActiveTabIds, + useTabs, + useWorkspacesStore, +} from "renderer/stores"; +import { EmptyTabView } from "./EmptyTabView"; +import { GroupTabView } from "./GroupTabView"; +import { SingleTabView } from "./SingleTabView"; + +export function TabsContent() { + const activeWorkspaceId = useWorkspacesStore( + (state) => state.activeWorkspaceId, + ); + const allTabs = useTabs(); + const activeTabIds = useActiveTabIds(); + + const activeTab = useMemo(() => { + if (!activeWorkspaceId) return null; + const activeTabId = activeTabIds[activeWorkspaceId]; + if (!activeTabId) return null; + return allTabs.find((tab) => tab.id === activeTabId) || null; + }, [activeWorkspaceId, activeTabIds, allTabs]); + + if (!activeTab) { + return ; + } + + if (activeTab.type === TabType.Single) { + return ; + } + + return ; +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/types.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/types.ts new file mode 100644 index 00000000000..8ea04f36766 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/types.ts @@ -0,0 +1,6 @@ +export interface DragItem { + type: "TAB"; + tabId: string; +} + +export const TAB_DND_TYPE = "TAB"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useDropTabTarget.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useDropTabTarget.ts new file mode 100644 index 00000000000..cd4032b1edf --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useDropTabTarget.ts @@ -0,0 +1,34 @@ +import { useDrop } from "react-dnd"; +import type { Tab } from "renderer/stores"; +import { useTabsStore } from "renderer/stores"; +import { TAB_DND_TYPE, type DragItem } from "./types"; + +export function useDropTabTarget(activeTab: Tab | null) { + const dragTabToTab = useTabsStore((state) => state.dragTabToTab); + + const [{ isOver, canDrop }, drop] = useDrop< + DragItem, + void, + { isOver: boolean; canDrop: boolean } + >({ + accept: TAB_DND_TYPE, + drop: (item) => { + // Only allow drop if there's an active tab and it's different from dragged tab + if (activeTab && item.tabId !== activeTab.id) { + dragTabToTab(item.tabId, activeTab.id); + } + }, + canDrop: (item) => { + // Can only drop if there's an active tab and it's different from the dragged tab + return activeTab !== null && item.tabId !== activeTab.id; + }, + collect: (monitor) => ({ + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), + }), + }); + + const isDropZone = isOver && canDrop; + + return { drop, isDropZone }; +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem.tsx deleted file mode 100644 index de0d1235163..00000000000 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { Button } from "@superset/ui/button"; -import { useState } from "react"; -import { HiMiniXMark } from "react-icons/hi2"; -import { - useRemoveTab, - useSetActiveTab, - useTabsStore, - useWorkspacesStore, -} from "renderer/stores"; - -interface TabItemProps { - tabId: string; - title: string; - isActive: boolean; -} - -export function TabItem({ tabId, title, isActive }: TabItemProps) { - const activeWorkspaceId = useWorkspacesStore( - (state) => state.activeWorkspaceId, - ); - const removeTab = useRemoveTab(); - const setActiveTab = useSetActiveTab(); - const dragTabToTab = useTabsStore((state) => state.dragTabToTab); - - const [isDragging, setIsDragging] = useState(false); - const [isDragOver, setIsDragOver] = useState(false); - - const handleRemoveTab = (e: React.MouseEvent) => { - e.stopPropagation(); - removeTab(tabId); - }; - - const handleTabClick = () => { - if (activeWorkspaceId) { - setActiveTab(activeWorkspaceId, tabId); - } - }; - - const handleDragStart = (e: React.DragEvent) => { - setIsDragging(true); - e.dataTransfer.effectAllowed = "move"; - e.dataTransfer.setData("application/x-tab-id", tabId); - }; - - const handleDragEnd = () => { - setIsDragging(false); - }; - - const handleDragOver = (e: React.DragEvent) => { - e.preventDefault(); - e.dataTransfer.dropEffect = "move"; - }; - - const handleDragEnter = (e: React.DragEvent) => { - e.preventDefault(); - setIsDragOver(true); - }; - - const handleDragLeave = () => { - setIsDragOver(false); - }; - - const handleDrop = (e: React.DragEvent) => { - e.preventDefault(); - setIsDragOver(false); - - const draggedTabId = e.dataTransfer.getData("application/x-tab-id"); - if (draggedTabId && draggedTabId !== tabId) { - dragTabToTab(draggedTabId, tabId); - } - }; - - return ( - - - ); -} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx new file mode 100644 index 00000000000..b1319b62802 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx @@ -0,0 +1,66 @@ +import { Button } from "@superset/ui/button"; +import { HiMiniXMark } from "react-icons/hi2"; +import { + useRemoveTab, + useSetActiveTab, + useWorkspacesStore, +} from "renderer/stores"; +import type { TabItemProps } from "./types"; +import { useDragTab } from "./useDragTab"; + +export function TabItem({ tabId, title, isActive }: TabItemProps) { + const activeWorkspaceId = useWorkspacesStore( + (state) => state.activeWorkspaceId, + ); + const removeTab = useRemoveTab(); + const setActiveTab = useSetActiveTab(); + + const { drag, drop, isDragging, isDragOver } = useDragTab(tabId); + + const handleRemoveTab = (e: React.MouseEvent) => { + e.stopPropagation(); + removeTab(tabId); + }; + + const handleTabClick = () => { + if (activeWorkspaceId) { + setActiveTab(activeWorkspaceId, tabId); + } + }; + + // Combine drag and drop refs + const attachRef = (el: HTMLButtonElement | null) => { + drag(el); + drop(el); + }; + + return ( + + + ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts new file mode 100644 index 00000000000..827399e79e4 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts @@ -0,0 +1,12 @@ +export interface DragItem { + type: "TAB"; + tabId: string; +} + +export const TAB_DND_TYPE = "TAB"; + +export interface TabItemProps { + tabId: string; + title: string; + isActive: boolean; +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts new file mode 100644 index 00000000000..ab5d0559076 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts @@ -0,0 +1,42 @@ +import { useDrag, useDrop } from "react-dnd"; +import { useTabsStore } from "renderer/stores"; +import { TAB_DND_TYPE, type DragItem } from "./types"; + +export function useDragTab(tabId: string) { + const dragTabToTab = useTabsStore((state) => state.dragTabToTab); + + // Set up drag source + const [{ isDragging }, drag] = useDrag< + DragItem, + void, + { isDragging: boolean } + >({ + type: TAB_DND_TYPE, + item: { type: TAB_DND_TYPE, tabId }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + }); + + // Set up drop target + const [{ isOver, canDrop }, drop] = useDrop< + DragItem, + void, + { isOver: boolean; canDrop: boolean } + >({ + accept: TAB_DND_TYPE, + drop: (item) => { + if (item.tabId !== tabId) { + dragTabToTab(item.tabId, tabId); + } + }, + collect: (monitor) => ({ + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), + }), + }); + + const isDragOver = isOver && canDrop; + + return { drag, drop, isDragging, isDragOver }; +} diff --git a/apps/desktop/src/renderer/stores/tabs/store.ts b/apps/desktop/src/renderer/stores/tabs/store.ts index 7113eef97a4..6885a496db4 100644 --- a/apps/desktop/src/renderer/stores/tabs/store.ts +++ b/apps/desktop/src/renderer/stores/tabs/store.ts @@ -38,12 +38,56 @@ interface TabsState { getLastActiveTabId: (workspaceId: string) => string | null; } +// Create initial test tabs +const createInitialTabs = (): Tab[] => { + const workspaceId = "workspace-1"; + + // Create a single tab + const singleTab: Tab = { + id: "tab-single-1", + title: "Welcome Tab", + workspaceId, + type: TabType.Single, + isNew: false, + }; + + // Create a group tab with two panes + const groupTab: Tab = { + id: "tab-group-1", + title: "Split View Example", + workspaceId, + type: TabType.Group, + isNew: false, + layout: { + direction: "row", + first: "pane-1", + second: "pane-2", + splitPercentage: 50, + }, + panes: { + "pane-1": { title: "Left Pane" }, + "pane-2": { title: "Right Pane" }, + }, + }; + + // Create another single tab + const singleTab2: Tab = { + id: "tab-single-2", + title: "Another Tab", + workspaceId, + type: TabType.Single, + isNew: false, + }; + + return [singleTab, groupTab, singleTab2]; +}; + export const useTabsStore = create()( devtools( (set, get) => ({ - tabs: [], - activeTabIds: {}, - tabHistoryStacks: {}, + tabs: createInitialTabs(), + activeTabIds: { "workspace-1": "tab-single-1" }, + tabHistoryStacks: { "workspace-1": [] }, addTab: (workspaceId, type = TabType.Single) => { const newTab = createNewTab(workspaceId, type); From 7ae734eba5912b681811c13099b9466a567b498d Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 19 Nov 2025 16:19:46 -0800 Subject: [PATCH 09/21] mosaic ui --- .../ContentView/TabsContent/GroupTabView.tsx | 106 ++++++++------ .../ContentView/TabsContent/SingleTabView.tsx | 4 +- .../ContentView/TabsContent/mosaic-theme.css | 65 +++++++++ .../TabsContent/useDropTabTarget.ts | 2 +- .../Sidebar/TabsView/TabItem/index.tsx | 130 +++++++++++++----- .../Sidebar/TabsView/TabItem/types.ts | 5 +- .../Sidebar/TabsView/TabItem/useDragTab.ts | 2 +- .../WorkspaceView/Sidebar/TabsView/index.tsx | 7 +- .../desktop/src/renderer/stores/tabs/store.ts | 36 +++-- .../desktop/src/renderer/stores/tabs/types.ts | 6 +- 10 files changed, 269 insertions(+), 94 deletions(-) create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/mosaic-theme.css diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx index b4ba7c8743c..3deff69be44 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx @@ -1,56 +1,82 @@ +import { useCallback } from "react"; +import { + Mosaic, + type MosaicBranch, + type MosaicNode, + MosaicWindow, +} from "react-mosaic-component"; +import "react-mosaic-component/react-mosaic-component.css"; +import { dragDropManager } from "renderer/lib/dnd"; import type { TabGroup } from "renderer/stores"; -import { useDropTabTarget } from "./useDropTabTarget"; +import "./mosaic-theme.css"; interface GroupTabViewProps { tab: TabGroup; } export function GroupTabView({ tab }: GroupTabViewProps) { - const { drop, isDropZone } = useDropTabTarget(tab); + const handleLayoutChange = useCallback( + (newLayout: MosaicNode | null) => { + // TODO: Persist layout changes to store + console.log("Layout changed:", newLayout); + }, + [], + ); - return ( -
} - className={`flex-1 h-full overflow-auto bg-background transition-colors ${ - isDropZone ? "bg-primary/10" : "" - }`} - > -
-
-
-

- {tab.title} -

-

- Split view - {Object.keys(tab.panes).length} panes{" "} - {isDropZone && "- Drop to add pane"} -

+ const renderPane = useCallback( + (paneId: string, path: MosaicBranch[]) => { + const pane = tab.panes[paneId]; + + if (!pane) { + return ( +
+ Pane not found: {paneId}
-
-

- React-mosaic split view will appear here -

-
- {Object.entries(tab.panes).map(([paneId, pane]) => ( -
- - {pane.title} ({paneId}) -
- ))} + ); + } + + return ( + + path={path} + title={pane.title} + toolbarControls={
} + > +
+ {/* TODO: Render actual pane content */} +
+ {pane.title} content will appear here
- {isDropZone && ( -

- Drop here to add to this split view -

- )}
+ + ); + }, + [tab.panes], + ); + + const paneCount = Object.keys(tab.panes).length; + + if (paneCount === 0) { + return ( +
+
+

No panes in this group

+

+ Create a new pane to get started +

+ ); + } + + return ( +
+ + renderTile={renderPane} + value={tab.layout} + onChange={handleLayoutChange} + className="mosaic-theme-dark" + dragAndDropManager={dragDropManager} + />
); } diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx index 8a8ceb7206d..29f47dbc812 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx @@ -32,7 +32,9 @@ export function SingleTabView({ tab }: SingleTabViewProps) { : "border-border" }`} > -

Tab content will appear here

+

+ Tab content will appear here +

{isDropZone && (

Drop here to create a split view diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/mosaic-theme.css b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/mosaic-theme.css new file mode 100644 index 00000000000..7a6cc963807 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/mosaic-theme.css @@ -0,0 +1,65 @@ +/* Container */ +.mosaic-container { + background: hsl(var(--background)); +} + +/* Window */ +.mosaic-theme-dark .mosaic-window { + background: hsl(var(--background)); + border: 1px solid hsl(var(--border)); + transition: + border-color 150ms ease, + box-shadow 150ms ease; +} + +/* Toolbar */ +.mosaic-theme-dark .mosaic-window .mosaic-window-toolbar { + background: hsl(var(--muted)); + border-bottom: 1px solid hsl(var(--border)); + height: 32px; + padding: 0 8px; + transition: background-color 150ms ease; +} + +.mosaic-theme-dark .mosaic-window .mosaic-window-title { + color: hsl(var(--foreground)); + font-size: 12px; + font-weight: 500; + transition: color 150ms ease; +} + +/* Window body */ +.mosaic-theme-dark .mosaic-window-body { + background: hsl(var(--background)); +} + +/* Split dividers */ +.mosaic-theme-dark .mosaic-split { + background: hsl(var(--border)); + opacity: 0; + border-radius: 25px; + transition: + opacity 200ms ease, + background-color 200ms ease; +} + +.mosaic-theme-dark .mosaic-split:hover { + opacity: 1; + background: hsl(var(--muted-foreground) / 0.3); +} + +.mosaic-theme-dark .mosaic-split.mosaic-split-dragging { + opacity: 1; + background: hsl(var(--primary) / 0.5); +} + +/* Focus states */ +.mosaic-window-content { + outline: none; +} + +.mosaic-window-content:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: -2px; + box-shadow: inset 0 0 0 4px hsl(var(--ring) / 0.2); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useDropTabTarget.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useDropTabTarget.ts index cd4032b1edf..5de7dc60f5b 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useDropTabTarget.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useDropTabTarget.ts @@ -1,7 +1,7 @@ import { useDrop } from "react-dnd"; import type { Tab } from "renderer/stores"; import { useTabsStore } from "renderer/stores"; -import { TAB_DND_TYPE, type DragItem } from "./types"; +import { type DragItem, TAB_DND_TYPE } from "./types"; export function useDropTabTarget(activeTab: Tab | null) { const dragTabToTab = useTabsStore((state) => state.dragTabToTab); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx index b1319b62802..6edd08fc72e 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx @@ -1,66 +1,134 @@ import { Button } from "@superset/ui/button"; -import { HiMiniXMark } from "react-icons/hi2"; +import { useState } from "react"; +import { HiChevronRight, HiMiniXMark } from "react-icons/hi2"; import { useRemoveTab, useSetActiveTab, useWorkspacesStore, } from "renderer/stores"; +import { TabType } from "renderer/stores/tabs/types"; import type { TabItemProps } from "./types"; import { useDragTab } from "./useDragTab"; -export function TabItem({ tabId, title, isActive }: TabItemProps) { +export function TabItem({ tab, isActive }: TabItemProps) { + const [isExpanded, setIsExpanded] = useState(true); const activeWorkspaceId = useWorkspacesStore( (state) => state.activeWorkspaceId, ); const removeTab = useRemoveTab(); const setActiveTab = useSetActiveTab(); - const { drag, drop, isDragging, isDragOver } = useDragTab(tabId); + const { drag, drop, isDragging, isDragOver } = useDragTab(tab.id); const handleRemoveTab = (e: React.MouseEvent) => { e.stopPropagation(); - removeTab(tabId); + removeTab(tab.id); }; const handleTabClick = () => { if (activeWorkspaceId) { - setActiveTab(activeWorkspaceId, tabId); + setActiveTab(activeWorkspaceId, tab.id); } }; + const handleToggleExpand = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsExpanded(!isExpanded); + }; + + const handlePaneClick = (_paneId: string) => { + // Make the parent group tab active when a child pane is clicked + if (activeWorkspaceId) { + setActiveTab(activeWorkspaceId, tab.id); + // TODO: Track active pane within group tab + } + }; + + const handleRemovePane = ( + e: React.MouseEvent, + paneId: string, + ) => { + e.stopPropagation(); + // TODO: Implement pane removal from group + console.log("Remove pane:", paneId); + }; + // Combine drag and drop refs const attachRef = (el: HTMLButtonElement | null) => { drag(el); drop(el); }; + const isGroupTab = tab.type === TabType.Group; + const childPanes = isGroupTab ? Object.entries(tab.panes) : []; + return ( - - +

+ {isGroupTab && ( + + )} + {tab.title} +
+ {!isGroupTab && ( + + )} + + + {isGroupTab && isExpanded && ( +
+ {childPanes.map(([paneId, pane]) => ( + + + ))} +
+ )} +
); } diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts index 827399e79e4..84a17b92a81 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts @@ -1,3 +1,5 @@ +import type { Tab } from "renderer/stores/tabs/types"; + export interface DragItem { type: "TAB"; tabId: string; @@ -6,7 +8,6 @@ export interface DragItem { export const TAB_DND_TYPE = "TAB"; export interface TabItemProps { - tabId: string; - title: string; + tab: Tab; isActive: boolean; } diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts index ab5d0559076..df3cc6b779e 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts @@ -1,6 +1,6 @@ import { useDrag, useDrop } from "react-dnd"; import { useTabsStore } from "renderer/stores"; -import { TAB_DND_TYPE, type DragItem } from "./types"; +import { type DragItem, TAB_DND_TYPE } from "./types"; export function useDragTab(tabId: string) { const dragTabToTab = useTabsStore((state) => state.dragTabToTab); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx index d57271734ad..6dee5b51315 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx @@ -39,12 +39,7 @@ export function TabsView() {