| ... | ... | @@ -61,6 +61,7 @@ import { |
| 61 | 61 | } from "./desktopUpdate.logic"; |
| 62 | 62 | import { Alert, AlertAction, AlertDescription, AlertTitle } from "./ui/alert"; |
| 63 | 63 | import { Button } from "./ui/button"; |
| 64 | import { Collapsible, CollapsibleContent } from "./ui/collapsible"; |
| 64 | 65 | import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; |
| 65 | 66 | import { |
| 66 | 67 | SidebarContent, |
| ... | ... | @@ -231,18 +232,20 @@ function SortableProjectItem({ |
| 231 | 232 | const { attributes, listeners, setNodeRef, transform, transition, isDragging, isOver } = |
| 232 | 233 | useSortable({ id: projectId }); |
| 233 | 234 | return ( |
| 234 | | <div |
| 235 | <li |
| 235 | 236 | ref={setNodeRef} |
| 236 | 237 | style={{ |
| 237 | 238 | transform: CSS.Translate.toString(transform), |
| 238 | 239 | transition, |
| 239 | 240 | }} |
| 240 | | className={`rounded-md ${ |
| 241 | className={`group/menu-item relative rounded-md ${ |
| 241 | 242 | isDragging ? "z-20 opacity-80" : "" |
| 242 | 243 | } ${isOver && !isDragging ? "ring-1 ring-primary/40" : ""}`} |
| 244 | data-sidebar="menu-item" |
| 245 | data-slot="sidebar-menu-item" |
| 243 | 246 | > |
| 244 | 247 | {children({ attributes, listeners })} |
| 245 | | </div> |
| 248 | </li> |
| 246 | 249 | ); |
| 247 | 250 | } |
| 248 | 251 | |
| ... | ... | @@ -294,14 +297,7 @@ export default function Sidebar() { |
| 294 | 297 | const renamingCommittedRef = useRef(false); |
| 295 | 298 | const renamingInputRef = useRef<HTMLInputElement | null>(null); |
| 296 | 299 | const dragInProgressRef = useRef(false); |
| 297 | | const suppressProjectClickFromGestureRef = useRef(false); |
| 298 | | const suppressProjectClickResetTimerRef = useRef<number | null>(null); |
| 299 | | const projectTitlePointerRef = useRef<{ |
| 300 | | pointerId: number; |
| 301 | | startX: number; |
| 302 | | startY: number; |
| 303 | | moved: boolean; |
| 304 | | } | null>(null); |
| 300 | const suppressProjectClickAfterDragRef = useRef(false); |
| 305 | 301 | const [desktopUpdateState, setDesktopUpdateState] = useState<DesktopUpdateState | null>(null); |
| 306 | 302 | const shouldBrowseForProjectImmediately = isElectron; |
| 307 | 303 | const shouldShowProjectPathEntry = addingProject && !shouldBrowseForProjectImmediately; |
| ... | ... | @@ -861,64 +857,20 @@ export default function Sidebar() { |
| 861 | 857 | |
| 862 | 858 | const handleProjectDragStart = useCallback((_event: DragStartEvent) => { |
| 863 | 859 | dragInProgressRef.current = true; |
| 860 | suppressProjectClickAfterDragRef.current = true; |
| 864 | 861 | }, []); |
| 865 | 862 | |
| 866 | 863 | const handleProjectDragCancel = useCallback((_event: DragCancelEvent) => { |
| 867 | 864 | dragInProgressRef.current = false; |
| 868 | 865 | }, []); |
| 869 | 866 | |
| 870 | | const handleProjectTitlePointerDownCapture = useCallback( |
| 871 | | (event: React.PointerEvent<HTMLButtonElement>) => { |
| 872 | | projectTitlePointerRef.current = { |
| 873 | | pointerId: event.pointerId, |
| 874 | | startX: event.clientX, |
| 875 | | startY: event.clientY, |
| 876 | | moved: false, |
| 877 | | }; |
| 878 | | }, |
| 879 | | [], |
| 880 | | ); |
| 881 | | |
| 882 | | const handleProjectTitlePointerMoveCapture = useCallback( |
| 883 | | (event: React.PointerEvent<HTMLButtonElement>) => { |
| 884 | | const pointer = projectTitlePointerRef.current; |
| 885 | | if (!pointer || pointer.pointerId !== event.pointerId) return; |
| 886 | | const movedX = Math.abs(event.clientX - pointer.startX); |
| 887 | | const movedY = Math.abs(event.clientY - pointer.startY); |
| 888 | | if (movedX > 3 || movedY > 3) { |
| 889 | | pointer.moved = true; |
| 890 | | } |
| 891 | | }, |
| 892 | | [], |
| 893 | | ); |
| 894 | | |
| 895 | | const handleProjectTitlePointerUpCapture = useCallback( |
| 896 | | (event: React.PointerEvent<HTMLButtonElement>) => { |
| 897 | | const pointer = projectTitlePointerRef.current; |
| 898 | | if (pointer?.pointerId === event.pointerId) { |
| 899 | | if (pointer.moved) { |
| 900 | | suppressProjectClickFromGestureRef.current = true; |
| 901 | | if (suppressProjectClickResetTimerRef.current !== null) { |
| 902 | | window.clearTimeout(suppressProjectClickResetTimerRef.current); |
| 903 | | } |
| 904 | | suppressProjectClickResetTimerRef.current = window.setTimeout(() => { |
| 905 | | suppressProjectClickFromGestureRef.current = false; |
| 906 | | suppressProjectClickResetTimerRef.current = null; |
| 907 | | }, 0); |
| 908 | | } |
| 909 | | projectTitlePointerRef.current = null; |
| 910 | | } |
| 911 | | }, |
| 912 | | [], |
| 913 | | ); |
| 914 | | |
| 915 | | const handleProjectTitlePointerCancelCapture = useCallback(() => { |
| 916 | | projectTitlePointerRef.current = null; |
| 867 | const handleProjectTitlePointerDownCapture = useCallback(() => { |
| 868 | suppressProjectClickAfterDragRef.current = false; |
| 917 | 869 | }, []); |
| 918 | 870 | |
| 919 | 871 | const handleProjectTitleClick = useCallback( |
| 920 | 872 | (event: React.MouseEvent<HTMLButtonElement>, projectId: ProjectId) => { |
| 921 | | if (dragInProgressRef.current || suppressProjectClickFromGestureRef.current) { |
| 873 | if (dragInProgressRef.current || suppressProjectClickAfterDragRef.current) { |
| 922 | 874 | event.preventDefault(); |
| 923 | 875 | event.stopPropagation(); |
| 924 | 876 | return; |
| ... | ... | @@ -940,14 +892,6 @@ export default function Sidebar() { |
| 940 | 892 | [toggleProject], |
| 941 | 893 | ); |
| 942 | 894 | |
| 943 | | useEffect(() => { |
| 944 | | return () => { |
| 945 | | if (suppressProjectClickResetTimerRef.current !== null) { |
| 946 | | window.clearTimeout(suppressProjectClickResetTimerRef.current); |
| 947 | | } |
| 948 | | }; |
| 949 | | }, []); |
| 950 | | |
| 951 | 895 | useEffect(() => { |
| 952 | 896 | const onWindowKeyDown = (event: KeyboardEvent) => { |
| 953 | 897 | const activeThread = routeThreadId |
| ... | ... | @@ -1318,7 +1262,10 @@ export default function Sidebar() { |
| 1318 | 1262 | return ( |
| 1319 | 1263 | <SortableProjectItem key={project.id} projectId={project.id}> |
| 1320 | 1264 | {(dragHandleProps) => ( |
| 1321 | | <SidebarMenuItem className="group/collapsible"> |
| 1265 | <Collapsible |
| 1266 | className="group/collapsible" |
| 1267 | open={project.expanded} |
| 1268 | > |
| 1322 | 1269 | <div className="group/project-header relative"> |
| 1323 | 1270 | <SidebarMenuButton |
| 1324 | 1271 | size="sm" |
| ... | ... | @@ -1326,9 +1273,6 @@ export default function Sidebar() { |
| 1326 | 1273 | {...dragHandleProps.attributes} |
| 1327 | 1274 | {...dragHandleProps.listeners} |
| 1328 | 1275 | onPointerDownCapture={handleProjectTitlePointerDownCapture} |
| 1329 | | onPointerMoveCapture={handleProjectTitlePointerMoveCapture} |
| 1330 | | onPointerUpCapture={handleProjectTitlePointerUpCapture} |
| 1331 | | onPointerCancelCapture={handleProjectTitlePointerCancelCapture} |
| 1332 | 1276 | onClick={(event) => handleProjectTitleClick(event, project.id)} |
| 1333 | 1277 | onKeyDown={(event) => handleProjectTitleKeyDown(event, project.id)} |
| 1334 | 1278 | onContextMenu={(event) => { |
| ... | ... | @@ -1379,15 +1323,8 @@ export default function Sidebar() { |
| 1379 | 1323 | </Tooltip> |
| 1380 | 1324 | </div> |
| 1381 | 1325 | |
| 1382 | | <div |
| 1383 | | className={`grid transition-[grid-template-rows,opacity] duration-200 ease-out ${ |
| 1384 | | project.expanded |
| 1385 | | ? "grid-rows-[1fr] opacity-100" |
| 1386 | | : "pointer-events-none grid-rows-[0fr] opacity-0" |
| 1387 | | }`} |
| 1388 | | > |
| 1389 | | <div className="min-h-0 overflow-hidden"> |
| 1390 | | <SidebarMenuSub className="mx-1 my-0 w-full translate-x-0 gap-0 px-1.5 py-0"> |
| 1326 | <CollapsibleContent keepMounted> |
| 1327 | <SidebarMenuSub className="mx-1 my-0 w-full translate-x-0 gap-0 px-1.5 py-0"> |
| 1391 | 1328 | {visibleThreads.map((thread) => { |
| 1392 | 1329 | const isActive = routeThreadId === thread.id; |
| 1393 | 1330 | const threadStatus = resolveThreadStatusPill({ |
| ... | ... | @@ -1558,9 +1495,8 @@ export default function Sidebar() { |
| 1558 | 1495 | </SidebarMenuSubItem> |
| 1559 | 1496 | )} |
| 1560 | 1497 | </SidebarMenuSub> |
| 1561 | | </div> |
| 1562 | | </div> |
| 1563 | | </SidebarMenuItem> |
| 1498 | </CollapsibleContent> |
| 1499 | </Collapsible> |
| 1564 | 1500 | )} |
| 1565 | 1501 | </SortableProjectItem> |
| 1566 | 1502 | ); |