Add avatar images and fix some type signatures

This commit is contained in:
Paul Frazee 2022-12-08 13:45:23 -06:00
parent 273e6d2973
commit 539bf5d350
56 changed files with 543 additions and 370 deletions

View file

@ -105,21 +105,6 @@ export async function unfollow(store: RootStoreModel, followUri: string) {
})
}
export async function updateProfile(
store: RootStoreModel,
did: string,
modifyFn: (existing?: Profile.Record) => Profile.Record,
) {
const res = await store.api.app.bsky.actor.profile.list({
user: did || '',
})
const existing = res.records[0]
await store.api.app.bsky.actor.updateProfile({
did: did || '',
...modifyFn(existing?.value),
})
}
export async function inviteToScene(
store: RootStoreModel,
sceneDid: string,
@ -183,6 +168,14 @@ async function fetchHandler(
const reqMimeType = reqHeaders['Content-Type'] || reqHeaders['content-type']
if (reqMimeType && reqMimeType.startsWith('application/json')) {
reqBody = JSON.stringify(reqBody)
} else if (
typeof reqBody === 'string' &&
(reqBody.startsWith('/') || reqBody.startsWith('file:'))
) {
// NOTE
// React native treats bodies with {uri: string} as file uploads to pull from cache
// -prf
reqBody = {uri: reqBody}
}
const controller = new AbortController()
@ -219,51 +212,4 @@ async function fetchHandler(
headers: resHeaders,
body: resBody,
}
// const res = await fetch(httpUri, {
// method: httpMethod,
// headers: httpHeaders,
// body: encodeMethodCallBody(httpHeaders, httpReqBody),
// })
// const resBody = await res.arrayBuffer()
// return {
// status: res.status,
// headers: Object.fromEntries(res.headers.entries()),
// body: httpResponseBodyParse(res.headers.get('content-type'), resBody),
// }
}
/*type WherePred = (_record: GetRecordResponseValidated) => Boolean
async function deleteWhere(
coll: AdxRepoCollectionClient,
schema: SchemaOpt,
cond: WherePred,
) {
const toDelete: string[] = []
await iterateAll(coll, schema, record => {
if (cond(record)) {
toDelete.push(record.key)
}
})
for (const key of toDelete) {
await coll.del(key)
}
return toDelete.length
}
type IterateAllCb = (_record: GetRecordResponseValidated) => void
async function iterateAll(
coll: AdxRepoCollectionClient,
schema: SchemaOpt,
cb: IterateAllCb,
) {
let cursor
let res: ListRecordsResponseValidated
do {
res = await coll.list(schema, {after: cursor, limit: 100})
for (const record of res.records) {
if (record.valid) {
cb(record)
cursor = record.key
}
}
} while (res.records.length === 100)
}*/

View file

@ -1,4 +0,0 @@
export interface Declaration {
cid: string
actorType: string
}

View file

@ -1,6 +1,7 @@
import {makeAutoObservable, runInAction} from 'mobx'
import {Record as PostRecord} from '../../third-party/api/src/client/types/app/bsky/feed/post'
import * as GetTimeline from '../../third-party/api/src/client/types/app/bsky/feed/getTimeline'
import * as ActorRef from '../../third-party/api/src/client/types/app/bsky/actor/ref'
import * as GetAuthorFeed from '../../third-party/api/src/client/types/app/bsky/feed/getAuthorFeed'
import {PostThreadViewModel} from './post-thread-view'
import {AtUri} from '../../third-party/uri'
@ -36,14 +37,15 @@ export class FeedItemModel implements GetTimeline.FeedItem {
// data
uri: string = ''
cid: string = ''
author: GetTimeline.Actor = {
author: ActorRef.WithInfo = {
did: '',
handle: '',
displayName: '',
declaration: {cid: '', actorType: ''},
avatar: undefined,
}
repostedBy?: GetTimeline.Actor
trendedBy?: GetTimeline.Actor
repostedBy?: ActorRef.WithInfo
trendedBy?: ActorRef.WithInfo
record: Record<string, unknown> = {}
replyCount: number = 0
repostCount: number = 0

View file

@ -9,6 +9,7 @@ export class MeModel {
handle: string = ''
displayName: string = ''
description: string = ''
avatar: string = ''
notificationCount: number = 0
memberships?: MembershipsViewModel
notifications: NotificationsViewModel
@ -27,6 +28,7 @@ export class MeModel {
this.handle = ''
this.displayName = ''
this.description = ''
this.avatar = ''
this.notificationCount = 0
this.memberships = undefined
}
@ -37,12 +39,13 @@ export class MeModel {
handle: this.handle,
displayName: this.displayName,
description: this.description,
avatar: this.avatar,
}
}
hydrate(v: unknown) {
if (isObj(v)) {
let did, handle, displayName, description
let did, handle, displayName, description, avatar
if (hasProp(v, 'did') && typeof v.did === 'string') {
did = v.did
}
@ -55,11 +58,15 @@ export class MeModel {
if (hasProp(v, 'description') && typeof v.description === 'string') {
description = v.description
}
if (hasProp(v, 'avatar') && typeof v.avatar === 'string') {
avatar = v.avatar
}
if (did && handle) {
this.did = did
this.handle = handle
this.displayName = displayName || ''
this.description = description || ''
this.avatar = avatar || ''
}
}
}
@ -76,9 +83,11 @@ export class MeModel {
if (profile?.data) {
this.displayName = profile.data.displayName || ''
this.description = profile.data.description || ''
this.avatar = profile.data.avatar || ''
} else {
this.displayName = ''
this.description = ''
this.avatar = ''
}
})
this.memberships = new MembershipsViewModel(this.rootStore, {

View file

@ -1,11 +1,11 @@
import {makeAutoObservable, runInAction} from 'mobx'
import * as GetMembers from '../../third-party/api/src/client/types/app/bsky/graph/getMembers'
import * as ActorRef from '../../third-party/api/src/client/types/app/bsky/actor/ref'
import {APP_BSKY_GRAPH} from '../../third-party/api'
import {AtUri} from '../../third-party/uri'
import {RootStoreModel} from './root-store'
type Subject = GetMembers.OutputSchema['subject']
export type MemberItem = GetMembers.OutputSchema['members'][number] & {
export type MemberItem = GetMembers.Member & {
_reactKey: string
}
@ -18,11 +18,12 @@ export class MembersViewModel {
params: GetMembers.QueryParams
// data
subject: Subject = {
subject: ActorRef.WithInfo = {
did: '',
handle: '',
displayName: '',
declaration: {cid: '', actorType: ''},
avatar: undefined,
}
members: MemberItem[] = []
@ -129,6 +130,7 @@ export class MembersViewModel {
this.subject.handle = res.data.subject.handle
this.subject.displayName = res.data.subject.displayName
this.subject.declaration = res.data.subject.declaration
this.subject.avatar = res.data.subject.avatar
this.members.length = 0
let counter = 0
for (const item of res.data.members) {

View file

@ -1,12 +1,11 @@
import {makeAutoObservable} from 'mobx'
import * as GetMemberships from '../../third-party/api/src/client/types/app/bsky/graph/getMemberships'
import * as ActorRef from '../../third-party/api/src/client/types/app/bsky/actor/ref'
import {RootStoreModel} from './root-store'
type Subject = GetMemberships.OutputSchema['subject']
export type MembershipItem =
GetMemberships.OutputSchema['memberships'][number] & {
_reactKey: string
}
export type MembershipItem = GetMemberships.Membership & {
_reactKey: string
}
export class MembershipsViewModel {
// state
@ -17,11 +16,12 @@ export class MembershipsViewModel {
params: GetMemberships.QueryParams
// data
subject: Subject = {
subject: ActorRef.WithInfo = {
did: '',
handle: '',
displayName: '',
declaration: {cid: '', actorType: ''},
avatar: undefined,
}
memberships: MembershipItem[] = []
@ -107,6 +107,8 @@ export class MembershipsViewModel {
this.subject.did = res.data.subject.did
this.subject.handle = res.data.subject.handle
this.subject.displayName = res.data.subject.displayName
this.subject.declaration = res.data.subject.declaration
this.subject.avatar = res.data.subject.avatar
this.memberships.length = 0
let counter = 0
for (const item of res.data.memberships) {

View file

@ -1,8 +1,8 @@
import {makeAutoObservable, runInAction} from 'mobx'
import * as ListNotifications from '../../third-party/api/src/client/types/app/bsky/notification/list'
import * as ActorRef from '../../third-party/api/src/client/types/app/bsky/actor/ref'
import {RootStoreModel} from './root-store'
import {PostThreadViewModel} from './post-thread-view'
import {Declaration} from './_common'
import {hasProp} from '../lib/type-guards'
import {APP_BSKY_GRAPH} from '../../third-party/api'
import {cleanError} from '../../lib/strings'
@ -22,12 +22,11 @@ export class NotificationsViewItemModel implements GroupedNotification {
// data
uri: string = ''
cid: string = ''
author: {
did: string
handle: string
displayName?: string
declaration: Declaration
} = {did: '', handle: '', declaration: {cid: '', actorType: ''}}
author: ActorRef.WithInfo = {
did: '',
handle: '',
declaration: {cid: '', actorType: ''},
}
reason: string = ''
reasonSubject?: string
record: any = {}

View file

@ -1,10 +1,20 @@
import {makeAutoObservable, runInAction} from 'mobx'
import {AppBskyFeedGetPostThread as GetPostThread} from '../../third-party/api'
import * as Embed from '../../third-party/api/src/client/types/app/bsky/feed/embed'
import * as ActorRef from '../../third-party/api/src/client/types/app/bsky/actor/ref'
import {AtUri} from '../../third-party/uri'
import _omit from 'lodash.omit'
import {RootStoreModel} from './root-store'
import * as apilib from '../lib/api'
type MaybePost =
| GetPostThread.Post
| GetPostThread.NotFoundPost
| {
$type: string
[k: string]: unknown
}
function* reactKeyGenerator(): Generator<string> {
let counter = 0
while (true) {
@ -16,6 +26,7 @@ interface ReplyingTo {
author: {
handle: string
displayName?: string
avatar?: string
}
text: string
}
@ -40,19 +51,16 @@ export class PostThreadViewPostModel implements GetPostThread.Post {
_isHighlightedPost = false
// data
$type: string = ''
uri: string = ''
cid: string = ''
author: GetPostThread.User = {
author: ActorRef.WithInfo = {
did: '',
handle: '',
displayName: '',
declaration: {cid: '', actorType: ''},
}
record: Record<string, unknown> = {}
embed?:
| GetPostThread.RecordEmbed
| GetPostThread.ExternalEmbed
| GetPostThread.UnknownEmbed
embed?: Embed.Main = undefined
parent?: PostThreadViewPostModel
replyCount: number = 0
replies?: PostThreadViewPostModel[]
@ -106,6 +114,7 @@ export class PostThreadViewPostModel implements GetPostThread.Post {
author: {
handle: v.parent.author.handle,
displayName: v.parent.author.displayName,
avatar: v.parent.author.avatar,
},
text: (v.parent.record as OriginalRecord).text,
}
@ -331,17 +340,30 @@ export class PostThreadViewModel {
const thread = new PostThreadViewPostModel(
this.rootStore,
keyGen.next().value,
res.data.thread,
res.data.thread as GetPostThread.Post,
)
thread._isHighlightedPost = true
thread.assignTreeModels(keyGen, res.data.thread)
thread.assignTreeModels(keyGen, res.data.thread as GetPostThread.Post)
this.thread = thread
}
}
function sortThread(post: GetPostThread.Post) {
function sortThread(post: MaybePost) {
if (post.notFound) {
return
}
post = post as GetPostThread.Post
if (post.replies) {
post.replies.sort((a: GetPostThread.Post, b: GetPostThread.Post) => {
post.replies.sort((a: MaybePost, b: MaybePost) => {
post = post as GetPostThread.Post
if (a.notFound) {
return 1
}
if (b.notFound) {
return -1
}
a = a as GetPostThread.Post
b = b as GetPostThread.Post
const aIsByOp = a.author.did === post.author.did
const bIsByOp = b.author.did === post.author.did
if (aIsByOp && bIsByOp) {

View file

@ -4,8 +4,6 @@ import {AtUri} from '../../third-party/uri'
import {RootStoreModel} from './root-store'
import {cleanError} from '../../lib/strings'
export type PostEntities = Post.Record['entities']
export type PostReply = Post.Record['reply']
type RemoveIndex<T> = {
[P in keyof T as string extends P
? never
@ -22,8 +20,8 @@ export class PostModel implements RemoveIndex<Post.Record> {
// data
text: string = ''
entities?: PostEntities
reply?: PostReply
entities?: Post.Entity[]
reply?: Post.ReplyRef
createdAt: string = ''
constructor(public rootStore: RootStoreModel, uri: string) {

View file

@ -1,9 +1,10 @@
import {makeAutoObservable, runInAction} from 'mobx'
import {Image as PickedImage} from 'react-native-image-crop-picker'
import * as GetProfile from '../../third-party/api/src/client/types/app/bsky/actor/getProfile'
import * as Profile from '../../third-party/api/src/client/types/app/bsky/actor/profile'
import {Main as DeclRef} from '../../third-party/api/src/client/types/app/bsky/system/declRef'
import {Entity} from '../../third-party/api/src/client/types/app/bsky/feed/post'
import {extractEntities} from '../../lib/strings'
import {Declaration} from './_common'
import {RootStoreModel} from './root-store'
import * as apilib from '../lib/api'
@ -30,13 +31,14 @@ export class ProfileViewModel {
// data
did: string = ''
handle: string = ''
declaration: Declaration = {
declaration: DeclRef = {
cid: '',
actorType: '',
}
creator: string = ''
displayName?: string
description?: string
avatar?: string
followersCount: number = 0
followsCount: number = 0
membersCount: number = 0
@ -44,7 +46,6 @@ export class ProfileViewModel {
myState = new ProfileViewMyStateModel()
// TODO TEMP data to be implemented in the protocol
userAvatar: string | null = null
userBanner: string | null = null
// added data
@ -120,15 +121,27 @@ export class ProfileViewModel {
}
async updateProfile(
fn: (existing?: Profile.Record) => Profile.Record,
userAvatar: string | null, // TODO TEMP
updates: Profile.Record,
newUserAvatar: PickedImage | undefined,
userBanner: string | null, // TODO TEMP
) {
// TODO TEMP add userBanner & userAvatar in the protocol when suported
this.userAvatar = userAvatar
// TODO TEMP add userBanner to the protocol when suported
this.userBanner = userBanner
await apilib.updateProfile(this.rootStore, this.did, fn)
if (newUserAvatar) {
const res = await this.rootStore.api.com.atproto.blob.upload(
newUserAvatar.path, // this will be special-cased by the fetch monkeypatch in /src/state/lib/api.ts
{
encoding: newUserAvatar.mime,
},
)
updates.avatar = {
cid: res.data.cid,
mimeType: newUserAvatar.mime,
}
}
await this.rootStore.api.app.bsky.actor.updateProfile(updates)
await this.rootStore.me.load()
await this.refresh()
}
@ -173,6 +186,7 @@ export class ProfileViewModel {
this.creator = res.data.creator
this.displayName = res.data.displayName
this.description = res.data.description
this.avatar = res.data.avatar
this.followersCount = res.data.followersCount
this.followsCount = res.data.followsCount
this.membersCount = res.data.membersCount

View file

@ -1,12 +1,10 @@
import {makeAutoObservable, runInAction} from 'mobx'
import {AtUri} from '../../third-party/uri'
import * as GetRepostedBy from '../../third-party/api/src/client/types/app/bsky/feed/getRepostedBy'
import {Main as DeclRef} from '../../third-party/api/src/client/types/app/bsky/system/declRef'
import {RootStoreModel} from './root-store'
import {Declaration} from './_common'
type RepostedByItem = GetRepostedBy.OutputSchema['repostedBy'][number]
export class RepostedByViewItemModel implements RepostedByItem {
export class RepostedByViewItemModel implements GetRepostedBy.RepostedBy {
// ui state
_reactKey: string = ''
@ -14,7 +12,8 @@ export class RepostedByViewItemModel implements RepostedByItem {
did: string = ''
handle: string = ''
displayName: string = ''
declaration: Declaration = {cid: '', actorType: ''}
avatar?: string
declaration: DeclRef = {cid: '', actorType: ''}
createdAt?: string
indexedAt: string = ''

View file

@ -30,7 +30,6 @@ export class SessionModel {
rootStore: false,
serialize: false,
hydrate: false,
_connectPromise: false,
})
}

View file

@ -58,6 +58,7 @@ export interface ComposerOptsPostRef {
author: {
handle: string
displayName?: string
avatar?: string
}
}
export interface ComposerOpts {

View file

@ -2,8 +2,7 @@ import {makeAutoObservable} from 'mobx'
import * as GetSuggestions from '../../third-party/api/src/client/types/app/bsky/actor/getSuggestions'
import {RootStoreModel} from './root-store'
type ResponseSuggestedActor = GetSuggestions.OutputSchema['actors'][number]
export type SuggestedActor = ResponseSuggestedActor & {
export type SuggestedActor = GetSuggestions.Actor & {
_reactKey: string
}

View file

@ -11,8 +11,8 @@ export class UserAutocompleteViewModel {
_searchPromise: Promise<any> | undefined
// data
follows: GetFollows.OutputSchema['follows'] = []
searchRes: SearchTypeahead.OutputSchema['users'] = []
follows: GetFollows.Follow[] = []
searchRes: SearchTypeahead.User[] = []
knownHandles: Set<string> = new Set()
constructor(public rootStore: RootStoreModel) {
@ -34,11 +34,13 @@ export class UserAutocompleteViewModel {
return this.searchRes.map(user => ({
handle: user.handle,
displayName: user.displayName,
avatar: user.avatar,
}))
}
return this.follows.map(follow => ({
handle: follow.handle,
displayName: follow.displayName,
avatar: follow.avatar,
}))
}

View file

@ -1,9 +1,9 @@
import {makeAutoObservable} from 'mobx'
import * as GetFollowers from '../../third-party/api/src/client/types/app/bsky/graph/getFollowers'
import * as ActorRef from '../../third-party/api/src/client/types/app/bsky/actor/ref'
import {RootStoreModel} from './root-store'
type Subject = GetFollowers.OutputSchema['subject']
export type FollowerItem = GetFollowers.OutputSchema['followers'][number] & {
export type FollowerItem = GetFollowers.Follower & {
_reactKey: string
}
@ -16,10 +16,9 @@ export class UserFollowersViewModel {
params: GetFollowers.QueryParams
// data
subject: Subject = {
subject: ActorRef.WithInfo = {
did: '',
handle: '',
displayName: '',
declaration: {cid: '', actorType: ''},
}
followers: FollowerItem[] = []
@ -102,6 +101,7 @@ export class UserFollowersViewModel {
this.subject.did = res.data.subject.did
this.subject.handle = res.data.subject.handle
this.subject.displayName = res.data.subject.displayName
this.subject.avatar = res.data.subject.avatar
this.followers.length = 0
let counter = 0
for (const item of res.data.followers) {

View file

@ -1,9 +1,9 @@
import {makeAutoObservable} from 'mobx'
import * as GetFollows from '../../third-party/api/src/client/types/app/bsky/graph/getFollows'
import * as ActorRef from '../../third-party/api/src/client/types/app/bsky/actor/ref'
import {RootStoreModel} from './root-store'
type Subject = GetFollows.OutputSchema['subject']
export type FollowItem = GetFollows.OutputSchema['follows'][number] & {
export type FollowItem = GetFollows.Follow & {
_reactKey: string
}
@ -16,10 +16,9 @@ export class UserFollowsViewModel {
params: GetFollows.QueryParams
// data
subject: Subject = {
subject: ActorRef.WithInfo = {
did: '',
handle: '',
displayName: '',
declaration: {cid: '', actorType: ''},
}
follows: FollowItem[] = []
@ -102,6 +101,7 @@ export class UserFollowsViewModel {
this.subject.did = res.data.subject.did
this.subject.handle = res.data.subject.handle
this.subject.displayName = res.data.subject.displayName
this.subject.avatar = res.data.subject.avatar
this.follows.length = 0
let counter = 0
for (const item of res.data.follows) {

View file

@ -1,11 +1,10 @@
import {makeAutoObservable, runInAction} from 'mobx'
import {AtUri} from '../../third-party/uri'
import * as GetVotes from '../../third-party/api/src/client/types/app/bsky/feed/getVotes'
import * as ActorRef from '../../third-party/api/src/client/types/app/bsky/actor/ref'
import {RootStoreModel} from './root-store'
type VoteItem = GetVotes.OutputSchema['votes'][number]
export class VotesViewItemModel implements VoteItem {
export class VotesViewItemModel implements GetVotes.Vote {
// ui state
_reactKey: string = ''
@ -13,9 +12,13 @@ export class VotesViewItemModel implements VoteItem {
direction: 'up' | 'down' = 'up'
indexedAt: string = ''
createdAt: string = ''
actor: GetVotes.Actor = {did: '', handle: ''}
actor: ActorRef.WithInfo = {
did: '',
handle: '',
declaration: {cid: '', actorType: ''},
}
constructor(reactKey: string, v: VoteItem) {
constructor(reactKey: string, v: GetVotes.Vote) {
makeAutoObservable(this)
this._reactKey = reactKey
Object.assign(this, v)
@ -127,7 +130,7 @@ export class VotesViewModel {
}
}
private _append(keyId: number, item: VoteItem) {
private _append(keyId: number, item: GetVotes.Vote) {
this.votes.push(new VotesViewItemModel(`item-${keyId}`, item))
}
}