@@ -3,6 +3,7 @@ import { parseScopedThreadKey, scopedThreadKey } from "@t3tools/client-runtime";
33import { useParams } from "@tanstack/react-router" ;
44import type { ScopedThreadRef } from "@t3tools/contracts" ;
55import type { SidebarProjectSortOrder , SidebarThreadSortOrder } from "@t3tools/contracts/settings" ;
6+ import { useShallow } from "zustand/react/shallow" ;
67import { isTerminalFocused } from "../../lib/terminalFocus" ;
78import { resolveThreadRouteRef } from "../../threadRoutes" ;
89import {
@@ -17,15 +18,14 @@ import { selectThreadTerminalState, useTerminalStateStore } from "../../terminal
1718import { useUiStateStore } from "../../uiStateStore" ;
1819import {
1920 resolveAdjacentThreadId ,
20- sortProjectsForSidebar ,
21+ shouldClearThreadSelectionOnMouseDown ,
2122 useThreadJumpHintVisibility ,
2223} from "../Sidebar.logic" ;
2324import {
2425 createSidebarActiveRouteProjectKeySelectorByRef ,
25- createSidebarProjectOrderingThreadSnapshotsSelector ,
26- createSidebarSortedThreadKeysByLogicalProjectSelector ,
26+ createSidebarSortedProjectKeysSelector ,
27+ createSidebarVisibleThreadKeysSelector ,
2728} from "./sidebarSelectors" ;
28- import { THREAD_PREVIEW_LIMIT } from "./sidebarConstants" ;
2929import type { LogicalProjectKey } from "../../logicalProject" ;
3030import {
3131 setSidebarKeyboardState ,
@@ -36,6 +36,7 @@ import {
3636import type { SidebarProjectSnapshot } from "./sidebarProjectSnapshots" ;
3737import { useServerKeybindings } from "../../rpc/serverState" ;
3838import { useStore } from "../../store" ;
39+ import { useThreadSelectionStore } from "../../threadSelectionStore" ;
3940
4041const EMPTY_THREAD_JUMP_LABELS = new Map < string , string > ( ) ;
4142
@@ -70,56 +71,53 @@ function buildThreadJumpLabelMap(input: {
7071}
7172
7273function useSidebarKeyboardController ( input : {
74+ physicalToLogicalKey : ReadonlyMap < string , LogicalProjectKey > ;
7375 sortedProjectKeys : readonly LogicalProjectKey [ ] ;
74- sortedThreadKeysByLogicalProject : ReadonlyMap < LogicalProjectKey , readonly string [ ] > ;
7576 expandedThreadListsByProject : ReadonlySet < LogicalProjectKey > ;
7677 routeThreadRef : ScopedThreadRef | null ;
7778 routeThreadKey : string | null ;
7879 platform : string ;
7980 keybindings : ReturnType < typeof useServerKeybindings > ;
8081 navigateToThread : ( threadRef : ScopedThreadRef ) => void ;
82+ threadSortOrder : SidebarThreadSortOrder ;
8183} ) {
8284 const {
85+ physicalToLogicalKey,
8386 sortedProjectKeys,
84- sortedThreadKeysByLogicalProject,
8587 expandedThreadListsByProject,
8688 routeThreadRef,
8789 routeThreadKey,
8890 platform,
8991 keybindings,
9092 navigateToThread,
93+ threadSortOrder,
9194 } = input ;
92- const projectExpandedById = useUiStateStore ( ( store ) => store . projectExpandedById ) ;
95+ const projectExpandedStates = useUiStateStore (
96+ useShallow ( ( store ) =>
97+ sortedProjectKeys . map ( ( projectKey ) => store . projectExpandedById [ projectKey ] ?? true ) ,
98+ ) ,
99+ ) ;
93100 const { showThreadJumpHints, updateThreadJumpHintsVisibility } = useThreadJumpHintVisibility ( ) ;
94- const visibleSidebarThreadKeys = useMemo (
95- ( ) =>
96- sortedProjectKeys . flatMap ( ( projectKey ) => {
97- const projectThreadKeys = sortedThreadKeysByLogicalProject . get ( projectKey ) ?? [ ] ;
98- const projectExpanded = projectExpandedById [ projectKey ] ?? true ;
99- const activeThreadKey = routeThreadKey ?? undefined ;
100- const pinnedCollapsedThread =
101- ! projectExpanded && activeThreadKey
102- ? ( projectThreadKeys . find ( ( threadKey ) => threadKey === activeThreadKey ) ?? null )
103- : null ;
104- const shouldShowThreadPanel = projectExpanded || pinnedCollapsedThread !== null ;
105- if ( ! shouldShowThreadPanel ) {
106- return [ ] ;
107- }
108- const isThreadListExpanded = expandedThreadListsByProject . has ( projectKey ) ;
109- const hasOverflowingThreads = projectThreadKeys . length > THREAD_PREVIEW_LIMIT ;
110- const previewThreads =
111- isThreadListExpanded || ! hasOverflowingThreads
112- ? projectThreadKeys
113- : projectThreadKeys . slice ( 0 , THREAD_PREVIEW_LIMIT ) ;
114- return pinnedCollapsedThread ? [ pinnedCollapsedThread ] : previewThreads ;
115- } ) ,
116- [
117- expandedThreadListsByProject ,
118- projectExpandedById ,
119- routeThreadKey ,
120- sortedProjectKeys ,
121- sortedThreadKeysByLogicalProject ,
122- ] ,
101+ const visibleSidebarThreadKeys = useStore (
102+ useMemo (
103+ ( ) =>
104+ createSidebarVisibleThreadKeysSelector ( {
105+ expandedThreadListsByProject,
106+ physicalToLogicalKey,
107+ projectExpandedStates,
108+ routeThreadKey,
109+ sortedProjectKeys,
110+ threadSortOrder,
111+ } ) ,
112+ [
113+ expandedThreadListsByProject ,
114+ physicalToLogicalKey ,
115+ projectExpandedStates ,
116+ routeThreadKey ,
117+ sortedProjectKeys ,
118+ threadSortOrder ,
119+ ] ,
120+ ) ,
123121 ) ;
124122 const threadJumpCommandByKey = useMemo ( ( ) => {
125123 const mapping = new Map < string , NonNullable < ReturnType < typeof threadJumpCommandForIndex > > > ( ) ;
@@ -333,36 +331,53 @@ function useSidebarKeyboardController(input: {
333331 return threadJumpLabelByKey ;
334332}
335333
334+ export const SidebarSelectionController = memo ( function SidebarSelectionController ( ) {
335+ const selectedThreadCount = useThreadSelectionStore ( ( state ) => state . selectedThreadKeys . size ) ;
336+ const clearSelection = useThreadSelectionStore ( ( state ) => state . clearSelection ) ;
337+ const selectedThreadCountRef = useRef ( selectedThreadCount ) ;
338+ selectedThreadCountRef . current = selectedThreadCount ;
339+ const clearSelectionRef = useRef ( clearSelection ) ;
340+ clearSelectionRef . current = clearSelection ;
341+
342+ useEffect ( ( ) => {
343+ const onMouseDown = ( event : globalThis . MouseEvent ) => {
344+ if ( selectedThreadCountRef . current === 0 ) {
345+ return ;
346+ }
347+ const target = event . target instanceof HTMLElement ? event . target : null ;
348+ if ( ! shouldClearThreadSelectionOnMouseDown ( target ) ) {
349+ return ;
350+ }
351+ clearSelectionRef . current ( ) ;
352+ } ;
353+
354+ window . addEventListener ( "mousedown" , onMouseDown ) ;
355+ return ( ) => {
356+ window . removeEventListener ( "mousedown" , onMouseDown ) ;
357+ } ;
358+ } , [ ] ) ;
359+
360+ return null ;
361+ } ) ;
362+
336363export const SidebarProjectOrderingController = memo (
337364 function SidebarProjectOrderingController ( props : {
338365 sidebarProjects : readonly SidebarProjectSnapshot [ ] ;
339366 physicalToLogicalKey : ReadonlyMap < string , LogicalProjectKey > ;
340367 sidebarProjectSortOrder : SidebarProjectSortOrder ;
341368 } ) {
342369 const { sidebarProjects, physicalToLogicalKey, sidebarProjectSortOrder } = props ;
343- const orderingThreads = useStore (
370+ const sortedProjectKeys = useStore (
344371 useMemo (
345372 ( ) =>
346- createSidebarProjectOrderingThreadSnapshotsSelector ( {
373+ createSidebarSortedProjectKeysSelector ( {
347374 physicalToLogicalKey,
375+ projects : sidebarProjects ,
348376 sortOrder : sidebarProjectSortOrder ,
349377 } ) ,
350- [ physicalToLogicalKey , sidebarProjectSortOrder ] ,
378+ [ physicalToLogicalKey , sidebarProjectSortOrder , sidebarProjects ] ,
351379 ) ,
352380 ) ;
353- const sortedProjectKeys = useMemo ( ( ) => {
354- if ( sidebarProjectSortOrder === "manual" ) {
355- return sidebarProjects . map ( ( project ) => project . projectKey ) ;
356- }
357-
358- const sortableProjects = sidebarProjects . map ( ( project ) => ( {
359- ...project ,
360- id : project . projectKey ,
361- } ) ) ;
362- return sortProjectsForSidebar ( sortableProjects , orderingThreads , sidebarProjectSortOrder ) . map (
363- ( project ) => project . id ,
364- ) ;
365- } , [ orderingThreads , sidebarProjectSortOrder , sidebarProjects ] ) ;
366381
367382 useEffect ( ( ) => {
368383 setSidebarProjectOrdering ( sortedProjectKeys ) ;
@@ -380,16 +395,6 @@ export const SidebarKeyboardController = memo(function SidebarKeyboardController
380395 const { navigateToThread, physicalToLogicalKey, sidebarThreadSortOrder } = props ;
381396 const sortedProjectKeys = useSidebarProjectKeys ( ) ;
382397 const expandedThreadListsByProject = useSidebarExpandedThreadListsByProject ( ) ;
383- const sortedThreadKeysByLogicalProject = useStore (
384- useMemo (
385- ( ) =>
386- createSidebarSortedThreadKeysByLogicalProjectSelector ( {
387- physicalToLogicalKey,
388- threadSortOrder : sidebarThreadSortOrder ,
389- } ) ,
390- [ physicalToLogicalKey , sidebarThreadSortOrder ] ,
391- ) ,
392- ) ;
393398 const routeThreadRef = useParams ( {
394399 strict : false ,
395400 select : ( params ) => resolveThreadRouteRef ( params ) ,
@@ -404,14 +409,15 @@ export const SidebarKeyboardController = memo(function SidebarKeyboardController
404409 const keybindings = useServerKeybindings ( ) ;
405410 const platform = navigator . platform ;
406411 const threadJumpLabelByKey = useSidebarKeyboardController ( {
412+ physicalToLogicalKey,
407413 sortedProjectKeys,
408- sortedThreadKeysByLogicalProject,
409414 expandedThreadListsByProject,
410415 routeThreadRef,
411416 routeThreadKey,
412417 platform,
413418 keybindings,
414419 navigateToThread,
420+ threadSortOrder : sidebarThreadSortOrder ,
415421 } ) ;
416422
417423 useEffect ( ( ) => {
0 commit comments