about summary refs log tree commit diff
path: root/src/state/preferences
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/preferences')
-rw-r--r--src/state/preferences/alt-text-required.tsx48
-rw-r--r--src/state/preferences/feed-tuners.tsx52
-rw-r--r--src/state/preferences/index.tsx17
-rw-r--r--src/state/preferences/languages.tsx142
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)
+}