diff options
Diffstat (limited to 'src/state/queries/preferences/useThreadPreferences.ts')
-rw-r--r-- | src/state/queries/preferences/useThreadPreferences.ts | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/src/state/queries/preferences/useThreadPreferences.ts b/src/state/queries/preferences/useThreadPreferences.ts new file mode 100644 index 000000000..dc3122a72 --- /dev/null +++ b/src/state/queries/preferences/useThreadPreferences.ts @@ -0,0 +1,179 @@ +import {useCallback, useMemo, useRef, useState} from 'react' +import {type AppBskyUnspeccedGetPostThreadV2} from '@atproto/api' +import debounce from 'lodash.debounce' + +import {OnceKey, useCallOnce} from '#/lib/hooks/useCallOnce' +import {logger} from '#/logger' +import { + usePreferencesQuery, + useSetThreadViewPreferencesMutation, +} from '#/state/queries/preferences' +import {type ThreadViewPreferences} from '#/state/queries/preferences/types' +import {type Literal} from '#/types/utils' + +export type ThreadSortOption = Literal< + AppBskyUnspeccedGetPostThreadV2.QueryParams['sort'], + string +> +export type ThreadViewOption = 'linear' | 'tree' +export type ThreadPreferences = { + isLoaded: boolean + isSaving: boolean + sort: ThreadSortOption + setSort: (sort: string) => void + view: ThreadViewOption + setView: (view: ThreadViewOption) => void + prioritizeFollowedUsers: boolean + setPrioritizeFollowedUsers: (prioritize: boolean) => void +} + +export function useThreadPreferences({ + save, +}: {save?: boolean} = {}): ThreadPreferences { + const {data: preferences} = usePreferencesQuery() + const serverPrefs = preferences?.threadViewPrefs + const once = useCallOnce(OnceKey.PreferencesThread) + + /* + * Create local state representations of server state + */ + const [sort, setSort] = useState(normalizeSort(serverPrefs?.sort || 'top')) + const [view, setView] = useState( + normalizeView({ + treeViewEnabled: !!serverPrefs?.lab_treeViewEnabled, + }), + ) + const [prioritizeFollowedUsers, setPrioritizeFollowedUsers] = useState( + !!serverPrefs?.prioritizeFollowedUsers, + ) + + /** + * If we get a server update, update local state + */ + const [prevServerPrefs, setPrevServerPrefs] = useState(serverPrefs) + const isLoaded = !!prevServerPrefs + if (serverPrefs && prevServerPrefs !== serverPrefs) { + setPrevServerPrefs(serverPrefs) + + /* + * Update + */ + setSort(normalizeSort(serverPrefs.sort)) + setPrioritizeFollowedUsers(serverPrefs.prioritizeFollowedUsers) + setView( + normalizeView({ + treeViewEnabled: !!serverPrefs.lab_treeViewEnabled, + }), + ) + + once(() => { + logger.metric('thread:preferences:load', { + sort: serverPrefs.sort, + view: serverPrefs.lab_treeViewEnabled ? 'tree' : 'linear', + prioritizeFollowedUsers: serverPrefs.prioritizeFollowedUsers, + }) + }) + } + + const userUpdatedPrefs = useRef(false) + const [isSaving, setIsSaving] = useState(false) + const {mutateAsync} = useSetThreadViewPreferencesMutation() + const savePrefs = useMemo(() => { + return debounce(async (prefs: ThreadViewPreferences) => { + try { + setIsSaving(true) + await mutateAsync(prefs) + logger.metric('thread:preferences:update', { + sort: prefs.sort, + view: prefs.lab_treeViewEnabled ? 'tree' : 'linear', + prioritizeFollowedUsers: prefs.prioritizeFollowedUsers, + }) + } catch (e) { + logger.error('useThreadPreferences failed to save', { + safeMessage: e, + }) + } finally { + setIsSaving(false) + } + }, 4e3) + }, [mutateAsync]) + + if (save && userUpdatedPrefs.current) { + savePrefs({ + sort, + prioritizeFollowedUsers, + lab_treeViewEnabled: view === 'tree', + }) + userUpdatedPrefs.current = false + } + + const setSortWrapped = useCallback( + (next: string) => { + userUpdatedPrefs.current = true + setSort(normalizeSort(next)) + }, + [setSort], + ) + const setViewWrapped = useCallback( + (next: ThreadViewOption) => { + userUpdatedPrefs.current = true + setView(next) + }, + [setView], + ) + const setPrioritizeFollowedUsersWrapped = useCallback( + (next: boolean) => { + userUpdatedPrefs.current = true + setPrioritizeFollowedUsers(next) + }, + [setPrioritizeFollowedUsers], + ) + + return useMemo( + () => ({ + isLoaded, + isSaving, + sort, + setSort: setSortWrapped, + view, + setView: setViewWrapped, + prioritizeFollowedUsers, + setPrioritizeFollowedUsers: setPrioritizeFollowedUsersWrapped, + }), + [ + isLoaded, + isSaving, + sort, + setSortWrapped, + view, + setViewWrapped, + prioritizeFollowedUsers, + setPrioritizeFollowedUsersWrapped, + ], + ) +} + +/** + * Migrates user thread preferences from the old sort values to V2 + */ +export function normalizeSort(sort: string): ThreadSortOption { + switch (sort) { + case 'oldest': + return 'oldest' + case 'newest': + return 'newest' + default: + return 'top' + } +} + +/** + * Transforms existing treeViewEnabled preference into a ThreadViewOption + */ +export function normalizeView({ + treeViewEnabled, +}: { + treeViewEnabled: boolean +}): ThreadViewOption { + return treeViewEnabled ? 'tree' : 'linear' +} |