Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/desktop/src/renderer/routes.tsx
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Button } from "@superset/ui/button";
import { useWorkspacesStore } from "renderer/stores/workspaces";

export function AddWorkspaceButton() {
const { addWorkspace } = useWorkspacesStore();

return (
<Button
variant="ghost"
size="icon"
onClick={addWorkspace}
aria-label="Add new workspace"
>
<span className="text-lg">+</span>
</Button>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,20 +16,21 @@ interface TabItemProps {
onMouseLeave?: () => void;
}

export function TabItem({
export function WorkspaceItem({
id,
title,
isActive,
index,
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(),
Expand All @@ -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;
}
},
Expand All @@ -53,16 +54,13 @@ export function TabItem({
className="group relative flex items-end shrink-0 h-full"
style={{ width: `${width}px` }}
>
{/* Active tab bottom border overlay */}
{isActive && <div className="absolute bottom-0 left-0 right-0 h-px" />}

{/* Main tab button */}
{/* Main workspace button */}
<button
type="button"
ref={(node) => {
drag(drop(node));
}}
onClick={() => setActiveTab(id)}
onClick={() => setActiveWorkspace(id)}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={`
Expand All @@ -87,13 +85,13 @@ export function TabItem({
size="icon"
onClick={(e) => {
e.stopPropagation();
removeTab(id);
removeWorkspace(id);
}}
className={cn(
"mt-1 absolute right-1 top-1/2 -translate-y-1/2 size-5 ",
isActive ? "opacity-90" : "opacity-0 group-hover:opacity-90",
)}
aria-label="Close tab"
aria-label="Close workspace"
>
<HiMiniXMark className="size-4" />
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 WorkspacesTabs() {
const { workspaces, activeWorkspaceId } = useWorkspacesStore();
const containerRef = useRef<HTMLDivElement>(null);
const scrollRef = useRef<HTMLDivElement>(null);
const [showStartFade, setShowStartFade] = useState(false);
const [showEndFade, setShowEndFade] = useState(false);
const [tabWidth, setTabWidth] = useState(MAX_TAB_WIDTH);
const [hoveredTabId, setHoveredTabId] = useState<string | null>(null);
const [workspaceWidth, setWorkspaceWidth] = useState(MAX_WORKSPACE_WIDTH);
const [hoveredWorkspaceId, setHoveredWorkspaceId] = useState<string | null>(
null,
);

useEffect(() => {
const checkScroll = () => {
Expand All @@ -26,37 +28,37 @@ export function Tabs() {
setShowEndFade(scrollLeft < scrollWidth - clientWidth - 1);
};

const updateTabWidth = () => {
const updateWorkspaceWidth = () => {
if (!containerRef.current) return;

const containerWidth = containerRef.current.offsetWidth;
const availableWidth = containerWidth - ADD_BUTTON_WIDTH;

// 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 (
<div
Expand All @@ -69,31 +71,31 @@ export function Tabs() {
ref={scrollRef}
className="flex h-full overflow-x-auto hide-scrollbar gap-2"
>
{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 (
<Fragment key={tab.id}>
<Fragment key={workspace.id}>
<div className="flex items-end h-full">
<TabItem
id={tab.id}
title={tab.title}
<WorkspaceItem
id={workspace.id}
title={workspace.title}
isActive={isActive}
index={index}
width={tabWidth}
onMouseEnter={() => setHoveredTabId(tab.id)}
onMouseLeave={() => setHoveredTabId(null)}
width={workspaceWidth}
onMouseEnter={() => setHoveredWorkspaceId(workspace.id)}
onMouseLeave={() => setHoveredWorkspaceId(null)}
/>
</div>
{index < tabs.length - 1 && (
{index < workspaces.length - 1 && (
<div
className="flex items-center h-full py-2 transition-opacity"
style={{ opacity: separatorOpacity / 100 }}
Expand All @@ -115,7 +117,7 @@ export function Tabs() {
)}
</div>

<AddTabButton />
<AddWorkspaceButton />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { trpc } from "renderer/lib/trpc";
import { SidebarControl } from "./SidebarControl";
import { Tabs } from "./Tabs";
import { WindowControls } from "./WindowControls";
import { WorkspacesTabs } from "./WorkspaceTabs";

export function TopBar() {
const { data: platform } = trpc.window.getPlatform.useQuery();
const isMac = platform === "darwin";
return (
<div className="drag gap-2 h-12 w-full flex items-center justify-between border-b border-border bg-background">
<div className="drag gap-2 h-12 w-full flex items-center justify-between border-b border-sidebar bg-background">
<div
className="flex items-center gap-4 h-full"
style={{
Expand All @@ -17,7 +17,7 @@ export function TopBar() {
<SidebarControl />
</div>
<div className="no-drag flex items-center gap-2 flex-1 overflow-hidden h-full">
<Tabs />
<WorkspacesTabs />
</div>
<div className="no-drag flex items-center gap-2 h-full pr-4">
{!isMac && <WindowControls />}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function ChangesContent() {
return (
<div className="flex-1 h-full overflow-auto bg-background">
<div className="h-full w-full p-6">
<div className="flex items-center justify-center h-full">
<div className="text-center">
<h2 className="text-2xl font-semibold text-foreground mb-2">
Changes
</h2>
<p className="text-muted-foreground">Coming soon...</p>
</div>
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export function EmptyTabView() {
return (
<div className="flex-1 h-full overflow-auto">
<div className="h-full w-full p-6">
<div className="flex items-center justify-center h-full">
<div className="text-center">
<h2 className="text-2xl font-semibold text-foreground mb-2">
No Active Tab
</h2>
<p className="text-muted-foreground">
Create a new tab to get started
</p>
</div>
</div>
</div>
</div>
);
}
Loading
Loading