1import type { EnvironmentId, SidebarProjectGroupingMode } from "@t3tools/contracts";
2import { DEFAULT_SIDEBAR_PROJECT_SORT_ORDER } from "@t3tools/contracts";
3import { useAtomSet } from "@effect/atom-react";
4import {
5 createContext,
6 createElement,
7 useCallback,
8 useContext,
9 useEffect,
10 useMemo,
11 useRef,
12 useState,
13 type PropsWithChildren,
14 type Dispatch,
15 type SetStateAction,
16} from "react";
17
18import type { ThreadListFilters } from "../../persistence/mobile-preferences";
19import { updateMobilePreferencesAtom } from "../../state/preferences";
20import type { HomeProjectSortOrder } from "./homeThreadList";
21
22export interface HomeListOptions {
23 readonly selectedEnvironmentId: EnvironmentId | null;
24 readonly selectedProjectKey: string | null;
25 readonly projectSortOrder: HomeProjectSortOrder;
26}
27
28export interface ResolvedHomeListOptions extends HomeListOptions {
29 readonly projectGroupingMode: SidebarProjectGroupingMode;
30}
31
32function defaultHomeListOptions(filters?: ThreadListFilters): HomeListOptions {
33 return {
34 selectedEnvironmentId: filters?.environmentId ?? null,
35 selectedProjectKey: filters?.projectKey ?? null,
36 projectSortOrder:
37 DEFAULT_SIDEBAR_PROJECT_SORT_ORDER === "manual"
38 ? "updated_at"
39 : DEFAULT_SIDEBAR_PROJECT_SORT_ORDER,
40 };
41}
42
43interface HomeListOptionsContextValue {
44 readonly options: HomeListOptions;
45 readonly setOptions: Dispatch<SetStateAction<HomeListOptions>>;
46 readonly projectGroupingMode: SidebarProjectGroupingMode;
47}
48
49const HomeListOptionsContext = createContext<HomeListOptionsContextValue | null>(null);
50
51/**
52 * Keeps list preferences stable while the app moves between compact and split shells.
53 * Filters start from the saved preference, and changes are saved for the next launch.
54 */
55export function HomeListOptionsProvider({
56 children,
57 projectGroupingMode,
58 savedFilters,
59}: PropsWithChildren<{
60 readonly projectGroupingMode: SidebarProjectGroupingMode;
61 readonly savedFilters?: ThreadListFilters | undefined;
62}>) {
63 const [options, setOptions] = useState(() => defaultHomeListOptions(savedFilters));
64 const savePreferences = useAtomSet(updateMobilePreferencesAtom);
65 const saved = useRef(savedFilters);
66 const { selectedEnvironmentId: environmentId, selectedProjectKey: projectKey } = options;
67 useEffect(() => {
68 if (
69 (saved.current?.environmentId ?? null) === environmentId &&
70 (saved.current?.projectKey ?? null) === projectKey
71 )
72 return;
73 saved.current = { environmentId, projectKey };
74 savePreferences({ threadListFilters: saved.current });
75 }, [environmentId, projectKey, savePreferences]);
76 const value = useMemo(
77 () => ({ options, setOptions, projectGroupingMode }),
78 [options, projectGroupingMode],
79 );
80 return createElement(HomeListOptionsContext, { value }, children);
81}
82
83export function useHomeListOptions(availableEnvironmentIds: ReadonlySet<EnvironmentId>) {
84 const shared = useContext(HomeListOptionsContext);
85 const [localOptions, setLocalOptions] = useState<HomeListOptions>(defaultHomeListOptions);
86 const options = shared?.options ?? localOptions;
87 const setOptions = shared?.setOptions ?? setLocalOptions;
88 const selectedEnvironmentId =
89 options.selectedEnvironmentId !== null &&
90 availableEnvironmentIds.has(options.selectedEnvironmentId)
91 ? options.selectedEnvironmentId
92 : null;
93 const availableOptions =
94 selectedEnvironmentId === options.selectedEnvironmentId
95 ? options
96 : { ...options, selectedEnvironmentId };
97 const resolvedOptions: ResolvedHomeListOptions = {
98 ...availableOptions,
99 projectGroupingMode: shared?.projectGroupingMode ?? "repository",
100 };
101
102 const setSelectedEnvironmentId = useCallback((value: EnvironmentId | null) => {
103 setOptions((current) => ({ ...current, selectedEnvironmentId: value }));
104 }, []);
105 const setSelectedProjectKey = useCallback(
106 (value: string | null) => {
107 setOptions((current) => ({ ...current, selectedProjectKey: value }));
108 },
109 [setOptions],
110 );
111 const setProjectSortOrder = useCallback((value: HomeProjectSortOrder) => {
112 setOptions((current) => ({ ...current, projectSortOrder: value }));
113 }, []);
114 return {
115 options: resolvedOptions,
116 setSelectedEnvironmentId,
117 setSelectedProjectKey,
118 setProjectSortOrder,
119 } as const;
120}