Add members (#271)
* export ColorHash type * add getObjectsDifference helper * fix CommunityDescription generated type * add members to community module * use members in sidebar
This commit is contained in:
parent
062c29d6fa
commit
5b4daedd37
|
@ -114,13 +114,12 @@ export namespace CommunityPermissions {
|
|||
|
||||
export interface CommunityDescription {
|
||||
clock: bigint
|
||||
members: CommunityMember
|
||||
members: Record<string, CommunityMember>
|
||||
permissions: CommunityPermissions
|
||||
identity: ChatIdentity
|
||||
// fixme!: Map
|
||||
chats: CommunityChat
|
||||
chats: Record<string, CommunityChat>
|
||||
banList: string[]
|
||||
categories: CommunityCategory
|
||||
categories: Record<string, CommunityCategory>
|
||||
archiveMagnetlinkClock: bigint
|
||||
adminSettings: CommunityAdminSettings
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@ import { waku_message } from 'js-waku'
|
|||
|
||||
import { MessageType } from '~/protos/enums'
|
||||
import { getDifferenceByKeys } from '~/src/helpers/get-difference-by-keys'
|
||||
import { getObjectsDifference } from '~/src/helpers/get-objects-difference'
|
||||
|
||||
import { idToContentTopic } from '../../contentTopic'
|
||||
import { createSymKeyFromPassword } from '../../encryption'
|
||||
import { Chat } from '../chat'
|
||||
import { Member } from '../member'
|
||||
|
||||
import type { Client } from '../../client'
|
||||
import type {
|
||||
|
@ -21,6 +23,7 @@ export class Community {
|
|||
private symmetricKey!: Uint8Array
|
||||
public description!: CommunityDescription
|
||||
public chats: Map<string, Chat>
|
||||
#members: Map<string, Member>
|
||||
public callback: ((description: CommunityDescription) => void) | undefined
|
||||
|
||||
constructor(client: Client, publicKey: string) {
|
||||
|
@ -28,6 +31,7 @@ export class Community {
|
|||
this.publicKey = publicKey
|
||||
|
||||
this.chats = new Map()
|
||||
this.#members = new Map()
|
||||
}
|
||||
|
||||
public async start() {
|
||||
|
@ -52,6 +56,7 @@ export class Community {
|
|||
this.description = description
|
||||
|
||||
this.observe()
|
||||
this.addMembers(this.description.members)
|
||||
|
||||
// Chats
|
||||
await this.observeChatMessages(this.description.chats)
|
||||
|
@ -62,6 +67,18 @@ export class Community {
|
|||
return [...this.chats.values()]
|
||||
}
|
||||
|
||||
public getChat(uuid: string) {
|
||||
return this.chats.get(uuid)
|
||||
}
|
||||
|
||||
public get members() {
|
||||
return [...this.#members.values()]
|
||||
}
|
||||
|
||||
public getMember(publicKey: string) {
|
||||
return this.#members.get(publicKey)
|
||||
}
|
||||
|
||||
public fetch = async () => {
|
||||
await this.client.waku.store.queryHistory([this.contentTopic], {
|
||||
// oldest message first
|
||||
|
@ -137,6 +154,19 @@ export class Community {
|
|||
)
|
||||
}
|
||||
|
||||
private addMembers = (members: CommunityDescription['members']) => {
|
||||
for (const publicKey of Object.keys(members)) {
|
||||
const member = new Member(publicKey)
|
||||
this.#members.set(publicKey, member)
|
||||
}
|
||||
}
|
||||
|
||||
private removeMembers = (ids: string[]) => {
|
||||
for (const id of ids) {
|
||||
this.#members.delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
public handleDescription = (description: CommunityDescription) => {
|
||||
if (this.description) {
|
||||
// already handled
|
||||
|
@ -161,6 +191,23 @@ export class Community {
|
|||
if (Object.keys(addedChats).length) {
|
||||
this.observeChatMessages(addedChats)
|
||||
}
|
||||
|
||||
// TODO: migrate chats to new format
|
||||
// const chats = getObjectsDifference(
|
||||
// this.description.chats,
|
||||
// description.chats
|
||||
// )
|
||||
|
||||
// this.observeChatMessages(chats.added)
|
||||
// this.unobserveChatMessages(chats.removed)
|
||||
|
||||
const members = getObjectsDifference(
|
||||
this.description.members,
|
||||
description.members
|
||||
)
|
||||
|
||||
this.addMembers(members.added)
|
||||
this.removeMembers(members.removed)
|
||||
}
|
||||
|
||||
// Community
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { generateUsername } from '../utils/generate-username'
|
||||
import { publicKeyToColorHash } from '../utils/public-key-to-color-hash'
|
||||
|
||||
import type { ColorHash } from '../utils/public-key-to-color-hash'
|
||||
|
||||
export class Member {
|
||||
publicKey: string
|
||||
username: string
|
||||
colorHash: ColorHash
|
||||
|
||||
constructor(publicKey: string) {
|
||||
this.publicKey = publicKey
|
||||
this.username = generateUsername(publicKey)
|
||||
// TODO: can it fail?
|
||||
this.colorHash = publicKeyToColorHash(publicKey)!
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { getObjectsDifference } from './get-objects-difference'
|
||||
|
||||
describe('getObjectsDifference', () => {
|
||||
it('returns correct difference', () => {
|
||||
const oldObject = { a: 1, b: 2, c: 3 }
|
||||
const newObject = { c: 3, d: 4, e: 5 }
|
||||
|
||||
expect(getObjectsDifference(oldObject, newObject)).toEqual({
|
||||
added: {
|
||||
d: 4,
|
||||
e: 5,
|
||||
},
|
||||
removed: ['a', 'b'],
|
||||
})
|
||||
})
|
||||
|
||||
it('returns empty arrays for the same object', () => {
|
||||
const object = { a: 1, b: 2, c: 3 }
|
||||
|
||||
expect(getObjectsDifference(object, object)).toEqual({
|
||||
added: {},
|
||||
removed: [],
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,23 @@
|
|||
type Input<Value> = Record<string, Value>
|
||||
|
||||
export function getObjectsDifference<Value>(
|
||||
oldObject: Input<Value>,
|
||||
newObject: Input<Value>
|
||||
) {
|
||||
const added: Record<string, Value> = {}
|
||||
const removed: string[] = []
|
||||
|
||||
for (const key of Object.keys(oldObject)) {
|
||||
if (!newObject[key]) {
|
||||
removed.push(key)
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of Object.keys(newObject)) {
|
||||
if (!oldObject[key]) {
|
||||
added[key] = newObject[key]
|
||||
}
|
||||
}
|
||||
|
||||
return { added, removed }
|
||||
}
|
|
@ -3,3 +3,4 @@ export type { Client, ClientOptions } from './client'
|
|||
export { createClient } from './client'
|
||||
export type { ChatMessage as Message } from './client/chat'
|
||||
export type { Community } from './client/community/community'
|
||||
export type { Member } from './client/member'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as secp256k1 from 'ethereum-cryptography/secp256k1'
|
||||
|
||||
type ColorHash = number[][]
|
||||
export type ColorHash = number[][]
|
||||
|
||||
const COLOR_HASH_COLORS_COUNT = 32
|
||||
const COLOR_HASH_SEGMENT_MAX_LENGTH = 5
|
||||
|
|
|
@ -9,8 +9,8 @@ import { MemberItem } from './member-item'
|
|||
import { UserItem } from './user-item'
|
||||
|
||||
export function MemberSidebar() {
|
||||
const members = useMembers()
|
||||
const { account } = useAccount()
|
||||
const members = useMembers()
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
|
@ -26,14 +26,12 @@ export function MemberSidebar() {
|
|||
<MemberGroup label="Online">
|
||||
{members.map(member => (
|
||||
<MemberItem
|
||||
key={member}
|
||||
key={member.publicKey}
|
||||
verified={false}
|
||||
untrustworthy={false}
|
||||
indicator="online"
|
||||
chatKey={member}
|
||||
>
|
||||
{member}
|
||||
</MemberItem>
|
||||
// indicator=""
|
||||
member={member}
|
||||
/>
|
||||
))}
|
||||
</MemberGroup>
|
||||
{/* <MemberGroup label="Offline"></MemberGroup> */}
|
||||
|
|
|
@ -2,26 +2,27 @@ import React from 'react'
|
|||
|
||||
import { Avatar, EthAddress, Flex, Text } from '~/src/system'
|
||||
|
||||
import type { Member } from '~/src/protocol'
|
||||
import type { AvatarProps } from '~/src/system/avatar'
|
||||
|
||||
interface Props {
|
||||
children: string
|
||||
chatKey: string
|
||||
verified: boolean
|
||||
untrustworthy: boolean
|
||||
indicator?: AvatarProps['indicator']
|
||||
member: Member
|
||||
}
|
||||
|
||||
export const MemberItem = (props: Props) => {
|
||||
const { children, chatKey, indicator, verified, untrustworthy } = props
|
||||
const { member, indicator, verified, untrustworthy } = props
|
||||
const { publicKey, username, colorHash } = member
|
||||
|
||||
return (
|
||||
<Flex gap="2" align="center" css={{ height: 56 }}>
|
||||
<Avatar size={32} indicator={indicator} />
|
||||
<Avatar size={32} indicator={indicator} colorHash={colorHash} />
|
||||
<div>
|
||||
<Flex align="center" gap={1}>
|
||||
<Text size="15" color="accent" truncate>
|
||||
{children}
|
||||
{username}
|
||||
</Text>
|
||||
{verified && (
|
||||
<svg
|
||||
|
@ -59,7 +60,7 @@ export const MemberItem = (props: Props) => {
|
|||
)}
|
||||
</Flex>
|
||||
<EthAddress size={10} color="gray">
|
||||
{chatKey}
|
||||
{publicKey}
|
||||
</EthAddress>
|
||||
</div>
|
||||
</Flex>
|
||||
|
|
|
@ -3,4 +3,5 @@ export { useAccount } from './use-account'
|
|||
export type { Chat } from './use-chat'
|
||||
export { useChat } from './use-chat'
|
||||
export { useChats } from './use-chats'
|
||||
export type { Member } from './use-members'
|
||||
export { useMembers } from './use-members'
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { useProtocol } from '~/src/protocol'
|
||||
|
||||
import type { Community } from '@status-im/js'
|
||||
import type { Member } from '@status-im/js'
|
||||
|
||||
export type Member = Community['description']['members'][0]
|
||||
// todo: remove in favor of useCommunity
|
||||
export const useMembers = (): Member[] => {
|
||||
const { client } = useProtocol()
|
||||
|
||||
export const useMembers = (): string[] => {
|
||||
const { community } = useProtocol()
|
||||
|
||||
return Object.keys(community.members)
|
||||
return client.community.members
|
||||
}
|
||||
|
||||
export type { Member }
|
||||
|
|
Loading…
Reference in New Issue