about summary refs log tree commit diff
path: root/src/state/models/post-thread-view.ts
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-04-03 15:21:17 -0500
committerGitHub <noreply@github.com>2023-04-03 15:21:17 -0500
commit2045c615a8f8a39ee9f54638a234f3d45f028399 (patch)
tree059b4435bb1c6720e40e8767c3eb0dae8d894e67 /src/state/models/post-thread-view.ts
parent9652d994dd207585fb1b8f3452382478f204f70a (diff)
downloadvoidsky-2045c615a8f8a39ee9f54638a234f3d45f028399.tar.zst
Reorganize state models for clarity (#378)
Diffstat (limited to 'src/state/models/post-thread-view.ts')
-rw-r--r--src/state/models/post-thread-view.ts372
1 files changed, 0 insertions, 372 deletions
diff --git a/src/state/models/post-thread-view.ts b/src/state/models/post-thread-view.ts
deleted file mode 100644
index c5395b9c8..000000000
--- a/src/state/models/post-thread-view.ts
+++ /dev/null
@@ -1,372 +0,0 @@
-import {makeAutoObservable, runInAction} from 'mobx'
-import {
-  AppBskyFeedGetPostThread as GetPostThread,
-  AppBskyFeedPost as FeedPost,
-  AppBskyFeedDefs,
-  RichText,
-} from '@atproto/api'
-import {AtUri} from '../../third-party/uri'
-import {RootStoreModel} from './root-store'
-import * as apilib from 'lib/api/index'
-import {cleanError} from 'lib/strings/errors'
-
-function* reactKeyGenerator(): Generator<string> {
-  let counter = 0
-  while (true) {
-    yield `item-${counter++}`
-  }
-}
-
-export class PostThreadViewPostModel {
-  // ui state
-  _reactKey: string = ''
-  _depth = 0
-  _isHighlightedPost = false
-  _showParentReplyLine = false
-  _showChildReplyLine = false
-  _hasMore = false
-
-  // data
-  post: AppBskyFeedDefs.PostView
-  postRecord?: FeedPost.Record
-  parent?: PostThreadViewPostModel | AppBskyFeedDefs.NotFoundPost
-  replies?: (PostThreadViewPostModel | AppBskyFeedDefs.NotFoundPost)[]
-  richText?: RichText
-
-  get uri() {
-    return this.post.uri
-  }
-
-  get parentUri() {
-    return this.postRecord?.reply?.parent.uri
-  }
-
-  constructor(
-    public rootStore: RootStoreModel,
-    reactKey: string,
-    v: AppBskyFeedDefs.ThreadViewPost,
-  ) {
-    this._reactKey = reactKey
-    this.post = v.post
-    if (FeedPost.isRecord(this.post.record)) {
-      const valid = FeedPost.validateRecord(this.post.record)
-      if (valid.success) {
-        this.postRecord = this.post.record
-        this.richText = new RichText(this.postRecord, {cleanNewlines: true})
-      } else {
-        rootStore.log.warn(
-          'Received an invalid app.bsky.feed.post record',
-          valid.error,
-        )
-      }
-    } else {
-      rootStore.log.warn(
-        'app.bsky.feed.getPostThread served an unexpected record type',
-        this.post.record,
-      )
-    }
-    // replies and parent are handled via assignTreeModels
-    makeAutoObservable(this, {rootStore: false})
-  }
-
-  assignTreeModels(
-    keyGen: Generator<string>,
-    v: AppBskyFeedDefs.ThreadViewPost,
-    higlightedPostUri: string,
-    includeParent = true,
-    includeChildren = true,
-  ) {
-    // parents
-    if (includeParent && v.parent) {
-      if (AppBskyFeedDefs.isThreadViewPost(v.parent)) {
-        const parentModel = new PostThreadViewPostModel(
-          this.rootStore,
-          keyGen.next().value,
-          v.parent,
-        )
-        parentModel._depth = this._depth - 1
-        parentModel._showChildReplyLine = true
-        if (v.parent.parent) {
-          parentModel._showParentReplyLine = true //parentModel.uri !== higlightedPostUri
-          parentModel.assignTreeModels(
-            keyGen,
-            v.parent,
-            higlightedPostUri,
-            true,
-            false,
-          )
-        }
-        this.parent = parentModel
-      } else if (AppBskyFeedDefs.isNotFoundPost(v.parent)) {
-        this.parent = v.parent
-      }
-    }
-    // replies
-    if (includeChildren && v.replies) {
-      const replies = []
-      for (const item of v.replies) {
-        if (AppBskyFeedDefs.isThreadViewPost(item)) {
-          const itemModel = new PostThreadViewPostModel(
-            this.rootStore,
-            keyGen.next().value,
-            item,
-          )
-          itemModel._depth = this._depth + 1
-          itemModel._showParentReplyLine =
-            itemModel.parentUri !== higlightedPostUri
-          if (item.replies?.length) {
-            itemModel._showChildReplyLine = true
-            itemModel.assignTreeModels(
-              keyGen,
-              item,
-              higlightedPostUri,
-              false,
-              true,
-            )
-          }
-          replies.push(itemModel)
-        } else if (AppBskyFeedDefs.isNotFoundPost(item)) {
-          replies.push(item)
-        }
-      }
-      this.replies = replies
-    }
-  }
-
-  async toggleLike() {
-    if (this.post.viewer?.like) {
-      await this.rootStore.agent.deleteLike(this.post.viewer.like)
-      runInAction(() => {
-        this.post.likeCount = this.post.likeCount || 0
-        this.post.viewer = this.post.viewer || {}
-        this.post.likeCount--
-        this.post.viewer.like = undefined
-      })
-    } else {
-      const res = await this.rootStore.agent.like(this.post.uri, this.post.cid)
-      runInAction(() => {
-        this.post.likeCount = this.post.likeCount || 0
-        this.post.viewer = this.post.viewer || {}
-        this.post.likeCount++
-        this.post.viewer.like = res.uri
-      })
-    }
-  }
-
-  async toggleRepost() {
-    if (this.post.viewer?.repost) {
-      await this.rootStore.agent.deleteRepost(this.post.viewer.repost)
-      runInAction(() => {
-        this.post.repostCount = this.post.repostCount || 0
-        this.post.viewer = this.post.viewer || {}
-        this.post.repostCount--
-        this.post.viewer.repost = undefined
-      })
-    } else {
-      const res = await this.rootStore.agent.repost(
-        this.post.uri,
-        this.post.cid,
-      )
-      runInAction(() => {
-        this.post.repostCount = this.post.repostCount || 0
-        this.post.viewer = this.post.viewer || {}
-        this.post.repostCount++
-        this.post.viewer.repost = res.uri
-      })
-    }
-  }
-
-  async delete() {
-    await this.rootStore.agent.deletePost(this.post.uri)
-    this.rootStore.emitPostDeleted(this.post.uri)
-  }
-}
-
-export class PostThreadViewModel {
-  // state
-  isLoading = false
-  isRefreshing = false
-  hasLoaded = false
-  error = ''
-  notFound = false
-  resolvedUri = ''
-  params: GetPostThread.QueryParams
-
-  // data
-  thread?: PostThreadViewPostModel
-
-  constructor(
-    public rootStore: RootStoreModel,
-    params: GetPostThread.QueryParams,
-  ) {
-    makeAutoObservable(
-      this,
-      {
-        rootStore: false,
-        params: false,
-      },
-      {autoBind: true},
-    )
-    this.params = params
-  }
-
-  get hasContent() {
-    return typeof this.thread !== 'undefined'
-  }
-
-  get hasError() {
-    return this.error !== ''
-  }
-
-  // public api
-  // =
-
-  /**
-   * Load for first render
-   */
-  async setup() {
-    if (!this.resolvedUri) {
-      await this._resolveUri()
-    }
-    if (this.hasContent) {
-      await this.update()
-    } else {
-      await this._load()
-    }
-  }
-
-  /**
-   * Register any event listeners. Returns a cleanup function.
-   */
-  registerListeners() {
-    const sub = this.rootStore.onPostDeleted(this.onPostDeleted.bind(this))
-    return () => sub.remove()
-  }
-
-  /**
-   * Reset and load
-   */
-  async refresh() {
-    await this._load(true)
-  }
-
-  /**
-   * Update content in-place
-   */
-  async update() {
-    // NOTE: it currently seems that a full load-and-replace works fine for this
-    //       if the UI loses its place or has jarring re-arrangements, replace this
-    //       with a more in-place update
-    this._load()
-  }
-
-  /**
-   * Refreshes when posts are deleted
-   */
-  onPostDeleted(_uri: string) {
-    this.refresh()
-  }
-
-  // state transitions
-  // =
-
-  _xLoading(isRefreshing = false) {
-    this.isLoading = true
-    this.isRefreshing = isRefreshing
-    this.error = ''
-    this.notFound = false
-  }
-
-  _xIdle(err?: any) {
-    this.isLoading = false
-    this.isRefreshing = false
-    this.hasLoaded = true
-    this.error = cleanError(err)
-    if (err) {
-      this.rootStore.log.error('Failed to fetch post thread', err)
-    }
-    this.notFound = err instanceof GetPostThread.NotFoundError
-  }
-
-  // loader functions
-  // =
-
-  async _resolveUri() {
-    const urip = new AtUri(this.params.uri)
-    if (!urip.host.startsWith('did:')) {
-      try {
-        urip.host = await apilib.resolveName(this.rootStore, urip.host)
-      } catch (e: any) {
-        this.error = e.toString()
-      }
-    }
-    runInAction(() => {
-      this.resolvedUri = urip.toString()
-    })
-  }
-
-  async _load(isRefreshing = false) {
-    this._xLoading(isRefreshing)
-    try {
-      const res = await this.rootStore.agent.getPostThread(
-        Object.assign({}, this.params, {uri: this.resolvedUri}),
-      )
-      this._replaceAll(res)
-      this._xIdle()
-    } catch (e: any) {
-      this._xIdle(e)
-    }
-  }
-
-  _replaceAll(res: GetPostThread.Response) {
-    sortThread(res.data.thread)
-    const keyGen = reactKeyGenerator()
-    const thread = new PostThreadViewPostModel(
-      this.rootStore,
-      keyGen.next().value,
-      res.data.thread as AppBskyFeedDefs.ThreadViewPost,
-    )
-    thread._isHighlightedPost = true
-    thread.assignTreeModels(
-      keyGen,
-      res.data.thread as AppBskyFeedDefs.ThreadViewPost,
-      thread.uri,
-    )
-    this.thread = thread
-  }
-}
-
-type MaybePost =
-  | AppBskyFeedDefs.ThreadViewPost
-  | AppBskyFeedDefs.NotFoundPost
-  | {[k: string]: unknown; $type: string}
-function sortThread(post: MaybePost) {
-  if (post.notFound) {
-    return
-  }
-  post = post as AppBskyFeedDefs.ThreadViewPost
-  if (post.replies) {
-    post.replies.sort((a: MaybePost, b: MaybePost) => {
-      post = post as AppBskyFeedDefs.ThreadViewPost
-      if (a.notFound) {
-        return 1
-      }
-      if (b.notFound) {
-        return -1
-      }
-      a = a as AppBskyFeedDefs.ThreadViewPost
-      b = b as AppBskyFeedDefs.ThreadViewPost
-      const aIsByOp = a.post.author.did === post.post.author.did
-      const bIsByOp = b.post.author.did === post.post.author.did
-      if (aIsByOp && bIsByOp) {
-        return a.post.indexedAt.localeCompare(b.post.indexedAt) // oldest
-      } else if (aIsByOp) {
-        return -1 // op's own reply
-      } else if (bIsByOp) {
-        return 1 // op's own reply
-      }
-      return b.post.indexedAt.localeCompare(a.post.indexedAt) // newest
-    })
-    post.replies.forEach(reply => sortThread(reply))
-  }
-}