Skip to content
Source
Sign inSign up
Open History to see the last commit

HomeRouteScreen.tsx

246 lines9.6 KB

1import { uuidv4 } from "../../lib/uuid";
2import { ThreadUndoNotice } from "./ThreadUndoNotice";
3import { use } from "react";
4import { NativePrimaryColumnContext } from "../../native/v5-workspace-context";
5import * as Arr from "effect/Array";
6import * as Order from "effect/Order";
7import { useNavigation } from "@react-navigation/native";
8import { useMemo, useState } from "react";
9import { Platform } from "react-native";
10​
11import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell";
12import { pickableProjectGroups } from "@t3tools/client-runtime/state/project-grouping";
13​
14import { useProjects } from "../../state/entities";
15import { steadyNavigationThreadShellsAtom } from "../../state/threads";
16import { useLaunchingThreads, usePendingNewTasks } from "../../state/use-pending-new-tasks";
17import { useWorkspaceState } from "../../state/workspace";
18import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry";
19import {
20 useAdaptiveWorkspaceLayout,
21 useWorkspacePaneState,
22} from "../layout/AdaptiveWorkspaceLayout";
23import { AndroidHomeFabLayout } from "./AndroidHomeFab";
24import { HomeScreen } from "./HomeScreen";
25import { useHomeListOptions } from "./home-list-options";
26import {
27 runWhenHomeCovered,
28 useAtomValueWhileVisible,
29 useHomeRouteVisible,
30} from "./home-route-visibility";
31import {
32 buildHomeProjectScopes,
33 resolveHomeProjectFilterScope,
34 sortHomeProjectScopes,
35} from "./homeThreadList";
36import { usePendingTaskListActions } from "./usePendingTaskListActions";
37import { useThreadListActions } from "./useThreadListActions";
38import { useWorkspaceTabs } from "../layout/workspace/WorkspaceTabsContext";
39import { openSettings } from "../settings/settings-navigation";
40import { useSelectWorkspaceTab } from "../layout/workspace/useSelectWorkspaceTab";
41import { useSelectWorkspaceDetail } from "../layout/workspace/useSelectWorkspaceDetail";
42import { WorkspaceListEmptyDetail } from "../layout/workspace/WorkspaceListEmptyDetail";
43import { WorkspaceListHeader } from "../layout/workspace/WorkspaceListHeader";
44import { buildHomeListFilterMenuItems } from "./home-list-filter-menu";
45​
46export function HomeRouteScreen() {
47 const nativePrimaryColumn = use(NativePrimaryColumnContext);
48 const { layout, setPrimarySidebarSearchQuery, startNewTask } = useAdaptiveWorkspaceLayout();
49 const { primarySidebarSearchQuery } = useWorkspacePaneState();
50 const selectDetail = useSelectWorkspaceDetail();
51 const projects = useProjects();
52 // Stop rebuilding the streaming thread list while Thread covers Home.
53 const visible = useHomeRouteVisible();
54 const threads = useLaunchingThreads(
55 useAtomValueWhileVisible(steadyNavigationThreadShellsAtom, visible),
56 );
57 const { environments: workspaceEnvironments, state: catalogState } = useWorkspaceState();
58 const { savedConnectionsById } = useSavedRemoteConnections();
59 const navigation = useNavigation();
60 const selectTab = useSelectWorkspaceTab();
61 const workspace = useWorkspaceTabs();
62 const [localSearchQuery, setLocalSearchQuery] = useState("");
63 const searchQuery =
64 Platform.OS === "android"
65 ? primarySidebarSearchQuery
66 : (workspace?.state.tabs.threads.query ?? localSearchQuery);
67 const setSearchQuery = (query: string) => {
68 if (Platform.OS === "android") setPrimarySidebarSearchQuery(query);
69 else if (workspace) workspace.dispatch({ type: "query", query, tab: "threads" });
70 else setLocalSearchQuery(query);
71 };
72 // Split layouts keep the query; only the stacked layout opens the thread over the list.
73 // Android's search field keeps its own open state, which a cleared query would leave open.
74 const clearsSearchOnOpen = Platform.OS !== "android" && !layout.usesSplitView;
75 const selectThreadRoute = (params: ReactNavigation.RootParamList["Thread"]) => {
76 if (searchQuery !== "" && clearsSearchOnOpen && workspace)
77 runWhenHomeCovered(() => workspace.cancelSearch("threads"));
78 selectDetail({ name: "Thread", params });
79 };
80 const handleSelectThread = (thread: EnvironmentThreadShell) =>
81 selectThreadRoute({ environmentId: String(thread.environmentId), threadId: String(thread.id) });
82 const handleNewThreadOnBranch = (thread: EnvironmentThreadShell) => {
83 selectThreadRoute({
84 draft: {
85 requestId: uuidv4(),
86 environmentId: String(thread.environmentId),
87 projectId: String(thread.projectId),
88 branch: thread.branch,
89 worktreePath: thread.worktreePath,
90 },
91 });
92 };
93​
94 const {
95 archiveThread,
96 confirmDeleteThread,
97 settleThread,
98 snoozeThread,
99 unsnoozeThread,
100 pinThread,
101 unpinThread,
102 setThreadAutoSettle,
103 moveThread,
104 renameThread,
105 regenerateThreadTitle,
106 unsettleThread,
107 } = useThreadListActions();
108 const pendingTasks = usePendingNewTasks();
109 const { openPendingTask, confirmDeletePendingTask } =
110 usePendingTaskListActions(selectThreadRoute);
111 const connectionStateByEnvironmentId = new Map(
112 workspaceEnvironments.map(
113 (environment) => [environment.environmentId, environment.connectionState] as const,
114 ),
115 );
116 const environments = Arr.sort(
117 Object.values(savedConnectionsById).map((connection) => ({
118 environmentId: connection.environmentId,
119 label: connection.environmentLabel,
120 connectionState: connectionStateByEnvironmentId.get(connection.environmentId) ?? "available",
121 })),
122 Order.mapInput(Order.String, (environment: { readonly label: string }) => environment.label),
123 );
124 const availableEnvironmentIds = useMemo(
125 () => new Set(environments.map((environment) => environment.environmentId)),
126 [environments],
127 );
128 const {
129 options: listOptions,
130 setSelectedEnvironmentId,
131 setSelectedProjectKey,
132 } = useHomeListOptions(availableEnvironmentIds);
133 const selectedEnvironmentId = listOptions.selectedEnvironmentId;
134 const projectScopes = useMemo(
135 () =>
136 buildHomeProjectScopes({
137 projects,
138 environmentId: selectedEnvironmentId,
139 projectGroupingMode: listOptions.projectGroupingMode,
140 }),
141 [listOptions.projectGroupingMode, projects, selectedEnvironmentId],
142 );
143 const projectFilterOptions = useMemo(
144 () =>
145 pickableProjectGroups(
146 sortHomeProjectScopes({
147 scopes: projectScopes,
148 threads,
149 pendingTasks: [],
150 projectSortOrder: "updated_at",
151 }),
152 ).map((scope) => ({
153 key: scope.key,
154 label: scope.title,
155 })),
156 [projectScopes, threads],
157 );
158 const selectedProjectKey =
159 resolveHomeProjectFilterScope(projectScopes, projects, listOptions)?.key ?? null;
160​
161 if (layout.usesSplitView && !nativePrimaryColumn) {
162 return (
163 <WorkspaceListEmptyDetail
164 hasConnections={catalogState.hasConnections}
165 onAddConnection={() => openSettings(navigation, "SettingsEnvironmentNew")}
166 onStartNewTask={startNewTask}
167 />
168 );
169 }
170​
171 return (
172 <AndroidHomeFabLayout onStartNewTask={startNewTask}>
173 <>
174 <WorkspaceListHeader
175 accessibilityLabel="T3 Code, Threads"
176 onOpenEnvironments={() => openSettings(navigation, "SettingsEnvironments")}
177 search={{
178 value: searchQuery,
179 onChangeText: setSearchQuery,
180 placeholder: "Search",
181 accessibilityLabel: "Search threads",
182 }}
183 filter={{
184 customized: selectedEnvironmentId !== null || selectedProjectKey !== null,
185 items: buildHomeListFilterMenuItems({
186 environments,
187 projects: projectFilterOptions,
188 selectedEnvironmentId,
189 selectedProjectKey,
190 onEnvironmentChange: setSelectedEnvironmentId,
191 onProjectChange: setSelectedProjectKey,
192 }),
193 }}
194 />
195​
196 <HomeScreen
197 catalogState={catalogState}
198 environments={environments}
199 onAddConnection={() => openSettings(navigation, "SettingsEnvironmentNew")}
200 onArchiveThread={archiveThread}
201 onDeleteThread={confirmDeleteThread}
202 onSettleThread={settleThread}
203 onSnoozeThread={snoozeThread}
204 onUnsnoozeThread={unsnoozeThread}
205 onUnsettleThread={unsettleThread}
206 onPinThread={pinThread}
207 onUnpinThread={unpinThread}
208 onSetThreadAutoSettle={setThreadAutoSettle}
209 onMoveThread={moveThread}
210 onRenameThread={renameThread}
211 onRegenerateThreadTitle={regenerateThreadTitle}
212 onEnvironmentChange={setSelectedEnvironmentId}
213 onProjectChange={setSelectedProjectKey}
214 onOpenSettings={() => selectTab("settings")}
215 onSearchQueryChange={setSearchQuery}
216 onSelectThread={handleSelectThread}
217 onSelectPendingTask={openPendingTask}
218 onDeletePendingTask={confirmDeletePendingTask}
219 onNewThreadOnBranch={handleNewThreadOnBranch}
220 onNewThreadInProject={(project) => {
221 selectThreadRoute({
222 draft: {
223 requestId: uuidv4(),
224 environmentId: String(project.environmentId),
225 projectId: String(project.id),
226 title: project.title,
227 },
228 });
229 }}
230 onStartNewTask={startNewTask}
231 pendingTasks={pendingTasks}
232 projectGroupingMode={listOptions.projectGroupingMode}
233 projects={projects}
234 projectSortOrder={listOptions.projectSortOrder}
235 savedConnectionsById={savedConnectionsById}
236 searchQuery={searchQuery}
237 selectedEnvironmentId={selectedEnvironmentId}
238 selectedProjectKey={selectedProjectKey}
239 threads={threads}
240 visible={visible}
241 />
242 <ThreadUndoNotice visible={visible} />
243 </>
244 </AndroidHomeFabLayout>
245 );