Add 'add to list' tool
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import {makeAutoObservable} from 'mobx'
|
||||
import {AtUri, AppBskyGraphListitem} from '@atproto/api'
|
||||
import {runInAction} from 'mobx'
|
||||
import {RootStoreModel} from '../root-store'
|
||||
|
||||
const PAGE_SIZE = 100
|
||||
interface Membership {
|
||||
uri: string
|
||||
value: AppBskyGraphListitem.Record
|
||||
}
|
||||
|
||||
export class ListMembershipModel {
|
||||
// data
|
||||
memberships: Membership[] = []
|
||||
|
||||
constructor(public rootStore: RootStoreModel, public subject: string) {
|
||||
makeAutoObservable(
|
||||
this,
|
||||
{
|
||||
rootStore: false,
|
||||
},
|
||||
{autoBind: true},
|
||||
)
|
||||
}
|
||||
|
||||
// public api
|
||||
// =
|
||||
|
||||
async fetch() {
|
||||
// NOTE
|
||||
// this approach to determining list membership is too inefficient to work at any scale
|
||||
// it needs to be replaced with server side list membership queries
|
||||
// -prf
|
||||
let cursor
|
||||
let records = []
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const res = await this.rootStore.agent.app.bsky.graph.listitem.list({
|
||||
repo: this.rootStore.me.did,
|
||||
cursor,
|
||||
limit: PAGE_SIZE,
|
||||
})
|
||||
records = records.concat(
|
||||
res.records.filter(record => record.value.subject === this.subject),
|
||||
)
|
||||
cursor = res.cursor
|
||||
if (!cursor) {
|
||||
break
|
||||
}
|
||||
}
|
||||
runInAction(() => {
|
||||
this.memberships = records
|
||||
})
|
||||
}
|
||||
|
||||
getMembership(listUri: string) {
|
||||
return this.memberships.find(m => m.value.list === listUri)
|
||||
}
|
||||
|
||||
isMember(listUri: string) {
|
||||
return !!this.getMembership(listUri)
|
||||
}
|
||||
|
||||
async add(listUri: string) {
|
||||
if (this.isMember(listUri)) {
|
||||
return
|
||||
}
|
||||
const res = await this.rootStore.agent.app.bsky.graph.listitem.create(
|
||||
{
|
||||
repo: this.rootStore.me.did,
|
||||
},
|
||||
{
|
||||
subject: this.subject,
|
||||
list: listUri,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
)
|
||||
const {rkey} = new AtUri(res.uri)
|
||||
const record = await this.rootStore.agent.app.bsky.graph.listitem.get({
|
||||
repo: this.rootStore.me.did,
|
||||
rkey,
|
||||
})
|
||||
runInAction(() => {
|
||||
this.memberships = this.memberships.concat([record])
|
||||
})
|
||||
}
|
||||
|
||||
async remove(listUri: string) {
|
||||
const membership = this.getMembership(listUri)
|
||||
if (!membership) {
|
||||
return
|
||||
}
|
||||
const {rkey} = new AtUri(membership.uri)
|
||||
await this.rootStore.agent.app.bsky.graph.listitem.delete({
|
||||
repo: this.rootStore.me.did,
|
||||
rkey,
|
||||
})
|
||||
runInAction(() => {
|
||||
this.memberships = this.memberships.filter(m => m.value.list !== listUri)
|
||||
})
|
||||
}
|
||||
|
||||
async updateTo(uris: string) {
|
||||
for (const uri of uris) {
|
||||
await this.add(uri)
|
||||
}
|
||||
for (const membership of this.memberships) {
|
||||
if (!uris.includes(membership.value.list)) {
|
||||
await this.remove(membership.value.list)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
import {makeAutoObservable} from 'mobx'
|
||||
import {
|
||||
AppBskyGraphGetList as GetList,
|
||||
AppBskyActorDefs,
|
||||
AppBskyGraphDefs,
|
||||
AppBskyGraphDefs as GraphDefs,
|
||||
AppBskyGraphList,
|
||||
AppBskyRichtextFacet,
|
||||
RichText,
|
||||
} from '@atproto/api'
|
||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||
import {RootStoreModel} from '../root-store'
|
||||
import * as apilib from 'lib/api/index'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {bundleAsync} from 'lib/async/bundle'
|
||||
|
||||
const PAGE_SIZE = 30
|
||||
|
||||
export class ListModel {
|
||||
// state
|
||||
@@ -18,21 +18,13 @@ export class ListModel {
|
||||
isRefreshing = false
|
||||
hasLoaded = false
|
||||
error = ''
|
||||
params: GetList.QueryParams
|
||||
loadMoreError = ''
|
||||
hasMore = true
|
||||
loadMoreCursor?: string
|
||||
|
||||
// data
|
||||
uri: string
|
||||
creator: AppBskyActorDefs.ProfileView
|
||||
name: string
|
||||
purpose: AppBskyGraphDefs.ListPurpose
|
||||
description?: string
|
||||
descriptionFacets?: AppBskyRichtextFacet.Main[]
|
||||
avatar?: string
|
||||
viewer?: AppBskyGraphDefs.ListViewerState
|
||||
indexedAt?: string
|
||||
|
||||
// added data
|
||||
descriptionRichText?: RichText = new RichText({text: ''})
|
||||
list: GraphDefs.ListView | null = null
|
||||
items: GraphDefs.ListItemView[] = []
|
||||
|
||||
static async createModList(
|
||||
rootStore: RootStoreModel,
|
||||
@@ -65,20 +57,18 @@ export class ListModel {
|
||||
)
|
||||
}
|
||||
|
||||
constructor(public rootStore: RootStoreModel, params: GetList.QueryParams) {
|
||||
constructor(public rootStore: RootStoreModel, public uri: string) {
|
||||
makeAutoObservable(
|
||||
this,
|
||||
{
|
||||
rootStore: false,
|
||||
params: false,
|
||||
},
|
||||
{autoBind: true},
|
||||
)
|
||||
this.params = params
|
||||
}
|
||||
|
||||
get hasContent() {
|
||||
return this.uri !== ''
|
||||
return this.items.length > 0
|
||||
}
|
||||
|
||||
get hasError() {
|
||||
@@ -92,12 +82,39 @@ export class ListModel {
|
||||
// public api
|
||||
// =
|
||||
|
||||
async setup() {
|
||||
await this._load()
|
||||
async refresh() {
|
||||
return this.loadMore(true)
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
await this._load(true)
|
||||
loadMore = bundleAsync(async (replace: boolean = false) => {
|
||||
if (!replace && !this.hasMore) {
|
||||
return
|
||||
}
|
||||
this._xLoading(replace)
|
||||
try {
|
||||
const res = await this.rootStore.agent.app.bsky.graph.getList({
|
||||
list: this.uri,
|
||||
limit: PAGE_SIZE,
|
||||
cursor: replace ? undefined : this.loadMoreCursor,
|
||||
})
|
||||
if (replace) {
|
||||
this._replaceAll(res)
|
||||
} else {
|
||||
this._appendAll(res)
|
||||
}
|
||||
this._xIdle()
|
||||
} catch (e: any) {
|
||||
this._xIdle(replace ? e : undefined, !replace ? e : undefined)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Attempt to load more again after a failure
|
||||
*/
|
||||
async retryLoadMore() {
|
||||
this.loadMoreError = ''
|
||||
this.hasMore = true
|
||||
return this.loadMore()
|
||||
}
|
||||
|
||||
// state transitions
|
||||
@@ -109,43 +126,34 @@ export class ListModel {
|
||||
this.error = ''
|
||||
}
|
||||
|
||||
_xIdle(err?: any) {
|
||||
_xIdle(err?: any, loadMoreErr?: any) {
|
||||
this.isLoading = false
|
||||
this.isRefreshing = false
|
||||
this.hasLoaded = true
|
||||
this.error = cleanError(err)
|
||||
this.loadMoreError = cleanError(loadMoreErr)
|
||||
if (err) {
|
||||
this.rootStore.log.error('Failed to fetch profile', err)
|
||||
this.rootStore.log.error('Failed to fetch user items', err)
|
||||
}
|
||||
if (loadMoreErr) {
|
||||
this.rootStore.log.error('Failed to fetch user items', loadMoreErr)
|
||||
}
|
||||
}
|
||||
|
||||
// loader functions
|
||||
// helper functions
|
||||
// =
|
||||
|
||||
async _load(isRefreshing = false) {
|
||||
this._xLoading(isRefreshing)
|
||||
try {
|
||||
const res = await this.rootStore.agent.app.bsky.graph.getList(this.params)
|
||||
this._replaceAll(res)
|
||||
this._xIdle()
|
||||
} catch (e: any) {
|
||||
this._xIdle(e)
|
||||
}
|
||||
_replaceAll(res: GetList.Response) {
|
||||
this.items = []
|
||||
this._appendAll(res)
|
||||
}
|
||||
|
||||
_replaceAll(res: GetList.Response) {
|
||||
this.uri = res.data.list.uri
|
||||
this.creator = res.data.list.creator
|
||||
this.name = res.data.list.name
|
||||
this.purpose = res.data.list.purpose
|
||||
this.description = res.data.list.description
|
||||
this.descriptionFacets = res.data.list.descriptionFacets
|
||||
this.avatar = res.data.list.avatar
|
||||
this.viewer = res.data.list.viewer
|
||||
this.indexedAt = res.data.list.indexedAt
|
||||
this.descriptionRichText = new RichText({
|
||||
text: this.description || '',
|
||||
facets: this.descriptionFacets,
|
||||
})
|
||||
_appendAll(res: GetList.Response) {
|
||||
this.loadMoreCursor = res.data.cursor
|
||||
this.hasMore = !!this.loadMoreCursor
|
||||
this.list = res.data.list
|
||||
this.items = this.items.concat(
|
||||
res.data.items.map(item => ({...item, _reactKey: item.subject})),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,12 @@ export interface CreateMuteListModal {
|
||||
onCreate?: (uri: string) => void
|
||||
}
|
||||
|
||||
export interface ListAddUserModal {
|
||||
name: 'list-add-user'
|
||||
subject: string
|
||||
displayName: string
|
||||
}
|
||||
|
||||
export interface CropImageModal {
|
||||
name: 'crop-image'
|
||||
uri: string
|
||||
@@ -104,6 +110,7 @@ export type Modal =
|
||||
| ReportAccountModal
|
||||
| ReportPostModal
|
||||
| CreateMuteListModal
|
||||
| ListAddUserModal
|
||||
|
||||
// Posts
|
||||
| AltTextImageModal
|
||||
|
||||
Reference in New Issue
Block a user