Skip to content

Commit 17c2176

Browse files
authored
feat: Inline task rename on double-click and right-click (#857)
1 parent 74e3753 commit 17c2176

6 files changed

Lines changed: 239 additions & 164 deletions

File tree

apps/twig/src/renderer/components/RenameTaskDialog.tsx

Lines changed: 0 additions & 108 deletions
This file was deleted.

apps/twig/src/renderer/features/sidebar/components/SidebarItem.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface SidebarItemProps {
99
subtitle?: React.ReactNode;
1010
isActive?: boolean;
1111
onClick?: () => void;
12+
onDoubleClick?: () => void;
1213
onContextMenu?: (e: React.MouseEvent) => void;
1314
action?: SidebarItemAction;
1415
endContent?: React.ReactNode;
@@ -21,6 +22,7 @@ export function SidebarItem({
2122
subtitle,
2223
isActive,
2324
onClick,
25+
onDoubleClick,
2426
onContextMenu,
2527
endContent,
2628
}: SidebarItemProps) {
@@ -34,6 +36,7 @@ export function SidebarItem({
3436
gap: "4px",
3537
}}
3638
onClick={onClick}
39+
onDoubleClick={onDoubleClick}
3740
onContextMenu={onContextMenu}
3841
>
3942
{icon && (

apps/twig/src/renderer/features/sidebar/components/SidebarMenu.tsx

Lines changed: 91 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import { DotsCircleSpinner } from "@components/DotsCircleSpinner";
2-
import { RenameTaskDialog } from "@components/RenameTaskDialog";
3-
import { useDeleteTask, useTasks } from "@features/tasks/hooks/useTasks";
2+
import {
3+
useDeleteTask,
4+
useTasks,
5+
useUpdateTask,
6+
} from "@features/tasks/hooks/useTasks";
47
import { useTaskContextMenu } from "@hooks/useTaskContextMenu";
58
import { Box, Flex } from "@radix-ui/themes";
9+
import { logger } from "@renderer/lib/logger";
610
import type { Task } from "@shared/types";
711
import { useNavigationStore } from "@stores/navigationStore";
8-
import { memo, useEffect, useRef } from "react";
12+
import { useQueryClient } from "@tanstack/react-query";
13+
import { memo, useCallback, useEffect, useRef } from "react";
914
import { useWorkspaceStore } from "@/renderer/features/workspace/stores/workspaceStore";
1015
import { useSidebarData } from "../hooks/useSidebarData";
1116
import { usePinnedTasksStore } from "../stores/pinnedTasksStore";
@@ -22,7 +27,7 @@ function SidebarMenuComponent() {
2227
const workspaces = useWorkspaceStore.use.workspaces();
2328
const markAsViewed = useTaskViewedStore((state) => state.markAsViewed);
2429

25-
const { showContextMenu, renameTask, renameDialogOpen, setRenameDialogOpen } =
30+
const { showContextMenu, editingTaskId, setEditingTaskId } =
2631
useTaskContextMenu();
2732
const { deleteWithConfirm } = useDeleteTask();
2833
const togglePin = usePinnedTasksStore((state) => state.togglePin);
@@ -94,52 +99,90 @@ function SidebarMenuComponent() {
9499
});
95100
};
96101

102+
const updateTask = useUpdateTask();
103+
const queryClient = useQueryClient();
104+
const log = logger.scope("sidebar-menu");
105+
106+
const handleTaskDoubleClick = useCallback(
107+
(taskId: string) => {
108+
setEditingTaskId(taskId);
109+
},
110+
[setEditingTaskId],
111+
);
112+
113+
const handleTaskEditSubmit = useCallback(
114+
async (taskId: string, newTitle: string) => {
115+
setEditingTaskId(null);
116+
117+
// Optimistically update task title in all cached task lists
118+
queryClient.setQueriesData<Task[]>(
119+
{ queryKey: ["tasks", "list"] },
120+
(old) =>
121+
old?.map((task) =>
122+
task.id === taskId ? { ...task, title: newTitle } : task,
123+
),
124+
);
125+
126+
try {
127+
await updateTask.mutateAsync({
128+
taskId,
129+
updates: { title: newTitle },
130+
});
131+
} catch (error) {
132+
log.error("Failed to rename task", error);
133+
// Refetch to revert optimistic update on failure
134+
queryClient.invalidateQueries({ queryKey: ["tasks", "list"] });
135+
}
136+
},
137+
[setEditingTaskId, updateTask, queryClient, log],
138+
);
139+
140+
const handleTaskEditCancel = useCallback(() => {
141+
setEditingTaskId(null);
142+
}, [setEditingTaskId]);
143+
97144
return (
98-
<>
99-
<RenameTaskDialog
100-
task={renameTask}
101-
open={renameDialogOpen}
102-
onOpenChange={setRenameDialogOpen}
103-
/>
104-
105-
<Box height="100%" position="relative">
106-
<Box
107-
style={{
108-
height: "100%",
109-
overflowY: "auto",
110-
overflowX: "hidden",
111-
}}
112-
>
113-
<Flex direction="column" py="2">
114-
<Box mb="2">
115-
<NewTaskItem
116-
isActive={sidebarData.isHomeActive}
117-
onClick={handleNewTaskClick}
118-
/>
119-
</Box>
120-
121-
{sidebarData.isLoading ? (
122-
<SidebarItem
123-
depth={0}
124-
icon={<DotsCircleSpinner size={12} className="text-gray-10" />}
125-
label="Loading tasks..."
126-
/>
127-
) : (
128-
<TaskListView
129-
pinnedTasks={sidebarData.pinnedTasks}
130-
flatTasks={sidebarData.flatTasks}
131-
groupedTasks={sidebarData.groupedTasks}
132-
activeTaskId={sidebarData.activeTaskId}
133-
onTaskClick={handleTaskClick}
134-
onTaskContextMenu={handleTaskContextMenu}
135-
onTaskDelete={handleTaskDelete}
136-
hasMore={sidebarData.hasMore}
137-
/>
138-
)}
139-
</Flex>
140-
</Box>
145+
<Box height="100%" position="relative">
146+
<Box
147+
style={{
148+
height: "100%",
149+
overflowY: "auto",
150+
overflowX: "hidden",
151+
}}
152+
>
153+
<Flex direction="column" py="2">
154+
<Box mb="2">
155+
<NewTaskItem
156+
isActive={sidebarData.isHomeActive}
157+
onClick={handleNewTaskClick}
158+
/>
159+
</Box>
160+
161+
{sidebarData.isLoading ? (
162+
<SidebarItem
163+
depth={0}
164+
icon={<DotsCircleSpinner size={12} className="text-gray-10" />}
165+
label="Loading tasks..."
166+
/>
167+
) : (
168+
<TaskListView
169+
pinnedTasks={sidebarData.pinnedTasks}
170+
flatTasks={sidebarData.flatTasks}
171+
groupedTasks={sidebarData.groupedTasks}
172+
activeTaskId={sidebarData.activeTaskId}
173+
editingTaskId={editingTaskId}
174+
onTaskClick={handleTaskClick}
175+
onTaskDoubleClick={handleTaskDoubleClick}
176+
onTaskContextMenu={handleTaskContextMenu}
177+
onTaskDelete={handleTaskDelete}
178+
onTaskEditSubmit={handleTaskEditSubmit}
179+
onTaskEditCancel={handleTaskEditCancel}
180+
hasMore={sidebarData.hasMore}
181+
/>
182+
)}
183+
</Flex>
141184
</Box>
142-
</>
185+
</Box>
143186
);
144187
}
145188

0 commit comments

Comments
 (0)