| ... | @@ -13,7 +13,15 @@ import { | ... | @@ -13,7 +13,15 @@ import { |
| 13 | } from "lucide-react"; | 13 | } from "lucide-react"; |
| 14 | import { ProjectFavicon } from "./ProjectFavicon"; | 14 | import { ProjectFavicon } from "./ProjectFavicon"; |
| 15 | import { autoAnimate } from "@formkit/auto-animate"; | 15 | import { autoAnimate } from "@formkit/auto-animate"; |
| 16 | import { useCallback, useEffect, useMemo, useRef, useState, type MouseEvent } from "react"; | 16 | import { |
| | 17 | useCallback, |
| | 18 | useEffect, |
| | 19 | useMemo, |
| | 20 | useRef, |
| | 21 | useState, |
| | 22 | type MouseEvent, |
| | 23 | type PointerEvent, |
| | 24 | } from "react"; |
| 17 | import { | 25 | import { |
| 18 | DndContext, | 26 | DndContext, |
| 19 | type DragCancelEvent, | 27 | type DragCancelEvent, |
| ... | @@ -93,6 +101,7 @@ import { useThreadSelectionStore } from "../threadSelectionStore"; | ... | @@ -93,6 +101,7 @@ import { useThreadSelectionStore } from "../threadSelectionStore"; |
| 93 | import { isNonEmpty as isNonEmptyString } from "effect/String"; | 101 | import { isNonEmpty as isNonEmptyString } from "effect/String"; |
| 94 | import { | 102 | import { |
| 95 | getVisibleThreadsForProject, | 103 | getVisibleThreadsForProject, |
| | 104 | isContextMenuPointerDown, |
| 96 | resolveProjectStatusIndicator, | 105 | resolveProjectStatusIndicator, |
| 97 | resolveSidebarNewThreadEnvMode, | 106 | resolveSidebarNewThreadEnvMode, |
| 98 | resolveThreadRowClassName, | 107 | resolveThreadRowClassName, |
| ... | @@ -348,6 +357,7 @@ export default function Sidebar() { | ... | @@ -348,6 +357,7 @@ export default function Sidebar() { |
| 348 | const renamingInputRef = useRef<HTMLInputElement | null>(null); | 357 | const renamingInputRef = useRef<HTMLInputElement | null>(null); |
| 349 | const dragInProgressRef = useRef(false); | 358 | const dragInProgressRef = useRef(false); |
| 350 | const suppressProjectClickAfterDragRef = useRef(false); | 359 | const suppressProjectClickAfterDragRef = useRef(false); |
| | 360 | const suppressProjectClickForContextMenuRef = useRef(false); |
| 351 | const [desktopUpdateState, setDesktopUpdateState] = useState<DesktopUpdateState | null>(null); | 361 | const [desktopUpdateState, setDesktopUpdateState] = useState<DesktopUpdateState | null>(null); |
| 352 | const selectedThreadIds = useThreadSelectionStore((s) => s.selectedThreadIds); | 362 | const selectedThreadIds = useThreadSelectionStore((s) => s.selectedThreadIds); |
| 353 | const toggleThreadSelection = useThreadSelectionStore((s) => s.toggleThread); | 363 | const toggleThreadSelection = useThreadSelectionStore((s) => s.toggleThread); |
| ... | @@ -941,9 +951,24 @@ export default function Sidebar() { | ... | @@ -941,9 +951,24 @@ export default function Sidebar() { |
| 941 | animatedThreadListsRef.current.add(node); | 951 | animatedThreadListsRef.current.add(node); |
| 942 | }, []); | 952 | }, []); |
| 943 | | 953 | |
| 944 | const handleProjectTitlePointerDownCapture = useCallback(() => { | 954 | const handleProjectTitlePointerDownCapture = useCallback( |
| 945 | suppressProjectClickAfterDragRef.current = false; | 955 | (event: PointerEvent<HTMLButtonElement>) => { |
| 946 | }, []); | 956 | suppressProjectClickForContextMenuRef.current = false; |
| | 957 | if ( |
| | 958 | isContextMenuPointerDown({ |
| | 959 | button: event.button, |
| | 960 | ctrlKey: event.ctrlKey, |
| | 961 | isMac: isMacPlatform(navigator.platform), |
| | 962 | }) |
| | 963 | ) { |
| | 964 | // Keep context-menu gestures from arming the sortable drag sensor. |
| | 965 | event.stopPropagation(); |
| | 966 | } |
| | 967 | |
| | 968 | suppressProjectClickAfterDragRef.current = false; |
| | 969 | }, |
| | 970 | [], |
| | 971 | ); |
| 947 | | 972 | |
| 948 | const visibleThreads = useMemo( | 973 | const visibleThreads = useMemo( |
| 949 | () => threads.filter((thread) => thread.archivedAt === null), | 974 | () => threads.filter((thread) => thread.archivedAt === null), |
| ... | @@ -1242,6 +1267,7 @@ export default function Sidebar() { | ... | @@ -1242,6 +1267,7 @@ export default function Sidebar() { |
| 1242 | onKeyDown={(event) => handleProjectTitleKeyDown(event, project.id)} | 1267 | onKeyDown={(event) => handleProjectTitleKeyDown(event, project.id)} |
| 1243 | onContextMenu={(event) => { | 1268 | onContextMenu={(event) => { |
| 1244 | event.preventDefault(); | 1269 | event.preventDefault(); |
| | 1270 | suppressProjectClickForContextMenuRef.current = true; |
| 1245 | void handleProjectContextMenu(project.id, { | 1271 | void handleProjectContextMenu(project.id, { |
| 1246 | x: event.clientX, | 1272 | x: event.clientX, |
| 1247 | y: event.clientY, | 1273 | y: event.clientY, |
| ... | @@ -1351,6 +1377,12 @@ export default function Sidebar() { | ... | @@ -1351,6 +1377,12 @@ export default function Sidebar() { |
| 1351 | | 1377 | |
| 1352 | const handleProjectTitleClick = useCallback( | 1378 | const handleProjectTitleClick = useCallback( |
| 1353 | (event: React.MouseEvent<HTMLButtonElement>, projectId: ProjectId) => { | 1379 | (event: React.MouseEvent<HTMLButtonElement>, projectId: ProjectId) => { |
| | 1380 | if (suppressProjectClickForContextMenuRef.current) { |
| | 1381 | suppressProjectClickForContextMenuRef.current = false; |
| | 1382 | event.preventDefault(); |
| | 1383 | event.stopPropagation(); |
| | 1384 | return; |
| | 1385 | } |
| 1354 | if (dragInProgressRef.current) { | 1386 | if (dragInProgressRef.current) { |
| 1355 | event.preventDefault(); | 1387 | event.preventDefault(); |
| 1356 | event.stopPropagation(); | 1388 | event.stopPropagation(); |