about summary refs log tree commit diff
path: root/src/state
diff options
context:
space:
mode:
Diffstat (limited to 'src/state')
-rw-r--r--src/state/models/content/feed-source.ts2
-rw-r--r--src/state/models/ui/preferences.ts18
-rw-r--r--src/state/queries/feed.ts132
-rw-r--r--src/state/queries/preferences/types.ts5
4 files changed, 128 insertions, 29 deletions
diff --git a/src/state/models/content/feed-source.ts b/src/state/models/content/feed-source.ts
index 156e3be3b..cd8c08b56 100644
--- a/src/state/models/content/feed-source.ts
+++ b/src/state/models/content/feed-source.ts
@@ -61,7 +61,7 @@ export class FeedSourceModel {
   }
 
   get isPinned() {
-    return this.rootStore.preferences.isPinnedFeed(this.uri)
+    return false
   }
 
   get isLiked() {
diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts
index 4f43487e7..1068ac651 100644
--- a/src/state/models/ui/preferences.ts
+++ b/src/state/models/ui/preferences.ts
@@ -4,7 +4,6 @@ import {
   BskyFeedViewPreference,
   BskyThreadViewPreference,
 } from '@atproto/api'
-import AwaitLock from 'await-lock'
 import {isObj, hasProp} from 'lib/type-guards'
 import {RootStoreModel} from '../root-store'
 import {ModerationOpts} from '@atproto/api'
@@ -33,30 +32,17 @@ export class LabelPreferencesModel {
 }
 
 export class PreferencesModel {
-  adultContentEnabled = false
   contentLabels = new LabelPreferencesModel()
   savedFeeds: string[] = []
   pinnedFeeds: string[] = []
-  birthDate: Date | undefined = undefined
-  homeFeed: FeedViewPreference = {
-    hideReplies: false,
-    hideRepliesByUnfollowed: false,
-    hideRepliesByLikeCount: 0,
-    hideReposts: false,
-    hideQuotePosts: false,
-    lab_mergeFeedEnabled: false, // experimental
-  }
   thread: ThreadViewPreference = {
     sort: 'oldest',
     prioritizeFollowedUsers: true,
     lab_treeViewEnabled: false, // experimental
   }
 
-  // used to linearize async modifications to state
-  lock = new AwaitLock()
-
   constructor(public rootStore: RootStoreModel) {
-    makeAutoObservable(this, {lock: false}, {autoBind: true})
+    makeAutoObservable(this, {}, {autoBind: true})
   }
 
   serialize() {
@@ -106,7 +92,7 @@ export class PreferencesModel {
   get moderationOpts(): ModerationOpts {
     return {
       userDid: this.rootStore.session.currentSession?.did || '',
-      adultContentEnabled: this.adultContentEnabled,
+      adultContentEnabled: false,
       labels: {
         // TEMP translate old settings until this UI can be migrated -prf
         porn: tempfixLabelPref(this.contentLabels.nsfw),
diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts
index 5754d2c70..dde37315d 100644
--- a/src/state/queries/feed.ts
+++ b/src/state/queries/feed.ts
@@ -1,9 +1,11 @@
+import React from 'react'
 import {
   useQuery,
   useInfiniteQuery,
   InfiniteData,
   QueryKey,
   useMutation,
+  useQueryClient,
 } from '@tanstack/react-query'
 import {
   AtUri,
@@ -13,16 +15,22 @@ import {
   AppBskyUnspeccedGetPopularFeedGenerators,
 } from '@atproto/api'
 
+import {router} from '#/routes'
 import {sanitizeDisplayName} from '#/lib/strings/display-names'
 import {sanitizeHandle} from '#/lib/strings/handles'
 import {useSession} from '#/state/session'
+import {usePreferencesQuery} from '#/state/queries/preferences'
 
-type FeedSourceInfo =
+export type FeedSourceInfo =
   | {
       type: 'feed'
       uri: string
+      route: {
+        href: string
+        name: string
+        params: Record<string, string>
+      }
       cid: string
-      href: string
       avatar: string | undefined
       displayName: string
       description: RichText
@@ -34,8 +42,12 @@ type FeedSourceInfo =
   | {
       type: 'list'
       uri: string
+      route: {
+        href: string
+        name: string
+        params: Record<string, string>
+      }
       cid: string
-      href: string
       avatar: string | undefined
       displayName: string
       description: RichText
@@ -43,7 +55,7 @@ type FeedSourceInfo =
       creatorHandle: string
     }
 
-export const useFeedSourceInfoQueryKey = ({uri}: {uri: string}) => [
+export const feedSourceInfoQueryKey = ({uri}: {uri: string}) => [
   'getFeedSourceInfo',
   uri,
 ]
@@ -53,19 +65,24 @@ const feedSourceNSIDs = {
   list: 'app.bsky.graph.list',
 }
 
-function hydrateFeedGenerator(
+export function hydrateFeedGenerator(
   view: AppBskyFeedDefs.GeneratorView,
 ): FeedSourceInfo {
   const urip = new AtUri(view.uri)
   const collection =
     urip.collection === 'app.bsky.feed.generator' ? 'feed' : 'lists'
   const href = `/profile/${urip.hostname}/${collection}/${urip.rkey}`
+  const route = router.matchPath(href)
 
   return {
     type: 'feed',
     uri: view.uri,
     cid: view.cid,
-    href,
+    route: {
+      href,
+      name: route[0],
+      params: route[1],
+    },
     avatar: view.avatar,
     displayName: view.displayName
       ? sanitizeDisplayName(view.displayName)
@@ -81,17 +98,22 @@ function hydrateFeedGenerator(
   }
 }
 
-function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo {
+export function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo {
   const urip = new AtUri(view.uri)
   const collection =
     urip.collection === 'app.bsky.feed.generator' ? 'feed' : 'lists'
   const href = `/profile/${urip.hostname}/${collection}/${urip.rkey}`
+  const route = router.matchPath(href)
 
   return {
     type: 'list',
     uri: view.uri,
+    route: {
+      href,
+      name: route[0],
+      params: route[1],
+    },
     cid: view.cid,
-    href,
     avatar: view.avatar,
     description: new RichText({
       text: view.description || '',
@@ -105,13 +127,17 @@ function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo {
   }
 }
 
+export function getFeedTypeFromUri(uri: string) {
+  const {pathname} = new AtUri(uri)
+  return pathname.includes(feedSourceNSIDs.feed) ? 'feed' : 'list'
+}
+
 export function useFeedSourceInfoQuery({uri}: {uri: string}) {
   const {agent} = useSession()
-  const {pathname} = new AtUri(uri)
-  const type = pathname.includes(feedSourceNSIDs.feed) ? 'feed' : 'list'
+  const type = getFeedTypeFromUri(uri)
 
   return useQuery({
-    queryKey: useFeedSourceInfoQueryKey({uri}),
+    queryKey: feedSourceInfoQueryKey({uri}),
     queryFn: async () => {
       let view: FeedSourceInfo
 
@@ -170,3 +196,87 @@ export function useSearchPopularFeedsMutation() {
     },
   })
 }
+
+const FOLLOWING_FEED_STUB: FeedSourceInfo = {
+  type: 'feed',
+  displayName: 'Following',
+  uri: '',
+  route: {
+    href: '/',
+    name: 'Home',
+    params: {},
+  },
+  cid: '',
+  avatar: '',
+  description: new RichText({text: ''}),
+  creatorDid: '',
+  creatorHandle: '',
+  likeCount: 0,
+  likeUri: '',
+}
+
+export function usePinnedFeedsInfos(): FeedSourceInfo[] {
+  const {agent} = useSession()
+  const queryClient = useQueryClient()
+  const [tabs, setTabs] = React.useState<FeedSourceInfo[]>([
+    FOLLOWING_FEED_STUB,
+  ])
+  const {data: preferences} = usePreferencesQuery()
+  const pinnedFeedsKey = JSON.stringify(preferences?.feeds?.pinned)
+
+  React.useEffect(() => {
+    if (!preferences?.feeds?.pinned) return
+    const uris = preferences.feeds.pinned
+
+    async function fetchFeedInfo() {
+      const reqs = []
+
+      for (const uri of uris) {
+        const cached = queryClient.getQueryData<FeedSourceInfo>(
+          feedSourceInfoQueryKey({uri}),
+        )
+
+        if (cached) {
+          reqs.push(cached)
+        } else {
+          reqs.push(
+            queryClient.fetchQuery({
+              queryKey: feedSourceInfoQueryKey({uri}),
+              queryFn: async () => {
+                const type = getFeedTypeFromUri(uri)
+
+                if (type === 'feed') {
+                  const res = await agent.app.bsky.feed.getFeedGenerator({
+                    feed: uri,
+                  })
+                  return hydrateFeedGenerator(res.data.view)
+                } else {
+                  const res = await agent.app.bsky.graph.getList({
+                    list: uri,
+                    limit: 1,
+                  })
+                  return hydrateList(res.data.list)
+                }
+              },
+            }),
+          )
+        }
+      }
+
+      const views = await Promise.all(reqs)
+
+      setTabs([FOLLOWING_FEED_STUB].concat(views))
+    }
+
+    fetchFeedInfo()
+  }, [
+    agent,
+    queryClient,
+    setTabs,
+    preferences?.feeds?.pinned,
+    // ensure we react to re-ordering
+    pinnedFeedsKey,
+  ])
+
+  return tabs
+}
diff --git a/src/state/queries/preferences/types.ts b/src/state/queries/preferences/types.ts
index 9f4c30e53..2b04b725f 100644
--- a/src/state/queries/preferences/types.ts
+++ b/src/state/queries/preferences/types.ts
@@ -2,6 +2,7 @@ import {
   BskyPreferences,
   LabelPreference,
   BskyThreadViewPreference,
+  BskyFeedViewPreference,
 } from '@atproto/api'
 
 export type ConfigurableLabelGroup =
@@ -29,7 +30,9 @@ export type UsePreferencesQueryResponse = Omit<
    * we clean up the data in `usePreferencesQuery`.
    */
   contentLabels: Record<ConfigurableLabelGroup, LabelPreference>
-  feedViewPrefs: BskyPreferences['feedViewPrefs']['home']
+  feedViewPrefs: BskyFeedViewPreference & {
+    lab_mergeFeedEnabled: boolean
+  }
   /**
    * User thread-view prefs, including newer fields that may not be typed yet.
    */