Refactor models to use bundleAsync and lock regions (#171)
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
import {bundleAsync} from '../../../src/lib/async/bundle'
|
||||||
|
|
||||||
|
describe('bundle', () => {
|
||||||
|
it('bundles multiple simultaneous calls into one execution', async () => {
|
||||||
|
let calls = 0
|
||||||
|
const fn = bundleAsync(async () => {
|
||||||
|
calls++
|
||||||
|
await new Promise(r => setTimeout(r, 1))
|
||||||
|
return 'hello'
|
||||||
|
})
|
||||||
|
const [res1, res2, res3] = await Promise.all([fn(), fn(), fn()])
|
||||||
|
expect(calls).toEqual(1)
|
||||||
|
expect(res1).toEqual('hello')
|
||||||
|
expect(res2).toEqual('hello')
|
||||||
|
expect(res3).toEqual('hello')
|
||||||
|
})
|
||||||
|
it('does not bundle non-simultaneous calls', async () => {
|
||||||
|
let calls = 0
|
||||||
|
const fn = bundleAsync(async () => {
|
||||||
|
calls++
|
||||||
|
await new Promise(r => setTimeout(r, 1))
|
||||||
|
return 'hello'
|
||||||
|
})
|
||||||
|
const res1 = await fn()
|
||||||
|
const res2 = await fn()
|
||||||
|
const res3 = await fn()
|
||||||
|
expect(calls).toEqual(3)
|
||||||
|
expect(res1).toEqual('hello')
|
||||||
|
expect(res2).toEqual('hello')
|
||||||
|
expect(res3).toEqual('hello')
|
||||||
|
})
|
||||||
|
it('is not affected by rejections', async () => {
|
||||||
|
let calls = 0
|
||||||
|
const fn = bundleAsync(async () => {
|
||||||
|
calls++
|
||||||
|
await new Promise(r => setTimeout(r, 1))
|
||||||
|
throw new Error()
|
||||||
|
})
|
||||||
|
const res1 = await fn().catch(() => 'reject')
|
||||||
|
const res2 = await fn().catch(() => 'reject')
|
||||||
|
const res3 = await fn().catch(() => 'reject')
|
||||||
|
expect(calls).toEqual(3)
|
||||||
|
expect(res1).toEqual('reject')
|
||||||
|
expect(res2).toEqual('reject')
|
||||||
|
expect(res3).toEqual('reject')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
"@segment/analytics-react-native": "^2.10.1",
|
"@segment/analytics-react-native": "^2.10.1",
|
||||||
"@segment/sovran-react-native": "^0.4.5",
|
"@segment/sovran-react-native": "^0.4.5",
|
||||||
"@zxing/text-encoding": "^0.9.0",
|
"@zxing/text-encoding": "^0.9.0",
|
||||||
|
"await-lock": "^2.2.2",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"email-validator": "^2.0.4",
|
"email-validator": "^2.0.4",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
type BundledFn<Args extends readonly unknown[], Res> = (
|
||||||
|
...args: Args
|
||||||
|
) => Promise<Res>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper which ensures that multiple calls to an async function
|
||||||
|
* only produces one in-flight request at a time.
|
||||||
|
*/
|
||||||
|
export function bundleAsync<Args extends readonly unknown[], Res>(
|
||||||
|
fn: BundledFn<Args, Res>,
|
||||||
|
): BundledFn<Args, Res> {
|
||||||
|
let promise: Promise<Res> | undefined
|
||||||
|
return async (...args) => {
|
||||||
|
if (promise) {
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
promise = fn(...args)
|
||||||
|
try {
|
||||||
|
return await promise
|
||||||
|
} finally {
|
||||||
|
promise = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+88
-136
@@ -5,6 +5,8 @@ import {
|
|||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
AppBskyFeedGetAuthorFeed as GetAuthorFeed,
|
AppBskyFeedGetAuthorFeed as GetAuthorFeed,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import AwaitLock from 'await-lock'
|
||||||
|
import {bundleAsync} from '../../lib/async/bundle'
|
||||||
type FeedViewPost = AppBskyFeedFeedViewPost.Main
|
type FeedViewPost = AppBskyFeedFeedViewPost.Main
|
||||||
type ReasonRepost = AppBskyFeedFeedViewPost.ReasonRepost
|
type ReasonRepost = AppBskyFeedFeedViewPost.ReasonRepost
|
||||||
type PostView = AppBskyFeedPost.View
|
type PostView = AppBskyFeedPost.View
|
||||||
@@ -188,10 +190,8 @@ export class FeedModel {
|
|||||||
loadMoreCursor: string | undefined
|
loadMoreCursor: string | undefined
|
||||||
pollCursor: string | undefined
|
pollCursor: string | undefined
|
||||||
|
|
||||||
private _loadPromise: Promise<void> | undefined
|
// used to linearize async modifications to state
|
||||||
private _loadMorePromise: Promise<void> | undefined
|
private lock = new AwaitLock()
|
||||||
private _loadLatestPromise: Promise<void> | undefined
|
|
||||||
private _updatePromise: Promise<void> | undefined
|
|
||||||
|
|
||||||
// data
|
// data
|
||||||
feed: FeedItemModel[] = []
|
feed: FeedItemModel[] = []
|
||||||
@@ -270,23 +270,26 @@ export class FeedModel {
|
|||||||
/**
|
/**
|
||||||
* Load for first render
|
* Load for first render
|
||||||
*/
|
*/
|
||||||
async setup(isRefreshing = false) {
|
setup = bundleAsync(async (isRefreshing: boolean = false) => {
|
||||||
this.rootStore.log.debug('FeedModel:setup', {isRefreshing})
|
this.rootStore.log.debug('FeedModel:setup', {isRefreshing})
|
||||||
if (isRefreshing) {
|
if (isRefreshing) {
|
||||||
this.isRefreshing = true // set optimistically for UI
|
this.isRefreshing = true // set optimistically for UI
|
||||||
}
|
}
|
||||||
if (this._loadPromise) {
|
await this.lock.acquireAsync()
|
||||||
return this._loadPromise
|
|
||||||
}
|
|
||||||
await this._pendingWork()
|
|
||||||
this.setHasNewLatest(false)
|
|
||||||
this._loadPromise = this._initialLoad(isRefreshing)
|
|
||||||
try {
|
try {
|
||||||
await this._loadPromise
|
this.setHasNewLatest(false)
|
||||||
|
this._xLoading(isRefreshing)
|
||||||
|
try {
|
||||||
|
const res = await this._getFeed({limit: PAGE_SIZE})
|
||||||
|
await this._replaceAll(res)
|
||||||
|
this._xIdle()
|
||||||
|
} catch (e: any) {
|
||||||
|
this._xIdle(e)
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this._loadPromise = undefined
|
this.lock.release()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register any event listeners. Returns a cleanup function.
|
* Register any event listeners. Returns a cleanup function.
|
||||||
@@ -306,51 +309,93 @@ export class FeedModel {
|
|||||||
/**
|
/**
|
||||||
* Load more posts to the end of the feed
|
* Load more posts to the end of the feed
|
||||||
*/
|
*/
|
||||||
async loadMore() {
|
loadMore = bundleAsync(async () => {
|
||||||
if (this._loadMorePromise) {
|
await this.lock.acquireAsync()
|
||||||
return this._loadMorePromise
|
|
||||||
}
|
|
||||||
await this._pendingWork()
|
|
||||||
this._loadMorePromise = this._loadMore()
|
|
||||||
try {
|
try {
|
||||||
await this._loadMorePromise
|
if (!this.hasMore || this.hasError) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._xLoading()
|
||||||
|
try {
|
||||||
|
const res = await this._getFeed({
|
||||||
|
before: this.loadMoreCursor,
|
||||||
|
limit: PAGE_SIZE,
|
||||||
|
})
|
||||||
|
await this._appendAll(res)
|
||||||
|
this._xIdle()
|
||||||
|
} catch (e: any) {
|
||||||
|
this._xIdle() // don't bubble the error to the user
|
||||||
|
this.rootStore.log.error('FeedView: Failed to load more', {
|
||||||
|
params: this.params,
|
||||||
|
e,
|
||||||
|
})
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this._loadMorePromise = undefined
|
this.lock.release()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load more posts to the start of the feed
|
* Load more posts to the start of the feed
|
||||||
*/
|
*/
|
||||||
async loadLatest() {
|
loadLatest = bundleAsync(async () => {
|
||||||
if (this._loadLatestPromise) {
|
await this.lock.acquireAsync()
|
||||||
return this._loadLatestPromise
|
|
||||||
}
|
|
||||||
await this._pendingWork()
|
|
||||||
this.setHasNewLatest(false)
|
|
||||||
this._loadLatestPromise = this._loadLatest()
|
|
||||||
try {
|
try {
|
||||||
await this._loadLatestPromise
|
this.setHasNewLatest(false)
|
||||||
|
this._xLoading()
|
||||||
|
try {
|
||||||
|
const res = await this._getFeed({limit: PAGE_SIZE})
|
||||||
|
await this._prependAll(res)
|
||||||
|
this._xIdle()
|
||||||
|
} catch (e: any) {
|
||||||
|
this._xIdle() // don't bubble the error to the user
|
||||||
|
this.rootStore.log.error('FeedView: Failed to load latest', {
|
||||||
|
params: this.params,
|
||||||
|
e,
|
||||||
|
})
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this._loadLatestPromise = undefined
|
this.lock.release()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update content in-place
|
* Update content in-place
|
||||||
*/
|
*/
|
||||||
async update() {
|
update = bundleAsync(async () => {
|
||||||
if (this._updatePromise) {
|
await this.lock.acquireAsync()
|
||||||
return this._updatePromise
|
|
||||||
}
|
|
||||||
await this._pendingWork()
|
|
||||||
this._updatePromise = this._update()
|
|
||||||
try {
|
try {
|
||||||
await this._updatePromise
|
if (!this.feed.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._xLoading()
|
||||||
|
let numToFetch = this.feed.length
|
||||||
|
let cursor
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
const res: GetTimeline.Response = await this._getFeed({
|
||||||
|
before: cursor,
|
||||||
|
limit: Math.min(numToFetch, 100),
|
||||||
|
})
|
||||||
|
if (res.data.feed.length === 0) {
|
||||||
|
break // sanity check
|
||||||
|
}
|
||||||
|
this._updateAll(res)
|
||||||
|
numToFetch -= res.data.feed.length
|
||||||
|
cursor = res.data.cursor
|
||||||
|
} while (cursor && numToFetch > 0)
|
||||||
|
this._xIdle()
|
||||||
|
} catch (e: any) {
|
||||||
|
this._xIdle() // don't bubble the error to the user
|
||||||
|
this.rootStore.log.error('FeedView: Failed to update', {
|
||||||
|
params: this.params,
|
||||||
|
e,
|
||||||
|
})
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this._updatePromise = undefined
|
this.lock.release()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if new posts are available
|
* Check if new posts are available
|
||||||
@@ -359,7 +404,6 @@ export class FeedModel {
|
|||||||
if (this.hasNewLatest) {
|
if (this.hasNewLatest) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await this._pendingWork()
|
|
||||||
const res = await this._getFeed({limit: 1})
|
const res = await this._getFeed({limit: 1})
|
||||||
const currentLatestUri = this.pollCursor
|
const currentLatestUri = this.pollCursor
|
||||||
const receivedLatestUri = res.data.feed[0]
|
const receivedLatestUri = res.data.feed[0]
|
||||||
@@ -404,101 +448,9 @@ export class FeedModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// loader functions
|
// helper functions
|
||||||
// =
|
// =
|
||||||
|
|
||||||
private async _pendingWork() {
|
|
||||||
if (this._loadPromise) {
|
|
||||||
await this._loadPromise
|
|
||||||
}
|
|
||||||
if (this._loadMorePromise) {
|
|
||||||
await this._loadMorePromise
|
|
||||||
}
|
|
||||||
if (this._loadLatestPromise) {
|
|
||||||
await this._loadLatestPromise
|
|
||||||
}
|
|
||||||
if (this._updatePromise) {
|
|
||||||
await this._updatePromise
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _initialLoad(isRefreshing = false) {
|
|
||||||
this._xLoading(isRefreshing)
|
|
||||||
try {
|
|
||||||
const res = await this._getFeed({limit: PAGE_SIZE})
|
|
||||||
await this._replaceAll(res)
|
|
||||||
this._xIdle()
|
|
||||||
} catch (e: any) {
|
|
||||||
this._xIdle(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadLatest() {
|
|
||||||
this._xLoading()
|
|
||||||
try {
|
|
||||||
const res = await this._getFeed({limit: PAGE_SIZE})
|
|
||||||
await this._prependAll(res)
|
|
||||||
this._xIdle()
|
|
||||||
} catch (e: any) {
|
|
||||||
this._xIdle() // don't bubble the error to the user
|
|
||||||
this.rootStore.log.error('FeedView: Failed to load latest', {
|
|
||||||
params: this.params,
|
|
||||||
e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadMore() {
|
|
||||||
if (!this.hasMore || this.hasError) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this._xLoading()
|
|
||||||
try {
|
|
||||||
const res = await this._getFeed({
|
|
||||||
before: this.loadMoreCursor,
|
|
||||||
limit: PAGE_SIZE,
|
|
||||||
})
|
|
||||||
await this._appendAll(res)
|
|
||||||
this._xIdle()
|
|
||||||
} catch (e: any) {
|
|
||||||
this._xIdle() // don't bubble the error to the user
|
|
||||||
this.rootStore.log.error('FeedView: Failed to load more', {
|
|
||||||
params: this.params,
|
|
||||||
e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _update() {
|
|
||||||
if (!this.feed.length) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this._xLoading()
|
|
||||||
let numToFetch = this.feed.length
|
|
||||||
let cursor
|
|
||||||
try {
|
|
||||||
do {
|
|
||||||
const res: GetTimeline.Response = await this._getFeed({
|
|
||||||
before: cursor,
|
|
||||||
limit: Math.min(numToFetch, 100),
|
|
||||||
})
|
|
||||||
if (res.data.feed.length === 0) {
|
|
||||||
break // sanity check
|
|
||||||
}
|
|
||||||
this._updateAll(res)
|
|
||||||
numToFetch -= res.data.feed.length
|
|
||||||
cursor = res.data.cursor
|
|
||||||
} while (cursor && numToFetch > 0)
|
|
||||||
this._xIdle()
|
|
||||||
} catch (e: any) {
|
|
||||||
this._xIdle() // don't bubble the error to the user
|
|
||||||
this.rootStore.log.error('FeedView: Failed to update', {
|
|
||||||
params: this.params,
|
|
||||||
e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _replaceAll(
|
private async _replaceAll(
|
||||||
res: GetTimeline.Response | GetAuthorFeed.Response,
|
res: GetTimeline.Response | GetAuthorFeed.Response,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
AppBskyGraphAssertion,
|
AppBskyGraphAssertion,
|
||||||
AppBskyGraphFollow,
|
AppBskyGraphFollow,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import AwaitLock from 'await-lock'
|
||||||
|
import {bundleAsync} from '../../lib/async/bundle'
|
||||||
import {RootStoreModel} from './root-store'
|
import {RootStoreModel} from './root-store'
|
||||||
import {PostThreadViewModel} from './post-thread-view'
|
import {PostThreadViewModel} from './post-thread-view'
|
||||||
import {cleanError} from '../../lib/strings'
|
import {cleanError} from '../../lib/strings'
|
||||||
@@ -191,9 +193,8 @@ export class NotificationsViewModel {
|
|||||||
hasMore = true
|
hasMore = true
|
||||||
loadMoreCursor?: string
|
loadMoreCursor?: string
|
||||||
|
|
||||||
private _loadPromise: Promise<void> | undefined
|
// used to linearize async modifications to state
|
||||||
private _loadMorePromise: Promise<void> | undefined
|
private lock = new AwaitLock()
|
||||||
private _updatePromise: Promise<void> | undefined
|
|
||||||
|
|
||||||
// data
|
// data
|
||||||
notifications: NotificationsViewItemModel[] = []
|
notifications: NotificationsViewItemModel[] = []
|
||||||
@@ -250,19 +251,28 @@ export class NotificationsViewModel {
|
|||||||
/**
|
/**
|
||||||
* Load for first render
|
* Load for first render
|
||||||
*/
|
*/
|
||||||
async setup(isRefreshing = false) {
|
setup = bundleAsync(async (isRefreshing: boolean = false) => {
|
||||||
this.rootStore.log.debug('NotificationsModel:setup', {isRefreshing})
|
this.rootStore.log.debug('NotificationsModel:setup', {isRefreshing})
|
||||||
if (isRefreshing) {
|
if (isRefreshing) {
|
||||||
this.isRefreshing = true // set optimistically for UI
|
this.isRefreshing = true // set optimistically for UI
|
||||||
}
|
}
|
||||||
if (this._loadPromise) {
|
await this.lock.acquireAsync()
|
||||||
return this._loadPromise
|
try {
|
||||||
|
this._xLoading(isRefreshing)
|
||||||
|
try {
|
||||||
|
const params = Object.assign({}, this.params, {
|
||||||
|
limit: PAGE_SIZE,
|
||||||
|
})
|
||||||
|
const res = await this.rootStore.api.app.bsky.notification.list(params)
|
||||||
|
await this._replaceAll(res)
|
||||||
|
this._xIdle()
|
||||||
|
} catch (e: any) {
|
||||||
|
this._xIdle(e)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.lock.release()
|
||||||
}
|
}
|
||||||
await this._pendingWork()
|
})
|
||||||
this._loadPromise = this._initialLoad(isRefreshing)
|
|
||||||
await this._loadPromise
|
|
||||||
this._loadPromise = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset and load
|
* Reset and load
|
||||||
@@ -274,34 +284,71 @@ export class NotificationsViewModel {
|
|||||||
/**
|
/**
|
||||||
* Load more posts to the end of the notifications
|
* Load more posts to the end of the notifications
|
||||||
*/
|
*/
|
||||||
async loadMore() {
|
loadMore = bundleAsync(async () => {
|
||||||
if (this._loadMorePromise) {
|
if (!this.hasMore) {
|
||||||
return this._loadMorePromise
|
return
|
||||||
}
|
}
|
||||||
await this._pendingWork()
|
this.lock.acquireAsync()
|
||||||
this._loadMorePromise = this._loadMore()
|
|
||||||
try {
|
try {
|
||||||
await this._loadMorePromise
|
this._xLoading()
|
||||||
|
try {
|
||||||
|
const params = Object.assign({}, this.params, {
|
||||||
|
limit: PAGE_SIZE,
|
||||||
|
before: this.loadMoreCursor,
|
||||||
|
})
|
||||||
|
const res = await this.rootStore.api.app.bsky.notification.list(params)
|
||||||
|
await this._appendAll(res)
|
||||||
|
this._xIdle()
|
||||||
|
} catch (e: any) {
|
||||||
|
this._xIdle() // don't bubble the error to the user
|
||||||
|
this.rootStore.log.error('NotificationsView: Failed to load more', {
|
||||||
|
params: this.params,
|
||||||
|
e,
|
||||||
|
})
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this._loadMorePromise = undefined
|
this.lock.release()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update content in-place
|
* Update content in-place
|
||||||
*/
|
*/
|
||||||
async update() {
|
update = bundleAsync(async () => {
|
||||||
if (this._updatePromise) {
|
await this.lock.acquireAsync()
|
||||||
return this._updatePromise
|
|
||||||
}
|
|
||||||
await this._pendingWork()
|
|
||||||
this._updatePromise = this._update()
|
|
||||||
try {
|
try {
|
||||||
await this._updatePromise
|
if (!this.notifications.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._xLoading()
|
||||||
|
let numToFetch = this.notifications.length
|
||||||
|
let cursor
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
const res: ListNotifications.Response =
|
||||||
|
await this.rootStore.api.app.bsky.notification.list({
|
||||||
|
before: cursor,
|
||||||
|
limit: Math.min(numToFetch, 100),
|
||||||
|
})
|
||||||
|
if (res.data.notifications.length === 0) {
|
||||||
|
break // sanity check
|
||||||
|
}
|
||||||
|
this._updateAll(res)
|
||||||
|
numToFetch -= res.data.notifications.length
|
||||||
|
cursor = res.data.cursor
|
||||||
|
} while (cursor && numToFetch > 0)
|
||||||
|
this._xIdle()
|
||||||
|
} catch (e: any) {
|
||||||
|
this._xIdle() // don't bubble the error to the user
|
||||||
|
this.rootStore.log.error('NotificationsView: Failed to update', {
|
||||||
|
params: this.params,
|
||||||
|
e,
|
||||||
|
})
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this._updatePromise = undefined
|
this.lock.release()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update read/unread state
|
* Update read/unread state
|
||||||
@@ -356,88 +403,9 @@ export class NotificationsViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// loader functions
|
// helper functions
|
||||||
// =
|
// =
|
||||||
|
|
||||||
private async _pendingWork() {
|
|
||||||
if (this._loadPromise) {
|
|
||||||
await this._loadPromise
|
|
||||||
}
|
|
||||||
if (this._loadMorePromise) {
|
|
||||||
await this._loadMorePromise
|
|
||||||
}
|
|
||||||
if (this._updatePromise) {
|
|
||||||
await this._updatePromise
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _initialLoad(isRefreshing = false) {
|
|
||||||
this._xLoading(isRefreshing)
|
|
||||||
try {
|
|
||||||
const params = Object.assign({}, this.params, {
|
|
||||||
limit: PAGE_SIZE,
|
|
||||||
})
|
|
||||||
const res = await this.rootStore.api.app.bsky.notification.list(params)
|
|
||||||
await this._replaceAll(res)
|
|
||||||
this._xIdle()
|
|
||||||
} catch (e: any) {
|
|
||||||
this._xIdle(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadMore() {
|
|
||||||
if (!this.hasMore) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this._xLoading()
|
|
||||||
try {
|
|
||||||
const params = Object.assign({}, this.params, {
|
|
||||||
limit: PAGE_SIZE,
|
|
||||||
before: this.loadMoreCursor,
|
|
||||||
})
|
|
||||||
const res = await this.rootStore.api.app.bsky.notification.list(params)
|
|
||||||
await this._appendAll(res)
|
|
||||||
this._xIdle()
|
|
||||||
} catch (e: any) {
|
|
||||||
this._xIdle() // don't bubble the error to the user
|
|
||||||
this.rootStore.log.error('NotificationsView: Failed to load more', {
|
|
||||||
params: this.params,
|
|
||||||
e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _update() {
|
|
||||||
if (!this.notifications.length) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this._xLoading()
|
|
||||||
let numToFetch = this.notifications.length
|
|
||||||
let cursor
|
|
||||||
try {
|
|
||||||
do {
|
|
||||||
const res: ListNotifications.Response =
|
|
||||||
await this.rootStore.api.app.bsky.notification.list({
|
|
||||||
before: cursor,
|
|
||||||
limit: Math.min(numToFetch, 100),
|
|
||||||
})
|
|
||||||
if (res.data.notifications.length === 0) {
|
|
||||||
break // sanity check
|
|
||||||
}
|
|
||||||
this._updateAll(res)
|
|
||||||
numToFetch -= res.data.notifications.length
|
|
||||||
cursor = res.data.cursor
|
|
||||||
} while (cursor && numToFetch > 0)
|
|
||||||
this._xIdle()
|
|
||||||
} catch (e: any) {
|
|
||||||
this._xIdle() // don't bubble the error to the user
|
|
||||||
this.rootStore.log.error('NotificationsView: Failed to update', {
|
|
||||||
params: this.params,
|
|
||||||
e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _replaceAll(res: ListNotifications.Response) {
|
private async _replaceAll(res: ListNotifications.Response) {
|
||||||
if (res.data.notifications[0]) {
|
if (res.data.notifications[0]) {
|
||||||
this.mostRecentNotification = new NotificationsViewItemModel(
|
this.mostRecentNotification = new NotificationsViewItemModel(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {makeAutoObservable, runInAction} from 'mobx'
|
|||||||
import {AtUri} from '../../third-party/uri'
|
import {AtUri} from '../../third-party/uri'
|
||||||
import {AppBskyFeedGetRepostedBy as GetRepostedBy} from '@atproto/api'
|
import {AppBskyFeedGetRepostedBy as GetRepostedBy} from '@atproto/api'
|
||||||
import {RootStoreModel} from './root-store'
|
import {RootStoreModel} from './root-store'
|
||||||
|
import {bundleAsync} from '../../lib/async/bundle'
|
||||||
import {cleanError} from '../../lib/strings'
|
import {cleanError} from '../../lib/strings'
|
||||||
import * as apilib from '../lib/api'
|
import * as apilib from '../lib/api'
|
||||||
|
|
||||||
@@ -19,7 +20,6 @@ export class RepostedByViewModel {
|
|||||||
params: GetRepostedBy.QueryParams
|
params: GetRepostedBy.QueryParams
|
||||||
hasMore = true
|
hasMore = true
|
||||||
loadMoreCursor?: string
|
loadMoreCursor?: string
|
||||||
private _loadMorePromise: Promise<void> | undefined
|
|
||||||
|
|
||||||
// data
|
// data
|
||||||
uri: string = ''
|
uri: string = ''
|
||||||
@@ -59,55 +59,7 @@ export class RepostedByViewModel {
|
|||||||
return this.loadMore(true)
|
return this.loadMore(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadMore(isRefreshing = false) {
|
loadMore = bundleAsync(async (replace: boolean = false) => {
|
||||||
if (this._loadMorePromise) {
|
|
||||||
return this._loadMorePromise
|
|
||||||
}
|
|
||||||
this._loadMorePromise = this._load(isRefreshing)
|
|
||||||
try {
|
|
||||||
await this._loadMorePromise
|
|
||||||
} finally {
|
|
||||||
this._loadMorePromise = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// state transitions
|
|
||||||
// =
|
|
||||||
|
|
||||||
private _xLoading(isRefreshing = false) {
|
|
||||||
this.isLoading = true
|
|
||||||
this.isRefreshing = isRefreshing
|
|
||||||
this.error = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
private _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 reposted by view', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loader functions
|
|
||||||
// =
|
|
||||||
|
|
||||||
private 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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _load(replace = false) {
|
|
||||||
this._xLoading(replace)
|
this._xLoading(replace)
|
||||||
try {
|
try {
|
||||||
if (!this.resolvedUri) {
|
if (!this.resolvedUri) {
|
||||||
@@ -128,6 +80,42 @@ export class RepostedByViewModel {
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this._xIdle(e)
|
this._xIdle(e)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// state transitions
|
||||||
|
// =
|
||||||
|
|
||||||
|
private _xLoading(isRefreshing = false) {
|
||||||
|
this.isLoading = true
|
||||||
|
this.isRefreshing = isRefreshing
|
||||||
|
this.error = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
private _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 reposted by view', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
// =
|
||||||
|
|
||||||
|
private 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()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private _replaceAll(res: GetRepostedBy.Response) {
|
private _replaceAll(res: GetRepostedBy.Response) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {makeAutoObservable, runInAction} from 'mobx'
|
|||||||
import {AppBskyActorGetSuggestions as GetSuggestions} from '@atproto/api'
|
import {AppBskyActorGetSuggestions as GetSuggestions} from '@atproto/api'
|
||||||
import {RootStoreModel} from './root-store'
|
import {RootStoreModel} from './root-store'
|
||||||
import {cleanError} from '../../lib/strings'
|
import {cleanError} from '../../lib/strings'
|
||||||
|
import {bundleAsync} from '../../lib/async/bundle'
|
||||||
|
|
||||||
const PAGE_SIZE = 30
|
const PAGE_SIZE = 30
|
||||||
|
|
||||||
@@ -15,7 +16,6 @@ export class SuggestedActorsViewModel {
|
|||||||
error = ''
|
error = ''
|
||||||
hasMore = true
|
hasMore = true
|
||||||
loadMoreCursor?: string
|
loadMoreCursor?: string
|
||||||
private _loadMorePromise: Promise<void> | undefined
|
|
||||||
|
|
||||||
// data
|
// data
|
||||||
suggestions: SuggestedActor[] = []
|
suggestions: SuggestedActor[] = []
|
||||||
@@ -49,41 +49,7 @@ export class SuggestedActorsViewModel {
|
|||||||
return this.loadMore(true)
|
return this.loadMore(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadMore(isRefreshing = false) {
|
loadMore = bundleAsync(async (replace: boolean = false) => {
|
||||||
if (this._loadMorePromise) {
|
|
||||||
return this._loadMorePromise
|
|
||||||
}
|
|
||||||
this._loadMorePromise = this._load(isRefreshing)
|
|
||||||
try {
|
|
||||||
await this._loadMorePromise
|
|
||||||
} finally {
|
|
||||||
this._loadMorePromise = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// state transitions
|
|
||||||
// =
|
|
||||||
|
|
||||||
private _xLoading(isRefreshing = false) {
|
|
||||||
this.isLoading = true
|
|
||||||
this.isRefreshing = isRefreshing
|
|
||||||
this.error = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
private _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 suggested actors', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loader functions
|
|
||||||
// =
|
|
||||||
|
|
||||||
private async _load(replace = false) {
|
|
||||||
if (!replace && !this.hasMore) {
|
if (!replace && !this.hasMore) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -121,5 +87,24 @@ export class SuggestedActorsViewModel {
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this._xIdle(e)
|
this._xIdle(e)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// state transitions
|
||||||
|
// =
|
||||||
|
|
||||||
|
private _xLoading(isRefreshing = false) {
|
||||||
|
this.isLoading = true
|
||||||
|
this.isRefreshing = isRefreshing
|
||||||
|
this.error = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
private _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 suggested actors', err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {RootStoreModel} from './root-store'
|
import {RootStoreModel} from './root-store'
|
||||||
import {cleanError} from '../../lib/strings'
|
import {cleanError} from '../../lib/strings'
|
||||||
|
import {bundleAsync} from '../../lib/async/bundle'
|
||||||
|
|
||||||
const PAGE_SIZE = 30
|
const PAGE_SIZE = 30
|
||||||
|
|
||||||
@@ -19,7 +20,6 @@ export class UserFollowersViewModel {
|
|||||||
params: GetFollowers.QueryParams
|
params: GetFollowers.QueryParams
|
||||||
hasMore = true
|
hasMore = true
|
||||||
loadMoreCursor?: string
|
loadMoreCursor?: string
|
||||||
private _loadMorePromise: Promise<void> | undefined
|
|
||||||
|
|
||||||
// data
|
// data
|
||||||
subject: ActorRef.WithInfo = {
|
subject: ActorRef.WithInfo = {
|
||||||
@@ -63,17 +63,27 @@ export class UserFollowersViewModel {
|
|||||||
return this.loadMore(true)
|
return this.loadMore(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadMore(isRefreshing = false) {
|
loadMore = bundleAsync(async (replace: boolean = false) => {
|
||||||
if (this._loadMorePromise) {
|
if (!replace && !this.hasMore) {
|
||||||
return this._loadMorePromise
|
return
|
||||||
}
|
}
|
||||||
this._loadMorePromise = this._load(isRefreshing)
|
this._xLoading(replace)
|
||||||
try {
|
try {
|
||||||
await this._loadMorePromise
|
const params = Object.assign({}, this.params, {
|
||||||
} finally {
|
limit: PAGE_SIZE,
|
||||||
this._loadMorePromise = undefined
|
before: replace ? undefined : this.loadMoreCursor,
|
||||||
|
})
|
||||||
|
const res = await this.rootStore.api.app.bsky.graph.getFollowers(params)
|
||||||
|
if (replace) {
|
||||||
|
this._replaceAll(res)
|
||||||
|
} else {
|
||||||
|
this._appendAll(res)
|
||||||
|
}
|
||||||
|
this._xIdle()
|
||||||
|
} catch (e: any) {
|
||||||
|
this._xIdle(e)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
// state transitions
|
// state transitions
|
||||||
// =
|
// =
|
||||||
@@ -94,31 +104,9 @@ export class UserFollowersViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// loader functions
|
// helper functions
|
||||||
// =
|
// =
|
||||||
|
|
||||||
private async _load(replace = false) {
|
|
||||||
if (!replace && !this.hasMore) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this._xLoading(replace)
|
|
||||||
try {
|
|
||||||
const params = Object.assign({}, this.params, {
|
|
||||||
limit: PAGE_SIZE,
|
|
||||||
before: replace ? undefined : this.loadMoreCursor,
|
|
||||||
})
|
|
||||||
const res = await this.rootStore.api.app.bsky.graph.getFollowers(params)
|
|
||||||
if (replace) {
|
|
||||||
this._replaceAll(res)
|
|
||||||
} else {
|
|
||||||
this._appendAll(res)
|
|
||||||
}
|
|
||||||
this._xIdle()
|
|
||||||
} catch (e: any) {
|
|
||||||
this._xIdle(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _replaceAll(res: GetFollowers.Response) {
|
private _replaceAll(res: GetFollowers.Response) {
|
||||||
this.followers = []
|
this.followers = []
|
||||||
this._appendAll(res)
|
this._appendAll(res)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {RootStoreModel} from './root-store'
|
import {RootStoreModel} from './root-store'
|
||||||
import {cleanError} from '../../lib/strings'
|
import {cleanError} from '../../lib/strings'
|
||||||
|
import {bundleAsync} from '../../lib/async/bundle'
|
||||||
|
|
||||||
const PAGE_SIZE = 30
|
const PAGE_SIZE = 30
|
||||||
|
|
||||||
@@ -19,7 +20,6 @@ export class UserFollowsViewModel {
|
|||||||
params: GetFollows.QueryParams
|
params: GetFollows.QueryParams
|
||||||
hasMore = true
|
hasMore = true
|
||||||
loadMoreCursor?: string
|
loadMoreCursor?: string
|
||||||
private _loadMorePromise: Promise<void> | undefined
|
|
||||||
|
|
||||||
// data
|
// data
|
||||||
subject: ActorRef.WithInfo = {
|
subject: ActorRef.WithInfo = {
|
||||||
@@ -63,17 +63,27 @@ export class UserFollowsViewModel {
|
|||||||
return this.loadMore(true)
|
return this.loadMore(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadMore(isRefreshing = false) {
|
loadMore = bundleAsync(async (replace: boolean = false) => {
|
||||||
if (this._loadMorePromise) {
|
if (!replace && !this.hasMore) {
|
||||||
return this._loadMorePromise
|
return
|
||||||
}
|
}
|
||||||
this._loadMorePromise = this._load(isRefreshing)
|
this._xLoading(replace)
|
||||||
try {
|
try {
|
||||||
await this._loadMorePromise
|
const params = Object.assign({}, this.params, {
|
||||||
} finally {
|
limit: PAGE_SIZE,
|
||||||
this._loadMorePromise = undefined
|
before: replace ? undefined : this.loadMoreCursor,
|
||||||
|
})
|
||||||
|
const res = await this.rootStore.api.app.bsky.graph.getFollows(params)
|
||||||
|
if (replace) {
|
||||||
|
this._replaceAll(res)
|
||||||
|
} else {
|
||||||
|
this._appendAll(res)
|
||||||
|
}
|
||||||
|
this._xIdle()
|
||||||
|
} catch (e: any) {
|
||||||
|
this._xIdle(e)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
// state transitions
|
// state transitions
|
||||||
// =
|
// =
|
||||||
@@ -94,31 +104,9 @@ export class UserFollowsViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// loader functions
|
// helper functions
|
||||||
// =
|
// =
|
||||||
|
|
||||||
private async _load(replace = false) {
|
|
||||||
if (!replace && !this.hasMore) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this._xLoading(replace)
|
|
||||||
try {
|
|
||||||
const params = Object.assign({}, this.params, {
|
|
||||||
limit: PAGE_SIZE,
|
|
||||||
before: replace ? undefined : this.loadMoreCursor,
|
|
||||||
})
|
|
||||||
const res = await this.rootStore.api.app.bsky.graph.getFollows(params)
|
|
||||||
if (replace) {
|
|
||||||
this._replaceAll(res)
|
|
||||||
} else {
|
|
||||||
this._appendAll(res)
|
|
||||||
}
|
|
||||||
this._xIdle()
|
|
||||||
} catch (e: any) {
|
|
||||||
this._xIdle(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _replaceAll(res: GetFollows.Response) {
|
private _replaceAll(res: GetFollows.Response) {
|
||||||
this.follows = []
|
this.follows = []
|
||||||
this._appendAll(res)
|
this._appendAll(res)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {AtUri} from '../../third-party/uri'
|
|||||||
import {AppBskyFeedGetVotes as GetVotes} from '@atproto/api'
|
import {AppBskyFeedGetVotes as GetVotes} from '@atproto/api'
|
||||||
import {RootStoreModel} from './root-store'
|
import {RootStoreModel} from './root-store'
|
||||||
import {cleanError} from '../../lib/strings'
|
import {cleanError} from '../../lib/strings'
|
||||||
|
import {bundleAsync} from '../../lib/async/bundle'
|
||||||
import * as apilib from '../lib/api'
|
import * as apilib from '../lib/api'
|
||||||
|
|
||||||
const PAGE_SIZE = 30
|
const PAGE_SIZE = 30
|
||||||
@@ -19,7 +20,6 @@ export class VotesViewModel {
|
|||||||
params: GetVotes.QueryParams
|
params: GetVotes.QueryParams
|
||||||
hasMore = true
|
hasMore = true
|
||||||
loadMoreCursor?: string
|
loadMoreCursor?: string
|
||||||
private _loadMorePromise: Promise<void> | undefined
|
|
||||||
|
|
||||||
// data
|
// data
|
||||||
uri: string = ''
|
uri: string = ''
|
||||||
@@ -56,55 +56,7 @@ export class VotesViewModel {
|
|||||||
return this.loadMore(true)
|
return this.loadMore(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadMore(isRefreshing = false) {
|
loadMore = bundleAsync(async (replace: boolean = false) => {
|
||||||
if (this._loadMorePromise) {
|
|
||||||
return this._loadMorePromise
|
|
||||||
}
|
|
||||||
this._loadMorePromise = this._load(isRefreshing)
|
|
||||||
try {
|
|
||||||
await this._loadMorePromise
|
|
||||||
} finally {
|
|
||||||
this._loadMorePromise = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// state transitions
|
|
||||||
// =
|
|
||||||
|
|
||||||
private _xLoading(isRefreshing = false) {
|
|
||||||
this.isLoading = true
|
|
||||||
this.isRefreshing = isRefreshing
|
|
||||||
this.error = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
private _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 votes', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loader functions
|
|
||||||
// =
|
|
||||||
|
|
||||||
private 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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _load(replace = false) {
|
|
||||||
if (!replace && !this.hasMore) {
|
if (!replace && !this.hasMore) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -128,6 +80,42 @@ export class VotesViewModel {
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this._xIdle(e)
|
this._xIdle(e)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// state transitions
|
||||||
|
// =
|
||||||
|
|
||||||
|
private _xLoading(isRefreshing = false) {
|
||||||
|
this.isLoading = true
|
||||||
|
this.isRefreshing = isRefreshing
|
||||||
|
this.error = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
private _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 votes', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
// =
|
||||||
|
|
||||||
|
private 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()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private _replaceAll(res: GetVotes.Response) {
|
private _replaceAll(res: GetVotes.Response) {
|
||||||
|
|||||||
@@ -3712,6 +3712,11 @@ autoprefixer@^10.4.13:
|
|||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
postcss-value-parser "^4.2.0"
|
postcss-value-parser "^4.2.0"
|
||||||
|
|
||||||
|
await-lock@^2.2.2:
|
||||||
|
version "2.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef"
|
||||||
|
integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==
|
||||||
|
|
||||||
axe-core@^4.4.3:
|
axe-core@^4.4.3:
|
||||||
version "4.6.1"
|
version "4.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.6.1.tgz#79cccdee3e3ab61a8f42c458d4123a6768e6fbce"
|
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.6.1.tgz#79cccdee3e3ab61a8f42c458d4123a6768e6fbce"
|
||||||
|
|||||||
Reference in New Issue
Block a user