about summary refs log tree commit diff
path: root/src/state/models
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/models')
-rw-r--r--src/state/models/content/feed-source.ts231
-rw-r--r--src/state/models/discovery/feeds.ts148
-rw-r--r--src/state/models/lists/actor-feeds.ts123
-rw-r--r--src/state/models/ui/preferences.ts27
-rw-r--r--src/state/models/ui/profile.ts255
5 files changed, 0 insertions, 784 deletions
diff --git a/src/state/models/content/feed-source.ts b/src/state/models/content/feed-source.ts
deleted file mode 100644
index cd8c08b56..000000000
--- a/src/state/models/content/feed-source.ts
+++ /dev/null
@@ -1,231 +0,0 @@
-import {AtUri, RichText, AppBskyFeedDefs, AppBskyGraphDefs} from '@atproto/api'
-import {makeAutoObservable, runInAction} from 'mobx'
-import {RootStoreModel} from 'state/models/root-store'
-import {sanitizeDisplayName} from 'lib/strings/display-names'
-import {sanitizeHandle} from 'lib/strings/handles'
-import {bundleAsync} from 'lib/async/bundle'
-import {cleanError} from 'lib/strings/errors'
-import {track} from 'lib/analytics/analytics'
-import {logger} from '#/logger'
-
-export class FeedSourceModel {
-  // state
-  _reactKey: string
-  hasLoaded = false
-  error: string | undefined
-
-  // data
-  uri: string
-  cid: string = ''
-  type: 'feed-generator' | 'list' | 'unsupported' = 'unsupported'
-  avatar: string | undefined = ''
-  displayName: string = ''
-  descriptionRT: RichText | null = null
-  creatorDid: string = ''
-  creatorHandle: string = ''
-  likeCount: number | undefined = 0
-  likeUri: string | undefined = ''
-
-  constructor(public rootStore: RootStoreModel, uri: string) {
-    this._reactKey = uri
-    this.uri = uri
-
-    try {
-      const urip = new AtUri(uri)
-      if (urip.collection === 'app.bsky.feed.generator') {
-        this.type = 'feed-generator'
-      } else if (urip.collection === 'app.bsky.graph.list') {
-        this.type = 'list'
-      }
-    } catch {}
-    this.displayName = uri.split('/').pop() || ''
-
-    makeAutoObservable(
-      this,
-      {
-        rootStore: false,
-      },
-      {autoBind: true},
-    )
-  }
-
-  get href() {
-    const urip = new AtUri(this.uri)
-    const collection =
-      urip.collection === 'app.bsky.feed.generator' ? 'feed' : 'lists'
-    return `/profile/${urip.hostname}/${collection}/${urip.rkey}`
-  }
-
-  get isSaved() {
-    return this.rootStore.preferences.savedFeeds.includes(this.uri)
-  }
-
-  get isPinned() {
-    return false
-  }
-
-  get isLiked() {
-    return !!this.likeUri
-  }
-
-  get isOwner() {
-    return this.creatorDid === this.rootStore.me.did
-  }
-
-  setup = bundleAsync(async () => {
-    try {
-      if (this.type === 'feed-generator') {
-        const res = await this.rootStore.agent.app.bsky.feed.getFeedGenerator({
-          feed: this.uri,
-        })
-        this.hydrateFeedGenerator(res.data.view)
-      } else if (this.type === 'list') {
-        const res = await this.rootStore.agent.app.bsky.graph.getList({
-          list: this.uri,
-          limit: 1,
-        })
-        this.hydrateList(res.data.list)
-      }
-    } catch (e) {
-      runInAction(() => {
-        this.error = cleanError(e)
-      })
-    }
-  })
-
-  hydrateFeedGenerator(view: AppBskyFeedDefs.GeneratorView) {
-    this.uri = view.uri
-    this.cid = view.cid
-    this.avatar = view.avatar
-    this.displayName = view.displayName
-      ? sanitizeDisplayName(view.displayName)
-      : `Feed by ${sanitizeHandle(view.creator.handle, '@')}`
-    this.descriptionRT = new RichText({
-      text: view.description || '',
-      facets: (view.descriptionFacets || [])?.slice(),
-    })
-    this.creatorDid = view.creator.did
-    this.creatorHandle = view.creator.handle
-    this.likeCount = view.likeCount
-    this.likeUri = view.viewer?.like
-    this.hasLoaded = true
-  }
-
-  hydrateList(view: AppBskyGraphDefs.ListView) {
-    this.uri = view.uri
-    this.cid = view.cid
-    this.avatar = view.avatar
-    this.displayName = view.name
-      ? sanitizeDisplayName(view.name)
-      : `User List by ${sanitizeHandle(view.creator.handle, '@')}`
-    this.descriptionRT = new RichText({
-      text: view.description || '',
-      facets: (view.descriptionFacets || [])?.slice(),
-    })
-    this.creatorDid = view.creator.did
-    this.creatorHandle = view.creator.handle
-    this.likeCount = undefined
-    this.hasLoaded = true
-  }
-
-  async save() {
-    if (this.type !== 'feed-generator') {
-      return
-    }
-    try {
-      await this.rootStore.preferences.addSavedFeed(this.uri)
-    } catch (error) {
-      logger.error('Failed to save feed', {error})
-    } finally {
-      track('CustomFeed:Save')
-    }
-  }
-
-  async unsave() {
-    // TODO TEMPORARY — see PRF's comment in content/list.ts togglePin
-    if (this.type !== 'feed-generator' && this.type !== 'list') {
-      return
-    }
-    try {
-      await this.rootStore.preferences.removeSavedFeed(this.uri)
-    } catch (error) {
-      logger.error('Failed to unsave feed', {error})
-    } finally {
-      track('CustomFeed:Unsave')
-    }
-  }
-
-  async pin() {
-    try {
-      await this.rootStore.preferences.addPinnedFeed(this.uri)
-    } catch (error) {
-      logger.error('Failed to pin feed', {error})
-    } finally {
-      track('CustomFeed:Pin', {
-        name: this.displayName,
-        uri: this.uri,
-      })
-    }
-  }
-
-  async togglePin() {
-    if (!this.isPinned) {
-      track('CustomFeed:Pin', {
-        name: this.displayName,
-        uri: this.uri,
-      })
-      return this.rootStore.preferences.addPinnedFeed(this.uri)
-    } else {
-      track('CustomFeed:Unpin', {
-        name: this.displayName,
-        uri: this.uri,
-      })
-
-      if (this.type === 'list') {
-        // TODO TEMPORARY — see PRF's comment in content/list.ts togglePin
-        return this.unsave()
-      } else {
-        return this.rootStore.preferences.removePinnedFeed(this.uri)
-      }
-    }
-  }
-
-  async like() {
-    if (this.type !== 'feed-generator') {
-      return
-    }
-    try {
-      this.likeUri = 'pending'
-      this.likeCount = (this.likeCount || 0) + 1
-      const res = await this.rootStore.agent.like(this.uri, this.cid)
-      this.likeUri = res.uri
-    } catch (e: any) {
-      this.likeUri = undefined
-      this.likeCount = (this.likeCount || 1) - 1
-      logger.error('Failed to like feed', {error: e})
-    } finally {
-      track('CustomFeed:Like')
-    }
-  }
-
-  async unlike() {
-    if (this.type !== 'feed-generator') {
-      return
-    }
-    if (!this.likeUri) {
-      return
-    }
-    const uri = this.likeUri
-    try {
-      this.likeUri = undefined
-      this.likeCount = (this.likeCount || 1) - 1
-      await this.rootStore.agent.deleteLike(uri!)
-    } catch (e: any) {
-      this.likeUri = uri
-      this.likeCount = (this.likeCount || 0) + 1
-      logger.error('Failed to unlike feed', {error: e})
-    } finally {
-      track('CustomFeed:Unlike')
-    }
-  }
-}
diff --git a/src/state/models/discovery/feeds.ts b/src/state/models/discovery/feeds.ts
deleted file mode 100644
index a7c94e40d..000000000
--- a/src/state/models/discovery/feeds.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-import {makeAutoObservable} from 'mobx'
-import {AppBskyUnspeccedGetPopularFeedGenerators} from '@atproto/api'
-import {RootStoreModel} from '../root-store'
-import {bundleAsync} from 'lib/async/bundle'
-import {cleanError} from 'lib/strings/errors'
-import {FeedSourceModel} from '../content/feed-source'
-import {logger} from '#/logger'
-
-const DEFAULT_LIMIT = 50
-
-export class FeedsDiscoveryModel {
-  // state
-  isLoading = false
-  isRefreshing = false
-  hasLoaded = false
-  error = ''
-  loadMoreCursor: string | undefined = undefined
-
-  // data
-  feeds: FeedSourceModel[] = []
-
-  constructor(public rootStore: RootStoreModel) {
-    makeAutoObservable(
-      this,
-      {
-        rootStore: false,
-      },
-      {autoBind: true},
-    )
-  }
-
-  get hasMore() {
-    if (this.loadMoreCursor) {
-      return true
-    }
-    return false
-  }
-
-  get hasContent() {
-    return this.feeds.length > 0
-  }
-
-  get hasError() {
-    return this.error !== ''
-  }
-
-  get isEmpty() {
-    return this.hasLoaded && !this.hasContent
-  }
-
-  // public api
-  // =
-
-  refresh = bundleAsync(async () => {
-    this._xLoading()
-    try {
-      const res =
-        await this.rootStore.agent.app.bsky.unspecced.getPopularFeedGenerators({
-          limit: DEFAULT_LIMIT,
-        })
-      this._replaceAll(res)
-      this._xIdle()
-    } catch (e: any) {
-      this._xIdle(e)
-    }
-  })
-
-  loadMore = bundleAsync(async () => {
-    if (!this.hasMore) {
-      return
-    }
-    this._xLoading()
-    try {
-      const res =
-        await this.rootStore.agent.app.bsky.unspecced.getPopularFeedGenerators({
-          limit: DEFAULT_LIMIT,
-          cursor: this.loadMoreCursor,
-        })
-      this._append(res)
-    } catch (e: any) {
-      this._xIdle(e)
-    }
-    this._xIdle()
-  })
-
-  search = async (query: string) => {
-    this._xLoading(false)
-    try {
-      const results =
-        await this.rootStore.agent.app.bsky.unspecced.getPopularFeedGenerators({
-          limit: DEFAULT_LIMIT,
-          query: query,
-        })
-      this._replaceAll(results)
-    } catch (e: any) {
-      this._xIdle(e)
-    }
-    this._xIdle()
-  }
-
-  clear() {
-    this.isLoading = false
-    this.isRefreshing = false
-    this.hasLoaded = false
-    this.error = ''
-    this.feeds = []
-  }
-
-  // state transitions
-  // =
-
-  _xLoading(isRefreshing = true) {
-    this.isLoading = true
-    this.isRefreshing = isRefreshing
-    this.error = ''
-  }
-
-  _xIdle(err?: any) {
-    this.isLoading = false
-    this.isRefreshing = false
-    this.hasLoaded = true
-    this.error = cleanError(err)
-    if (err) {
-      logger.error('Failed to fetch popular feeds', {error: err})
-    }
-  }
-
-  // helper functions
-  // =
-
-  _replaceAll(res: AppBskyUnspeccedGetPopularFeedGenerators.Response) {
-    // 1. set feeds data to empty array
-    this.feeds = []
-    // 2. call this._append()
-    this._append(res)
-  }
-
-  _append(res: AppBskyUnspeccedGetPopularFeedGenerators.Response) {
-    // 1. push data into feeds array
-    for (const f of res.data.feeds) {
-      const model = new FeedSourceModel(this.rootStore, f.uri)
-      model.hydrateFeedGenerator(f)
-      this.feeds.push(model)
-    }
-    // 2. set loadMoreCursor
-    this.loadMoreCursor = res.data.cursor
-  }
-}
diff --git a/src/state/models/lists/actor-feeds.ts b/src/state/models/lists/actor-feeds.ts
deleted file mode 100644
index 29c01e536..000000000
--- a/src/state/models/lists/actor-feeds.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import {makeAutoObservable} from 'mobx'
-import {AppBskyFeedGetActorFeeds as GetActorFeeds} from '@atproto/api'
-import {RootStoreModel} from '../root-store'
-import {bundleAsync} from 'lib/async/bundle'
-import {cleanError} from 'lib/strings/errors'
-import {FeedSourceModel} from '../content/feed-source'
-import {logger} from '#/logger'
-
-const PAGE_SIZE = 30
-
-export class ActorFeedsModel {
-  // state
-  isLoading = false
-  isRefreshing = false
-  hasLoaded = false
-  error = ''
-  hasMore = true
-  loadMoreCursor?: string
-
-  // data
-  feeds: FeedSourceModel[] = []
-
-  constructor(
-    public rootStore: RootStoreModel,
-    public params: GetActorFeeds.QueryParams,
-  ) {
-    makeAutoObservable(
-      this,
-      {
-        rootStore: false,
-      },
-      {autoBind: true},
-    )
-  }
-
-  get hasContent() {
-    return this.feeds.length > 0
-  }
-
-  get hasError() {
-    return this.error !== ''
-  }
-
-  get isEmpty() {
-    return this.hasLoaded && !this.hasContent
-  }
-
-  // public api
-  // =
-
-  async refresh() {
-    return this.loadMore(true)
-  }
-
-  clear() {
-    this.isLoading = false
-    this.isRefreshing = false
-    this.hasLoaded = false
-    this.error = ''
-    this.hasMore = true
-    this.loadMoreCursor = undefined
-    this.feeds = []
-  }
-
-  loadMore = bundleAsync(async (replace: boolean = false) => {
-    if (!replace && !this.hasMore) {
-      return
-    }
-    this._xLoading(replace)
-    try {
-      const res = await this.rootStore.agent.app.bsky.feed.getActorFeeds({
-        actor: this.params.actor,
-        limit: PAGE_SIZE,
-        cursor: replace ? undefined : this.loadMoreCursor,
-      })
-      if (replace) {
-        this._replaceAll(res)
-      } else {
-        this._appendAll(res)
-      }
-      this._xIdle()
-    } catch (e: any) {
-      this._xIdle(e)
-    }
-  })
-
-  // state transitions
-  // =
-
-  _xLoading(isRefreshing = false) {
-    this.isLoading = true
-    this.isRefreshing = isRefreshing
-    this.error = ''
-  }
-
-  _xIdle(err?: any) {
-    this.isLoading = false
-    this.isRefreshing = false
-    this.hasLoaded = true
-    this.error = cleanError(err)
-    if (err) {
-      logger.error('Failed to fetch user followers', {error: err})
-    }
-  }
-
-  // helper functions
-  // =
-
-  _replaceAll(res: GetActorFeeds.Response) {
-    this.feeds = []
-    this._appendAll(res)
-  }
-
-  _appendAll(res: GetActorFeeds.Response) {
-    this.loadMoreCursor = res.data.cursor
-    this.hasMore = !!this.loadMoreCursor
-    for (const f of res.data.feeds) {
-      const model = new FeedSourceModel(this.rootStore, f.uri)
-      model.hydrateFeedGenerator(f)
-      this.feeds.push(model)
-    }
-  }
-}
diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts
index 1068ac651..a4c3517cc 100644
--- a/src/state/models/ui/preferences.ts
+++ b/src/state/models/ui/preferences.ts
@@ -126,33 +126,6 @@ export class PreferencesModel {
       ],
     }
   }
-
-  // feeds
-  // =
-
-  isPinnedFeed(uri: string) {
-    return this.pinnedFeeds.includes(uri)
-  }
-
-  /**
-   * @deprecated use `useAddSavedFeedMutation` from `#/state/queries/preferences` instead
-   */
-  async addSavedFeed(_v: string) {}
-
-  /**
-   * @deprecated use `useRemoveSavedFeedMutation` from `#/state/queries/preferences` instead
-   */
-  async removeSavedFeed(_v: string) {}
-
-  /**
-   * @deprecated use `usePinFeedMutation` from `#/state/queries/preferences` instead
-   */
-  async addPinnedFeed(_v: string) {}
-
-  /**
-   * @deprecated use `useUnpinFeedMutation` from `#/state/queries/preferences` instead
-   */
-  async removePinnedFeed(_v: string) {}
 }
 
 // TEMP we need to permanently convert 'show' to 'ignore', for now we manually convert -prf
diff --git a/src/state/models/ui/profile.ts b/src/state/models/ui/profile.ts
deleted file mode 100644
index d6ea0c084..000000000
--- a/src/state/models/ui/profile.ts
+++ /dev/null
@@ -1,255 +0,0 @@
-import {makeAutoObservable, runInAction} from 'mobx'
-import {RootStoreModel} from '../root-store'
-import {ProfileModel} from '../content/profile'
-import {ActorFeedsModel} from '../lists/actor-feeds'
-import {logger} from '#/logger'
-
-export enum Sections {
-  PostsNoReplies = 'Posts',
-  PostsWithReplies = 'Posts & replies',
-  PostsWithMedia = 'Media',
-  Likes = 'Likes',
-  CustomAlgorithms = 'Feeds',
-  Lists = 'Lists',
-}
-
-export interface ProfileUiParams {
-  user: string
-}
-
-export class ProfileUiModel {
-  static LOADING_ITEM = {_reactKey: '__loading__'}
-  static END_ITEM = {_reactKey: '__end__'}
-  static EMPTY_ITEM = {_reactKey: '__empty__'}
-
-  isAuthenticatedUser = false
-
-  // data
-  profile: ProfileModel
-  feed: PostsFeedModel
-  algos: ActorFeedsModel
-  lists: ListsListModel
-
-  // ui state
-  selectedViewIndex = 0
-
-  constructor(
-    public rootStore: RootStoreModel,
-    public params: ProfileUiParams,
-  ) {
-    makeAutoObservable(
-      this,
-      {
-        rootStore: false,
-        params: false,
-      },
-      {autoBind: true},
-    )
-    this.profile = new ProfileModel(rootStore, {actor: params.user})
-    this.feed = new PostsFeedModel(rootStore, 'author', {
-      actor: params.user,
-      limit: 10,
-      filter: 'posts_no_replies',
-    })
-    this.algos = new ActorFeedsModel(rootStore, {actor: params.user})
-    this.lists = new ListsListModel(rootStore, params.user)
-  }
-
-  get currentView(): PostsFeedModel | ActorFeedsModel | ListsListModel {
-    if (
-      this.selectedView === Sections.PostsNoReplies ||
-      this.selectedView === Sections.PostsWithReplies ||
-      this.selectedView === Sections.PostsWithMedia ||
-      this.selectedView === Sections.Likes
-    ) {
-      return this.feed
-    } else if (this.selectedView === Sections.Lists) {
-      return this.lists
-    }
-    if (this.selectedView === Sections.CustomAlgorithms) {
-      return this.algos
-    }
-    throw new Error(`Invalid selector value: ${this.selectedViewIndex}`)
-  }
-
-  get isInitialLoading() {
-    const view = this.currentView
-    return view.isLoading && !view.isRefreshing && !view.hasContent
-  }
-
-  get isRefreshing() {
-    return this.profile.isRefreshing || this.currentView.isRefreshing
-  }
-
-  get selectorItems() {
-    const items = [
-      Sections.PostsNoReplies,
-      Sections.PostsWithReplies,
-      Sections.PostsWithMedia,
-      this.isAuthenticatedUser && Sections.Likes,
-    ].filter(Boolean) as string[]
-    if (this.algos.hasLoaded && !this.algos.isEmpty) {
-      items.push(Sections.CustomAlgorithms)
-    }
-    if (this.lists.hasLoaded && !this.lists.isEmpty) {
-      items.push(Sections.Lists)
-    }
-    return items
-  }
-
-  get selectedView() {
-    // If, for whatever reason, the selected view index is not available, default back to posts
-    // This can happen when the user was focused on a view but performed an action that caused
-    // the view to disappear (e.g. deleting the last list in their list of lists https://imgflip.com/i/7txu1y)
-    return this.selectorItems[this.selectedViewIndex] || Sections.PostsNoReplies
-  }
-
-  get uiItems() {
-    let arr: any[] = []
-    // if loading, return loading item to show loading spinner
-    if (this.isInitialLoading) {
-      arr = arr.concat([ProfileUiModel.LOADING_ITEM])
-    } else if (this.currentView.hasError) {
-      // if error, return error item to show error message
-      arr = arr.concat([
-        {
-          _reactKey: '__error__',
-          error: this.currentView.error,
-        },
-      ])
-    } else {
-      if (
-        this.selectedView === Sections.PostsNoReplies ||
-        this.selectedView === Sections.PostsWithReplies ||
-        this.selectedView === Sections.PostsWithMedia ||
-        this.selectedView === Sections.Likes
-      ) {
-        if (this.feed.hasContent) {
-          arr = this.feed.slices.slice()
-          if (!this.feed.hasMore) {
-            arr = arr.concat([ProfileUiModel.END_ITEM])
-          }
-        } else if (this.feed.isEmpty) {
-          arr = arr.concat([ProfileUiModel.EMPTY_ITEM])
-        }
-      } else if (this.selectedView === Sections.CustomAlgorithms) {
-        if (this.algos.hasContent) {
-          arr = this.algos.feeds
-        } else if (this.algos.isEmpty) {
-          arr = arr.concat([ProfileUiModel.EMPTY_ITEM])
-        }
-      } else if (this.selectedView === Sections.Lists) {
-        if (this.lists.hasContent) {
-          arr = this.lists.lists
-        } else if (this.lists.isEmpty) {
-          arr = arr.concat([ProfileUiModel.EMPTY_ITEM])
-        }
-      } else {
-        // fallback, add empty item, to show empty message
-        arr = arr.concat([ProfileUiModel.EMPTY_ITEM])
-      }
-    }
-    return arr
-  }
-
-  get showLoadingMoreFooter() {
-    if (
-      this.selectedView === Sections.PostsNoReplies ||
-      this.selectedView === Sections.PostsWithReplies ||
-      this.selectedView === Sections.PostsWithMedia ||
-      this.selectedView === Sections.Likes
-    ) {
-      return this.feed.hasContent && this.feed.hasMore && this.feed.isLoading
-    } else if (this.selectedView === Sections.Lists) {
-      return this.lists.hasContent && this.lists.hasMore && this.lists.isLoading
-    }
-    return false
-  }
-
-  // public api
-  // =
-
-  setSelectedViewIndex(index: number) {
-    // ViewSelector fires onSelectView on mount
-    if (index === this.selectedViewIndex) return
-
-    this.selectedViewIndex = index
-
-    if (
-      this.selectedView === Sections.PostsNoReplies ||
-      this.selectedView === Sections.PostsWithReplies ||
-      this.selectedView === Sections.PostsWithMedia
-    ) {
-      let filter = 'posts_no_replies'
-      if (this.selectedView === Sections.PostsWithReplies) {
-        filter = 'posts_with_replies'
-      } else if (this.selectedView === Sections.PostsWithMedia) {
-        filter = 'posts_with_media'
-      }
-
-      this.feed = new PostsFeedModel(
-        this.rootStore,
-        'author',
-        {
-          actor: this.params.user,
-          limit: 10,
-          filter,
-        },
-        {
-          isSimpleFeed: ['posts_with_media'].includes(filter),
-        },
-      )
-
-      this.feed.setup()
-    } else if (this.selectedView === Sections.Likes) {
-      this.feed = new PostsFeedModel(
-        this.rootStore,
-        'likes',
-        {
-          actor: this.params.user,
-          limit: 10,
-        },
-        {
-          isSimpleFeed: true,
-        },
-      )
-
-      this.feed.setup()
-    }
-  }
-
-  async setup() {
-    await Promise.all([
-      this.profile
-        .setup()
-        .catch(err => logger.error('Failed to fetch profile', {error: err})),
-      this.feed
-        .setup()
-        .catch(err => logger.error('Failed to fetch feed', {error: err})),
-    ])
-    runInAction(() => {
-      this.isAuthenticatedUser =
-        this.profile.did === this.rootStore.session.currentSession?.did
-    })
-    this.algos.refresh()
-    // HACK: need to use the DID as a param, not the username -prf
-    this.lists.source = this.profile.did
-    this.lists
-      .loadMore()
-      .catch(err => logger.error('Failed to fetch lists', {error: err}))
-  }
-
-  async refresh() {
-    await Promise.all([this.profile.refresh(), this.currentView.refresh()])
-  }
-
-  async loadMore() {
-    if (
-      !this.currentView.isLoading &&
-      !this.currentView.hasError &&
-      !this.currentView.isEmpty
-    ) {
-      await this.currentView.loadMore()
-    }
-  }
-}