| 1 | import type { EnvironmentId, SidebarProjectGroupingMode } from "@t3tools/contracts"; |
| 2 | import { DEFAULT_SIDEBAR_PROJECT_SORT_ORDER } from "@t3tools/contracts"; |
| 3 | import { useAtomSet } from "@effect/atom-react"; |
| 4 | import { |
| 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 | |
| 18 | import type { ThreadListFilters } from "../../persistence/mobile-preferences"; |
| 19 | import { updateMobilePreferencesAtom } from "../../state/preferences"; |
| 20 | import type { HomeProjectSortOrder } from "./homeThreadList"; |
| 21 | |
| 22 | export interface HomeListOptions { |
| 23 | readonly selectedEnvironmentId: EnvironmentId | null; |
| 24 | readonly selectedProjectKey: string | null; |
| 25 | readonly projectSortOrder: HomeProjectSortOrder; |
| 26 | } |
| 27 | |
| 28 | export interface ResolvedHomeListOptions extends HomeListOptions { |
| 29 | readonly projectGroupingMode: SidebarProjectGroupingMode; |
| 30 | } |
| 31 | |
| 32 | function 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 | |
| 43 | interface HomeListOptionsContextValue { |
| 44 | readonly options: HomeListOptions; |
| 45 | readonly setOptions: Dispatch<SetStateAction<HomeListOptions>>; |
| 46 | readonly projectGroupingMode: SidebarProjectGroupingMode; |
| 47 | } |
| 48 | |
| 49 | const 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 | */ |
| 55 | export 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 | |
| 83 | export 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 | } |