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
13 changes: 13 additions & 0 deletions apps/code/src/main/services/context-menu/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export const taskContextMenuInput = z.object({
hasEmptyCommandCenterCell: z.boolean().optional(),
});

export const bulkTaskContextMenuInput = z.object({
taskCount: z.number().int().min(2),
});

export const archivedTaskContextMenuInput = z.object({
taskTitle: z.string(),
});
Expand Down Expand Up @@ -45,6 +49,10 @@ const taskAction = z.discriminatedUnion("type", [
z.object({ type: z.literal("external-app"), action: externalAppAction }),
]);

const bulkTaskAction = z.discriminatedUnion("type", [
z.object({ type: z.literal("archive") }),
]);

const archivedTaskAction = z.discriminatedUnion("type", [
z.object({ type: z.literal("restore") }),
z.object({ type: z.literal("delete") }),
Expand Down Expand Up @@ -72,6 +80,9 @@ const splitDirection = z.enum(["left", "right", "up", "down"]);
export const taskContextMenuOutput = z.object({
action: taskAction.nullable(),
});
export const bulkTaskContextMenuOutput = z.object({
action: bulkTaskAction.nullable(),
});
export const archivedTaskContextMenuOutput = z.object({
action: archivedTaskAction.nullable(),
});
Expand All @@ -87,6 +98,7 @@ export const splitContextMenuOutput = z.object({
});

export type TaskContextMenuInput = z.infer<typeof taskContextMenuInput>;
export type BulkTaskContextMenuInput = z.infer<typeof bulkTaskContextMenuInput>;
export type ArchivedTaskContextMenuInput = z.infer<
typeof archivedTaskContextMenuInput
>;
Expand All @@ -96,6 +108,7 @@ export type FileContextMenuInput = z.infer<typeof fileContextMenuInput>;

export type ExternalAppAction = z.infer<typeof externalAppAction>;
export type TaskAction = z.infer<typeof taskAction>;
export type BulkTaskAction = z.infer<typeof bulkTaskAction>;
export type ArchivedTaskAction = z.infer<typeof archivedTaskAction>;
export type FolderAction = z.infer<typeof folderAction>;
export type TabAction = z.infer<typeof tabAction>;
Expand Down
23 changes: 23 additions & 0 deletions apps/code/src/main/services/context-menu/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import type {
ArchivedTaskAction,
ArchivedTaskContextMenuInput,
ArchivedTaskContextMenuResult,
BulkTaskAction,
BulkTaskContextMenuInput,
ConfirmDeleteArchivedTaskInput,
ConfirmDeleteArchivedTaskResult,
ConfirmDeleteTaskInput,
Expand Down Expand Up @@ -160,6 +162,27 @@ export class ContextMenuService {
]);
}

async showBulkTaskContextMenu(
input: BulkTaskContextMenuInput,
): Promise<{ action: BulkTaskAction | null }> {
const { taskCount } = input;
const label = `Archive ${taskCount} tasks`;
return this.showMenu<BulkTaskAction>([
this.item(
label,
{ type: "archive" },
{
confirm: {
title: "Archive Tasks",
message: `Archive ${taskCount} tasks?`,
detail: "You can unarchive them later.",
confirmLabel: "Archive",
},
},
),
]);
}

async showArchivedTaskContextMenu(
input: ArchivedTaskContextMenuInput,
): Promise<ArchivedTaskContextMenuResult> {
Expand Down
7 changes: 7 additions & 0 deletions apps/code/src/main/trpc/routers/context-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { MAIN_TOKENS } from "../../di/tokens";
import {
archivedTaskContextMenuInput,
archivedTaskContextMenuOutput,
bulkTaskContextMenuInput,
bulkTaskContextMenuOutput,
confirmDeleteArchivedTaskInput,
confirmDeleteArchivedTaskOutput,
confirmDeleteTaskInput,
Expand Down Expand Up @@ -46,6 +48,11 @@ export const contextMenuRouter = router({
.output(taskContextMenuOutput)
.mutation(({ input }) => getService().showTaskContextMenu(input)),

showBulkTaskContextMenu: publicProcedure
.input(bulkTaskContextMenuInput)
.output(bulkTaskContextMenuOutput)
.mutation(({ input }) => getService().showBulkTaskContextMenu(input)),

showArchivedTaskContextMenu: publicProcedure
.input(archivedTaskContextMenuInput)
.output(archivedTaskContextMenuOutput)
Expand Down
21 changes: 21 additions & 0 deletions apps/code/src/renderer/features/sidebar/components/MainSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@ import { useWorkspaces } from "@features/workspace/hooks/useWorkspace";
import { Box } from "@radix-ui/themes";
import { useEffect } from "react";
import { useSidebarStore } from "../stores/sidebarStore";
import { useTaskSelectionStore } from "../stores/taskSelectionStore";
import { Sidebar, SidebarContent } from "./index";

function isEditableTarget(target: EventTarget | null): boolean {
if (!(target instanceof HTMLElement)) return false;
if (target.isContentEditable) return true;
const tag = target.tagName;
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
}

export function MainSidebar() {
const { data: workspaces = {}, isFetched } = useWorkspaces();
const hasCompletedOnboarding = useOnboardingStore(
Expand All @@ -19,6 +27,19 @@ export function MainSidebar() {
}
}, [isFetched, workspaces, hasCompletedOnboarding, setOpenAuto]);

useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key !== "Escape") return;
if (isEditableTarget(e.target)) return;
const { selectedTaskIds, clearSelection } =
useTaskSelectionStore.getState();
if (selectedTaskIds.length === 0) return;
clearSelection();
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, []);

return (
<Box flexShrink="0" className="shrink-0">
<Sidebar>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ interface SidebarItemProps {
label: React.ReactNode;
subtitle?: React.ReactNode;
isActive?: boolean;
isSelected?: boolean;
isDimmed?: boolean;
draggable?: boolean;
onDragStart?: (e: React.DragEvent) => void;
onClick?: () => void;
onClick?: (e: React.MouseEvent) => void;
onDoubleClick?: () => void;
onContextMenu?: (e: React.MouseEvent) => void;
action?: SidebarItemAction;
Expand Down Expand Up @@ -68,6 +69,7 @@ export function SidebarItem({
label,
subtitle,
isActive,
isSelected,
draggable,
onDragStart,
onClick,
Expand All @@ -82,9 +84,10 @@ export function SidebarItem({
className={cn(
"group flex w-full cursor-default text-left text-[13px] leading-snug transition-colors",
"focus-visible:-outline-offset-2 focus-visible:outline-2 focus-visible:outline-accent-8",
"disabled:opacity-100 data-active:bg-fill-selected",
"disabled:opacity-100 data-active:bg-fill-selected data-selected:bg-(--gray-3)",
)}
data-active={isActive || undefined}
data-selected={(isSelected && !isActive) || undefined}
draggable={draggable}
onDragStart={onDragStart}
style={{
Expand Down
Loading
Loading