Skip to content
Closed
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
14 changes: 4 additions & 10 deletions apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { db } from "main/lib/db";
import { terminalManager } from "main/lib/terminal-manager";
import { z } from "zod";
import { publicProcedure, router } from "../..";
import { getWorktreePath } from "../workspaces/utils/worktree";

/**
* Terminal router using TerminalManager with node-pty
Expand Down Expand Up @@ -44,17 +45,10 @@ export const createTerminalRouter = () => {

// Get workspace to determine cwd and workspace name
const workspace = db.data.workspaces.find((w) => w.id === workspaceId);
let cwd: string | undefined = cwdOverride;
const workspaceName = workspace?.name || "Workspace";

if (!cwd && workspace) {
const worktree = db.data.worktrees.find(
(wt) => wt.id === workspace.worktreeId,
);
if (worktree) {
cwd = worktree.path;
}
}
const cwd =
cwdOverride ||
(workspace ? getWorktreePath(workspace.worktreeId) : undefined);

const result = await terminalManager.createOrAttach({
tabId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { db } from "main/lib/db";

/**
* Gets the worktree path for a workspace
*/
export function getWorktreePath(worktreeId: string): string | undefined {
const worktree = db.data.worktrees.find((w) => w.id === worktreeId);
return worktree?.path;
}
7 changes: 6 additions & 1 deletion apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
worktreeExists,
} from "./utils/git";
import { copySetupFiles, loadSetupConfig } from "./utils/setup";
import { getWorktreePath } from "./utils/worktree";

export const createWorkspacesRouter = () => {
return router({
Expand Down Expand Up @@ -153,6 +154,7 @@ export const createWorkspacesRouter = () => {
id: string;
projectId: string;
worktreeId: string;
worktreePath: string;
name: string;
tabOrder: number;
createdAt: number;
Expand Down Expand Up @@ -180,7 +182,10 @@ export const createWorkspacesRouter = () => {

for (const workspace of workspaces) {
if (groupsMap.has(workspace.projectId)) {
groupsMap.get(workspace.projectId)?.workspaces.push(workspace);
groupsMap.get(workspace.projectId)?.workspaces.push({
...workspace,
worktreePath: getWorktreePath(workspace.worktreeId) ?? "",
});
}
}

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--tertiary: oklch(0.95 0.003 40);
--tertiary-active: oklch(0.90 0.003 40);
--tertiary-active: oklch(0.9 0.003 40);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.922 0 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { WorkspaceItem } from "./WorkspaceItem";
interface Workspace {
id: string;
projectId: string;
worktreePath: string;
name: string;
tabOrder: number;
}
Expand Down Expand Up @@ -71,6 +72,7 @@ export function WorkspaceGroup({
<WorkspaceItem
id={workspace.id}
projectId={workspace.projectId}
worktreePath={workspace.worktreePath}
title={workspace.name}
isActive={workspace.id === activeWorkspaceId}
index={index}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import {
import { useTabs } from "renderer/stores";
import { DeleteWorkspaceDialog } from "./DeleteWorkspaceDialog";
import { useWorkspaceRename } from "./useWorkspaceRename";
import { WorkspaceItemContextMenu } from "./WorkspaceItemContextMenu";

const WORKSPACE_TYPE = "WORKSPACE";

interface WorkspaceItemProps {
id: string;
projectId: string;
worktreePath: string;
title: string;
isActive: boolean;
index: number;
Expand All @@ -27,6 +29,7 @@ interface WorkspaceItemProps {
export function WorkspaceItem({
id,
projectId,
worktreePath,
title,
isActive,
index,
Expand Down Expand Up @@ -72,75 +75,80 @@ export function WorkspaceItem({

return (
<>
<div
className="group relative flex items-end shrink-0 h-full no-drag"
style={{ width: `${width}px` }}
<WorkspaceItemContextMenu
worktreePath={worktreePath}
onRename={rename.startRename}
>
{/* Main workspace button */}
<button
type="button"
ref={(node) => {
drag(drop(node));
}}
onMouseDown={() => !rename.isRenaming && setActive.mutate({ id })}
onDoubleClick={rename.startRename}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={`
flex items-center gap-0.5 rounded-t-md transition-all w-full shrink-0 pr-6 pl-3 h-[80%]
${
isActive
? "text-foreground bg-tertiary-active"
: "text-muted-foreground hover:text-foreground hover:bg-tertiary/30"
}
${isDragging ? "opacity-30" : "opacity-100"}
`}
style={{ cursor: isDragging ? "grabbing" : "pointer" }}
<div
className="group relative flex items-end shrink-0 h-full no-drag"
style={{ width: `${width}px` }}
>
{rename.isRenaming ? (
<input
ref={rename.inputRef}
type="text"
value={rename.renameValue}
onChange={(e) => rename.setRenameValue(e.target.value)}
onBlur={rename.submitRename}
onKeyDown={rename.handleKeyDown}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
className="flex-1 min-w-0 bg-muted border border-primary rounded px-1 py-0.5 text-sm outline-none"
/>
) : (
<>
<span className="text-sm whitespace-nowrap truncate flex-1 text-left">
{title}
</span>
{needsAttention && (
<span className="relative flex size-2 shrink-0">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-red-400 opacity-75" />
<span className="relative inline-flex size-2 rounded-full bg-red-500" />
{/* Main workspace button */}
<button
type="button"
ref={(node) => {
drag(drop(node));
}}
onMouseDown={() => !rename.isRenaming && setActive.mutate({ id })}
onDoubleClick={rename.startRename}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={`
flex items-center gap-0.5 rounded-t-md transition-all w-full shrink-0 pr-6 pl-3 h-[80%]
${
isActive
? "text-foreground bg-tertiary-active"
: "text-muted-foreground hover:text-foreground hover:bg-tertiary/30"
}
${isDragging ? "opacity-30" : "opacity-100"}
`}
style={{ cursor: isDragging ? "grabbing" : "pointer" }}
>
{rename.isRenaming ? (
<input
ref={rename.inputRef}
type="text"
value={rename.renameValue}
onChange={(e) => rename.setRenameValue(e.target.value)}
onBlur={rename.submitRename}
onKeyDown={rename.handleKeyDown}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
className="flex-1 min-w-0 bg-muted border border-primary rounded px-1 py-0.5 text-sm outline-none"
/>
) : (
<>
<span className="text-sm whitespace-nowrap truncate flex-1 text-left">
{title}
</span>
)}
</>
)}
</button>
{needsAttention && (
<span className="relative flex size-2 shrink-0">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-red-400 opacity-75" />
<span className="relative inline-flex size-2 rounded-full bg-red-500" />
</span>
)}
</>
)}
</button>

<Button
type="button"
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
setShowDeleteDialog(true);
}}
className={cn(
"mt-1 absolute right-1 top-1/2 -translate-y-1/2 cursor-pointer size-5 group-hover:opacity-100",
isActive ? "opacity-90" : "opacity-0",
)}
aria-label="Close workspace"
>
<HiMiniXMark />
</Button>
</div>
<Button
type="button"
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
setShowDeleteDialog(true);
}}
className={cn(
"mt-1 absolute right-1 top-1/2 -translate-y-1/2 cursor-pointer size-5 group-hover:opacity-100",
isActive ? "opacity-90" : "opacity-0",
)}
aria-label="Close workspace"
>
<HiMiniXMark />
</Button>
</div>
</WorkspaceItemContextMenu>

<DeleteWorkspaceDialog
workspaceId={id}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuTrigger,
} from "@superset/ui/context-menu";
import type { ReactNode } from "react";
import { trpc } from "renderer/lib/trpc";

interface WorkspaceItemContextMenuProps {
children: ReactNode;
worktreePath: string;
onRename: () => void;
}

export function WorkspaceItemContextMenu({
children,
worktreePath,
onRename,
}: WorkspaceItemContextMenuProps) {
const openInFinder = trpc.external.openInFinder.useMutation();

const handleOpenInFinder = () => {
if (worktreePath) {
openInFinder.mutate(worktreePath);
}
};

return (
<ContextMenu>
<ContextMenuTrigger asChild>{children}</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem onSelect={onRename}>Rename</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem onSelect={handleOpenInFinder}>
Open in Finder
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
);
}
38 changes: 13 additions & 25 deletions apps/desktop/src/renderer/screens/main/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,34 +29,22 @@ export function MainScreen() {
const isWorkspaceView = currentView === "workspace";

// Sidebar toggle shortcut - only in workspace view
useHotkeys(
"meta+s",
() => {
if (isWorkspaceView) toggleSidebar();
},
[toggleSidebar, isWorkspaceView],
);
useHotkeys("meta+s", () => {
if (isWorkspaceView) toggleSidebar();
}, [toggleSidebar, isWorkspaceView]);

// Split view shortcuts - only in workspace view
useHotkeys(
"meta+d",
() => {
if (isWorkspaceView && activeWorkspaceId) {
splitTabVertical(activeWorkspaceId);
}
},
[activeWorkspaceId, splitTabVertical, isWorkspaceView],
);
useHotkeys("meta+d", () => {
if (isWorkspaceView && activeWorkspaceId) {
splitTabVertical(activeWorkspaceId);
}
}, [activeWorkspaceId, splitTabVertical, isWorkspaceView]);

useHotkeys(
"meta+shift+d",
() => {
if (isWorkspaceView && activeWorkspaceId) {
splitTabHorizontal(activeWorkspaceId);
}
},
[activeWorkspaceId, splitTabHorizontal, isWorkspaceView],
);
useHotkeys("meta+shift+d", () => {
if (isWorkspaceView && activeWorkspaceId) {
splitTabHorizontal(activeWorkspaceId);
}
}, [activeWorkspaceId, splitTabHorizontal, isWorkspaceView]);

return (
<DndProvider manager={dragDropManager}>
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/resources/public/theme-boot.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Apply saved theme class immediately to prevent flash of wrong colors
// This runs before React hydration to ensure correct initial appearance
(function () {
(() => {
try {
var themeType = localStorage.getItem("theme-type");
document.documentElement.classList.add(
Expand Down
Loading