about summary refs log tree commit diff
path: root/src/state/models/lists/lists-list.ts
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-11-01 16:15:40 -0700
committerGitHub <noreply@github.com>2023-11-01 16:15:40 -0700
commitf57a8cf8ba0cd10a54abf35d960d8fb90266fa6b (patch)
treea9da6032bcbd587d92fd1030e698aea2dbef9f72 /src/state/models/lists/lists-list.ts
parentf9944b55e26fe6109bc2e7a25b88979111470ed9 (diff)
downloadvoidsky-f57a8cf8ba0cd10a54abf35d960d8fb90266fa6b.tar.zst
Lists updates: curate lists and blocklists (#1689)
* Add lists screen

* Update Lists screen and List create/edit modal to support curate lists

* Rework the ProfileList screen and add curatelist support

* More ProfileList progress

* Update list modals

* Rename mutelists to modlists

* Layout updates/fixes

* More layout fixes

* Modal fixes

* List list screen updates

* Update feed page to give more info

* Layout fixes to ListAddUser modal

* Layout fixes to FlatList and Feed on desktop

* Layout fix to LoadLatestBtn on Web

* Handle did resolution before showing the ProfileList screen

* Rename the CustomFeed routes to ProfileFeed for consistency

* Fix layout issues with the pager and feeds

* Factor out some common code

* Fix UIs for mobile

* Fix user list rendering

* Fix: dont bubble custom feed errors in the merge feed

* Refactor feed models to reduce usage of the SavedFeeds model

* Replace CustomFeedModel with FeedSourceModel which abstracts feed-generators and lists

* Add the ability to pin lists

* Add pinned lists to mobile

* Remove dead code

* Rework the ProfileScreenHeader to create more real-estate for action buttons

* Improve layout behavior on web mobile breakpoints

* Refactor feed & list pages to use new Tabs layout component

* Refactor to ProfileSubpageHeader

* Implement modlist block and mute

* Switch to new api and just modify state on modlist actions

* Fix some UI overflows

* Fix: dont show edit buttons on lists you dont own

* Fix alignment issue on long titles

* Improve loading and error states for feeds & lists

* Update list dropdown icons for ios

* Fetch feed display names in the mergefeed

* Improve rendering off offline feeds in the feed-listing page

* Update Feeds listing UI to react to changes in saved/pinned state

* Refresh list and feed on posts tab press

* Fix pinned feed ordering UI

* Fixes to list pinning

* Remove view=simple qp

* Add list to feed tuners

* Render richtext

* Add list href

* Add 'view avatar'

* Remove unused import

* Fix missing import

* Correctly reflect block by list state

* Replace the <Tabs> component with the more effective <PagerWithHeader> component

* Improve the responsiveness of the PagerWithHeader

* Fix visual jank in the feed loading state

* Improve performance of the PagerWithHeader

* Fix a case that would cause the header to animate too aggressively

* Add the ability to scroll to top by tapping the selected tab

* Fix unit test runner

* Update modlists test

* Add curatelist tests

* Fix: remove link behavior in ListAddUser modal

* Fix some layout jank in the PagerWithHeader on iOS

* Simplify ListItems header rendering

* Wait for the appview to recognize the list before proceeding with list creation

* Fix glitch in the onPageSelecting index of the Pager

* Fix until()

* Copy fix

Co-authored-by: Eric Bailey <git@esb.lol>

---------

Co-authored-by: Eric Bailey <git@esb.lol>
Diffstat (limited to 'src/state/models/lists/lists-list.ts')
-rw-r--r--src/state/models/lists/lists-list.ts205
1 files changed, 108 insertions, 97 deletions
diff --git a/src/state/models/lists/lists-list.ts b/src/state/models/lists/lists-list.ts
index 54e2f5fde..42638757a 100644
--- a/src/state/models/lists/lists-list.ts
+++ b/src/state/models/lists/lists-list.ts
@@ -1,12 +1,9 @@
 import {makeAutoObservable} from 'mobx'
-import {
-  AppBskyGraphGetLists as GetLists,
-  AppBskyGraphGetListMutes as GetListMutes,
-  AppBskyGraphDefs as GraphDefs,
-} from '@atproto/api'
+import {AppBskyGraphDefs as GraphDefs} from '@atproto/api'
 import {RootStoreModel} from '../root-store'
 import {cleanError} from 'lib/strings/errors'
 import {bundleAsync} from 'lib/async/bundle'
+import {accumulate} from 'lib/async/accumulate'
 
 const PAGE_SIZE = 30
 
@@ -25,7 +22,7 @@ export class ListsListModel {
 
   constructor(
     public rootStore: RootStoreModel,
-    public source: 'my-modlists' | string,
+    public source: 'mine' | 'my-curatelists' | 'my-modlists' | string,
   ) {
     makeAutoObservable(
       this,
@@ -48,6 +45,26 @@ export class ListsListModel {
     return this.hasLoaded && !this.hasContent
   }
 
+  get curatelists() {
+    return this.lists.filter(
+      list => list.purpose === 'app.bsky.graph.defs#curatelist',
+    )
+  }
+
+  get isCuratelistsEmpty() {
+    return this.hasLoaded && this.curatelists.length === 0
+  }
+
+  get modlists() {
+    return this.lists.filter(
+      list => list.purpose === 'app.bsky.graph.defs#modlist',
+    )
+  }
+
+  get isModlistsEmpty() {
+    return this.hasLoaded && this.modlists.length === 0
+  }
+
   /**
    * Removes posts from the feed upon deletion.
    */
@@ -76,44 +93,85 @@ export class ListsListModel {
     }
     this._xLoading(replace)
     try {
-      let res: GetLists.Response
-      if (this.source === 'my-modlists') {
-        res = {
-          success: true,
-          headers: {},
-          data: {
-            subject: undefined,
-            lists: [],
-          },
-        }
-        const [res1, res2] = await Promise.all([
-          fetchAllUserLists(this.rootStore, this.rootStore.me.did),
-          fetchAllMyMuteLists(this.rootStore),
-        ])
-        for (let list of res1.data.lists) {
-          if (list.purpose === 'app.bsky.graph.defs#modlist') {
-            res.data.lists.push(list)
-          }
+      let cursor: string | undefined
+      let lists: GraphDefs.ListView[] = []
+      if (
+        this.source === 'mine' ||
+        this.source === 'my-curatelists' ||
+        this.source === 'my-modlists'
+      ) {
+        const promises = [
+          accumulate(cursor =>
+            this.rootStore.agent.app.bsky.graph
+              .getLists({
+                actor: this.rootStore.me.did,
+                cursor,
+                limit: 50,
+              })
+              .then(res => ({cursor: res.data.cursor, items: res.data.lists})),
+          ),
+        ]
+        if (this.source === 'my-modlists') {
+          promises.push(
+            accumulate(cursor =>
+              this.rootStore.agent.app.bsky.graph
+                .getListMutes({
+                  cursor,
+                  limit: 50,
+                })
+                .then(res => ({
+                  cursor: res.data.cursor,
+                  items: res.data.lists,
+                })),
+            ),
+          )
+          promises.push(
+            accumulate(cursor =>
+              this.rootStore.agent.app.bsky.graph
+                .getListBlocks({
+                  cursor,
+                  limit: 50,
+                })
+                .then(res => ({
+                  cursor: res.data.cursor,
+                  items: res.data.lists,
+                })),
+            ),
+          )
         }
-        for (let list of res2.data.lists) {
-          if (
-            list.purpose === 'app.bsky.graph.defs#modlist' &&
-            !res.data.lists.find(l => l.uri === list.uri)
-          ) {
-            res.data.lists.push(list)
+        const resultset = await Promise.all(promises)
+        for (const res of resultset) {
+          for (let list of res) {
+            if (
+              this.source === 'my-curatelists' &&
+              list.purpose !== 'app.bsky.graph.defs#curatelist'
+            ) {
+              continue
+            }
+            if (
+              this.source === 'my-modlists' &&
+              list.purpose !== 'app.bsky.graph.defs#modlist'
+            ) {
+              continue
+            }
+            if (!lists.find(l => l.uri === list.uri)) {
+              lists.push(list)
+            }
           }
         }
       } else {
-        res = await this.rootStore.agent.app.bsky.graph.getLists({
+        const res = await this.rootStore.agent.app.bsky.graph.getLists({
           actor: this.source,
           limit: PAGE_SIZE,
           cursor: replace ? undefined : this.loadMoreCursor,
         })
+        lists = res.data.lists
+        cursor = res.data.cursor
       }
       if (replace) {
-        this._replaceAll(res)
+        this._replaceAll({lists, cursor})
       } else {
-        this._appendAll(res)
+        this._appendAll({lists, cursor})
       }
       this._xIdle()
     } catch (e: any) {
@@ -156,75 +214,28 @@ export class ListsListModel {
   // helper functions
   // =
 
-  _replaceAll(res: GetLists.Response | GetListMutes.Response) {
+  _replaceAll({
+    lists,
+    cursor,
+  }: {
+    lists: GraphDefs.ListView[]
+    cursor: string | undefined
+  }) {
     this.lists = []
-    this._appendAll(res)
+    this._appendAll({lists, cursor})
   }
 
-  _appendAll(res: GetLists.Response | GetListMutes.Response) {
-    this.loadMoreCursor = res.data.cursor
+  _appendAll({
+    lists,
+    cursor,
+  }: {
+    lists: GraphDefs.ListView[]
+    cursor: string | undefined
+  }) {
+    this.loadMoreCursor = cursor
     this.hasMore = !!this.loadMoreCursor
     this.lists = this.lists.concat(
-      res.data.lists.map(list => ({...list, _reactKey: list.uri})),
+      lists.map(list => ({...list, _reactKey: list.uri})),
     )
   }
 }
-
-async function fetchAllUserLists(
-  store: RootStoreModel,
-  did: string,
-): Promise<GetLists.Response> {
-  let acc: GetLists.Response = {
-    success: true,
-    headers: {},
-    data: {
-      subject: undefined,
-      lists: [],
-    },
-  }
-
-  let cursor
-  for (let i = 0; i < 100; i++) {
-    const res: GetLists.Response = await store.agent.app.bsky.graph.getLists({
-      actor: did,
-      cursor,
-      limit: 50,
-    })
-    cursor = res.data.cursor
-    acc.data.lists = acc.data.lists.concat(res.data.lists)
-    if (!cursor) {
-      break
-    }
-  }
-
-  return acc
-}
-
-async function fetchAllMyMuteLists(
-  store: RootStoreModel,
-): Promise<GetListMutes.Response> {
-  let acc: GetListMutes.Response = {
-    success: true,
-    headers: {},
-    data: {
-      subject: undefined,
-      lists: [],
-    },
-  }
-
-  let cursor
-  for (let i = 0; i < 100; i++) {
-    const res: GetListMutes.Response =
-      await store.agent.app.bsky.graph.getListMutes({
-        cursor,
-        limit: 50,
-      })
-    cursor = res.data.cursor
-    acc.data.lists = acc.data.lists.concat(res.data.lists)
-    if (!cursor) {
-      break
-    }
-  }
-
-  return acc
-}