Skip to content
Open
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
3 changes: 3 additions & 0 deletions apps/code/src/main/services/context-menu/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export const taskContextMenuInput = z.object({
folderPath: z.string().optional(),
isPinned: z.boolean().optional(),
isSuspended: z.boolean().optional(),
isInCommandCenter: z.boolean().optional(),
hasEmptyCommandCenterCell: z.boolean().optional(),
});

export const archivedTaskContextMenuInput = z.object({
Expand Down Expand Up @@ -40,6 +42,7 @@ const taskAction = z.discriminatedUnion("type", [
z.object({ type: z.literal("archive") }),
z.object({ type: z.literal("archive-prior") }),
z.object({ type: z.literal("delete") }),
z.object({ type: z.literal("add-to-command-center") }),
z.object({ type: z.literal("external-app"), action: externalAppAction }),
]);

Expand Down
19 changes: 18 additions & 1 deletion apps/code/src/main/services/context-menu/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,14 @@ export class ContextMenuService {
async showTaskContextMenu(
input: TaskContextMenuInput,
): Promise<TaskContextMenuResult> {
const { worktreePath, folderPath, isPinned, isSuspended } = input;
const {
worktreePath,
folderPath,
isPinned,
isSuspended,
isInCommandCenter,
hasEmptyCommandCenterCell,
} = input;
const { apps, lastUsedAppId } = await this.getExternalAppsData();
const hasPath = worktreePath || folderPath;

Expand All @@ -126,6 +133,16 @@ export class ContextMenuService {
...this.externalAppItems<TaskAction>(apps, lastUsedAppId),
]
: []),
...(!isInCommandCenter
? [
this.separator(),
this.item(
"Add to Command Center",
{ type: "add-to-command-center" as const },
{ enabled: hasEmptyCommandCenterCell ?? true },
),
]
: []),
this.separator(),
this.item("Archive", { type: "archive" }),
this.item(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Combobox } from "@components/ui/combobox/Combobox";
import { Plus } from "@phosphor-icons/react";
import { Popover, Separator } from "@radix-ui/themes";
import { Popover } from "@radix-ui/themes";
import { useNavigationStore } from "@stores/navigationStore";
import { type ReactNode, useCallback } from "react";
import { useAvailableTasks } from "../hooks/useAvailableTasks";
Expand Down Expand Up @@ -42,42 +43,54 @@ export function TaskSelector({
}, [onOpenChange, onNewTask, navigateToTaskInput]);

return (
<Popover.Root open={open} onOpenChange={onOpenChange}>
<Combobox.Root
open={open}
onOpenChange={onOpenChange}
value=""
onValueChange={handleSelect}
size="1"
>
<Popover.Trigger>{children}</Popover.Trigger>
<Popover.Content
<Combobox.Content
items={availableTasks}
getValue={(task) => task.title}
side="bottom"
align="center"
sideOffset={4}
style={{ padding: 4, minWidth: 240, maxHeight: 300 }}
style={{ minWidth: 240 }}
>
<button
type="button"
onClick={handleNewTask}
className="flex w-full items-center gap-1.5 rounded-sm px-2 py-1.5 text-left text-[12px] text-gray-12 transition-colors hover:bg-gray-3"
>
<Plus size={12} />
New task
</button>
<Separator size="4" className="my-1" />
<div className="overflow-y-auto" style={{ maxHeight: 240 }}>
{availableTasks.length === 0 ? (
<div className="px-2 py-3 text-center font-mono text-[11px] text-gray-10">
No available tasks
</div>
) : (
availableTasks.map((task) => (
<button
{({ filtered, hasMore, moreCount }) => (
<>
<Combobox.Input placeholder="Search tasks..." />
<Combobox.Empty>No matching tasks</Combobox.Empty>
{filtered.map((task) => (
<Combobox.Item
key={task.id}
value={task.id}
textValue={task.title}
>
{task.title}
</Combobox.Item>
))}
{hasMore && (
<div className="combobox-label">
{moreCount} more {moreCount === 1 ? "task" : "tasks"}; type to
filter
</div>
)}
<Combobox.Footer>
<button
type="button"
onClick={() => handleSelect(task.id)}
className="flex w-full items-center rounded-sm px-2 py-1.5 text-left text-[12px] text-gray-12 transition-colors hover:bg-gray-3"
className="combobox-footer-button"
onClick={handleNewTask}
>
<span className="min-w-0 flex-1 truncate">{task.title}</span>
<Plus size={11} weight="bold" />
New task
</button>
))
)}
</div>
</Popover.Content>
</Popover.Root>
</Combobox.Footer>
</>
)}
</Combobox.Content>
</Combobox.Root>
);
}
20 changes: 20 additions & 0 deletions apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ function SidebarMenuComponent() {
}

const commandCenterCells = useCommandCenterStore((s) => s.cells);
const assignTaskToCommandCenter = useCommandCenterStore((s) => s.assignTask);
const commandCenterActiveCount = commandCenterCells.filter(
(taskId) => taskId != null && taskMap.has(taskId),
).length;
Expand Down Expand Up @@ -138,13 +139,32 @@ function SidebarMenuComponent() {
if (task) {
const workspace = workspaces[taskId];
const taskData = allSidebarTasks.find((t) => t.id === taskId);
const isInCommandCenter = commandCenterCells.some(
(id) => id === taskId && taskMap.has(id),
);
const hasEmptyCommandCenterCell = commandCenterCells.some(
(id) => id == null || !taskMap.has(id),
);

showContextMenu(task, e, {
worktreePath: workspace?.worktreePath ?? undefined,
folderPath: workspace?.folderPath ?? undefined,
isPinned,
isSuspended: taskData?.isSuspended,
isInCommandCenter,
hasEmptyCommandCenterCell,
onTogglePin: () => togglePin(taskId),
onArchivePrior: handleArchivePrior,
onAddToCommandCenter: () => {
const cells = useCommandCenterStore.getState().cells;
const idx = cells.findIndex((id) => id == null || !taskMap.has(id));
if (idx !== -1) {
assignTaskToCommandCenter(idx, taskId);
navigateToCommandCenter();
} else {
toast.info("Command center is full");
}
},
});
}
};
Expand Down
11 changes: 11 additions & 0 deletions apps/code/src/renderer/hooks/useTaskContextMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ export function useTaskContextMenu() {
folderPath?: string;
isPinned?: boolean;
isSuspended?: boolean;
isInCommandCenter?: boolean;
hasEmptyCommandCenterCell?: boolean;
onTogglePin?: () => void;
onArchivePrior?: (taskId: string) => void;
onAddToCommandCenter?: () => void;
},
) => {
event.preventDefault();
Expand All @@ -40,8 +43,11 @@ export function useTaskContextMenu() {
folderPath,
isPinned,
isSuspended,
isInCommandCenter,
hasEmptyCommandCenterCell,
onTogglePin,
onArchivePrior,
onAddToCommandCenter,
} = options ?? {};

try {
Expand All @@ -51,6 +57,8 @@ export function useTaskContextMenu() {
folderPath,
isPinned,
isSuspended,
isInCommandCenter,
hasEmptyCommandCenterCell,
});

if (!result.action) return;
Expand Down Expand Up @@ -86,6 +94,9 @@ export function useTaskContextMenu() {
hasWorktree: !!worktreePath,
});
break;
case "add-to-command-center":
onAddToCommandCenter?.();
break;
case "external-app": {
const effectivePath = worktreePath ?? folderPath;
if (effectivePath) {
Expand Down
Loading