diff options
Diffstat (limited to 'src/state/preferences')
-rw-r--r-- | src/state/preferences/alt-text-required.tsx | 48 | ||||
-rw-r--r-- | src/state/preferences/feed-tuners.tsx | 52 | ||||
-rw-r--r-- | src/state/preferences/index.tsx | 17 | ||||
-rw-r--r-- | src/state/preferences/languages.tsx | 142 |
4 files changed, 259 insertions, 0 deletions
diff --git a/src/state/preferences/alt-text-required.tsx b/src/state/preferences/alt-text-required.tsx new file mode 100644 index 000000000..81de9e006 --- /dev/null +++ b/src/state/preferences/alt-text-required.tsx @@ -0,0 +1,48 @@ +import React from 'react' +import * as persisted from '#/state/persisted' + +type StateContext = persisted.Schema['requireAltTextEnabled'] +type SetContext = (v: persisted.Schema['requireAltTextEnabled']) => void + +const stateContext = React.createContext<StateContext>( + persisted.defaults.requireAltTextEnabled, +) +const setContext = React.createContext<SetContext>( + (_: persisted.Schema['requireAltTextEnabled']) => {}, +) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [state, setState] = React.useState( + persisted.get('requireAltTextEnabled'), + ) + + const setStateWrapped = React.useCallback( + (requireAltTextEnabled: persisted.Schema['requireAltTextEnabled']) => { + setState(requireAltTextEnabled) + persisted.write('requireAltTextEnabled', requireAltTextEnabled) + }, + [setState], + ) + + React.useEffect(() => { + return persisted.onUpdate(() => { + setState(persisted.get('requireAltTextEnabled')) + }) + }, [setStateWrapped]) + + return ( + <stateContext.Provider value={state}> + <setContext.Provider value={setStateWrapped}> + {children} + </setContext.Provider> + </stateContext.Provider> + ) +} + +export function useRequireAltTextEnabled() { + return React.useContext(stateContext) +} + +export function useSetRequireAltTextEnabled() { + return React.useContext(setContext) +} diff --git a/src/state/preferences/feed-tuners.tsx b/src/state/preferences/feed-tuners.tsx new file mode 100644 index 000000000..c4954d20a --- /dev/null +++ b/src/state/preferences/feed-tuners.tsx @@ -0,0 +1,52 @@ +import {useMemo} from 'react' +import {FeedTuner} from '#/lib/api/feed-manip' +import {FeedDescriptor} from '../queries/post-feed' +import {useLanguagePrefs} from './languages' +import {usePreferencesQuery} from '../queries/preferences' +import {useSession} from '../session' + +export function useFeedTuners(feedDesc: FeedDescriptor) { + const langPrefs = useLanguagePrefs() + const {data: preferences} = usePreferencesQuery() + const {currentAccount} = useSession() + + return useMemo(() => { + if (feedDesc.startsWith('feedgen')) { + return [ + FeedTuner.dedupReposts, + FeedTuner.preferredLangOnly(langPrefs.contentLanguages), + ] + } + if (feedDesc.startsWith('list')) { + return [FeedTuner.dedupReposts] + } + if (feedDesc === 'home' || feedDesc === 'following') { + const feedTuners = [] + + if (preferences?.feedViewPrefs.hideReposts) { + feedTuners.push(FeedTuner.removeReposts) + } else { + feedTuners.push(FeedTuner.dedupReposts) + } + + if (preferences?.feedViewPrefs.hideReplies) { + feedTuners.push(FeedTuner.removeReplies) + } else { + feedTuners.push( + FeedTuner.thresholdRepliesOnly({ + userDid: currentAccount?.did || '', + minLikes: preferences?.feedViewPrefs.hideRepliesByLikeCount || 0, + followedOnly: !!preferences?.feedViewPrefs.hideRepliesByUnfollowed, + }), + ) + } + + if (preferences?.feedViewPrefs.hideQuotePosts) { + feedTuners.push(FeedTuner.removeQuotePosts) + } + + return feedTuners + } + return [] + }, [feedDesc, currentAccount, preferences, langPrefs]) +} diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx new file mode 100644 index 000000000..1f4348cfc --- /dev/null +++ b/src/state/preferences/index.tsx @@ -0,0 +1,17 @@ +import React from 'react' +import {Provider as LanguagesProvider} from './languages' +import {Provider as AltTextRequiredProvider} from '../preferences/alt-text-required' + +export {useLanguagePrefs, useLanguagePrefsApi} from './languages' +export { + useRequireAltTextEnabled, + useSetRequireAltTextEnabled, +} from './alt-text-required' + +export function Provider({children}: React.PropsWithChildren<{}>) { + return ( + <LanguagesProvider> + <AltTextRequiredProvider>{children}</AltTextRequiredProvider> + </LanguagesProvider> + ) +} diff --git a/src/state/preferences/languages.tsx b/src/state/preferences/languages.tsx new file mode 100644 index 000000000..8e779cfe5 --- /dev/null +++ b/src/state/preferences/languages.tsx @@ -0,0 +1,142 @@ +import React from 'react' +import * as persisted from '#/state/persisted' + +type SetStateCb = ( + s: persisted.Schema['languagePrefs'], +) => persisted.Schema['languagePrefs'] +type StateContext = persisted.Schema['languagePrefs'] +type ApiContext = { + setPrimaryLanguage: (code2: string) => void + setPostLanguage: (commaSeparatedLangCodes: string) => void + toggleContentLanguage: (code2: string) => void + togglePostLanguage: (code2: string) => void + savePostLanguageToHistory: () => void + setAppLanguage: (code2: string) => void +} + +const stateContext = React.createContext<StateContext>( + persisted.defaults.languagePrefs, +) +const apiContext = React.createContext<ApiContext>({ + setPrimaryLanguage: (_: string) => {}, + setPostLanguage: (_: string) => {}, + toggleContentLanguage: (_: string) => {}, + togglePostLanguage: (_: string) => {}, + savePostLanguageToHistory: () => {}, + setAppLanguage: (_: string) => {}, +}) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [state, setState] = React.useState(persisted.get('languagePrefs')) + + const setStateWrapped = React.useCallback( + (fn: SetStateCb) => { + const s = fn(persisted.get('languagePrefs')) + setState(s) + persisted.write('languagePrefs', s) + }, + [setState], + ) + + React.useEffect(() => { + return persisted.onUpdate(() => { + setState(persisted.get('languagePrefs')) + }) + }, [setStateWrapped]) + + const api = React.useMemo( + () => ({ + setPrimaryLanguage(code2: string) { + setStateWrapped(s => ({...s, primaryLanguage: code2})) + }, + setPostLanguage(commaSeparatedLangCodes: string) { + setStateWrapped(s => ({...s, postLanguage: commaSeparatedLangCodes})) + }, + toggleContentLanguage(code2: string) { + setStateWrapped(s => { + const exists = s.contentLanguages.includes(code2) + const next = exists + ? s.contentLanguages.filter(lang => lang !== code2) + : s.contentLanguages.concat(code2) + return { + ...s, + contentLanguages: next, + } + }) + }, + togglePostLanguage(code2: string) { + setStateWrapped(s => { + const exists = hasPostLanguage(state.postLanguage, code2) + let next = s.postLanguage + + if (exists) { + next = toPostLanguages(s.postLanguage) + .filter(lang => lang !== code2) + .join(',') + } else { + // sort alphabetically for deterministic comparison in context menu + next = toPostLanguages(s.postLanguage) + .concat([code2]) + .sort((a, b) => a.localeCompare(b)) + .join(',') + } + + return { + ...s, + postLanguage: next, + } + }) + }, + /** + * Saves whatever language codes are currently selected into a history array, + * which is then used to populate the language selector menu. + */ + savePostLanguageToHistory() { + // filter out duplicate `this.postLanguage` if exists, and prepend + // value to start of array + setStateWrapped(s => ({ + ...s, + postLanguageHistory: [s.postLanguage] + .concat( + s.postLanguageHistory.filter( + commaSeparatedLangCodes => + commaSeparatedLangCodes !== s.postLanguage, + ), + ) + .slice(0, 6), + })) + }, + setAppLanguage(code2: string) { + setStateWrapped(s => ({...s, appLanguage: code2})) + }, + }), + [state, setStateWrapped], + ) + + return ( + <stateContext.Provider value={state}> + <apiContext.Provider value={api}>{children}</apiContext.Provider> + </stateContext.Provider> + ) +} + +export function useLanguagePrefs() { + return React.useContext(stateContext) +} + +export function useLanguagePrefsApi() { + return React.useContext(apiContext) +} + +export function getContentLanguages() { + return persisted.get('languagePrefs').contentLanguages +} + +export function toPostLanguages(postLanguage: string): string[] { + // filter out empty strings if exist + return postLanguage.split(',').filter(Boolean) +} + +export function hasPostLanguage(postLanguage: string, code2: string): boolean { + return toPostLanguages(postLanguage).includes(code2) +} |