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
b62bc86dfe
commit
c9bffe4a52
|
@ -114,13 +114,12 @@ export namespace CommunityPermissions {
|
||||||
|
|
||||||
export interface CommunityDescription {
|
export interface CommunityDescription {
|
||||||
clock: bigint
|
clock: bigint
|
||||||
members: CommunityMember
|
members: Record<string, CommunityMember>
|
||||||
permissions: CommunityPermissions
|
permissions: CommunityPermissions
|
||||||
identity: ChatIdentity
|
identity: ChatIdentity
|
||||||
// fixme!: Map
|
chats: Record<string, CommunityChat>
|
||||||
chats: CommunityChat
|
|
||||||
banList: string[]
|
banList: string[]
|
||||||
categories: CommunityCategory
|
categories: Record<string, CommunityCategory>
|
||||||
archiveMagnetlinkClock: bigint
|
archiveMagnetlinkClock: bigint
|
||||||
adminSettings: CommunityAdminSettings
|
adminSettings: CommunityAdminSettings
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,12 @@ import { waku_message } from 'js-waku'
|
||||||
|
|
||||||
import { MessageType } from '~/protos/enums'
|
import { MessageType } from '~/protos/enums'
|
||||||
import { getDifferenceByKeys } from '~/src/helpers/get-difference-by-keys'
|
import { getDifferenceByKeys } from '~/src/helpers/get-difference-by-keys'
|
||||||
|
import { getObjectsDifference } from '~/src/helpers/get-objects-difference'
|
||||||
|
|
||||||
import { idToContentTopic } from '../../contentTopic'
|
import { idToContentTopic } from '../../contentTopic'
|
||||||
import { createSymKeyFromPassword } from '../../encryption'
|
import { createSymKeyFromPassword } from '../../encryption'
|
||||||
import { Chat } from '../chat'
|
import { Chat } from '../chat'
|
||||||
|
import { Member } from '../member'
|
||||||
|
|
||||||
import type { Client } from '../../client'
|
import type { Client } from '../../client'
|
||||||
import type {
|
import type {
|
||||||
|
@ -21,6 +23,7 @@ export class Community {
|
||||||
private symmetricKey!: Uint8Array
|
private symmetricKey!: Uint8Array
|
||||||
public description!: CommunityDescription
|
public description!: CommunityDescription
|
||||||
public chats: Map<string, Chat>
|
public chats: Map<string, Chat>
|
||||||
|
#members: Map<string, Member>
|
||||||
public callback: ((description: CommunityDescription) => void) | undefined
|
public callback: ((description: CommunityDescription) => void) | undefined
|
||||||
|
|
||||||
constructor(client: Client, publicKey: string) {
|
constructor(client: Client, publicKey: string) {
|
||||||
|
@ -28,6 +31,7 @@ export class Community {
|
||||||
this.publicKey = publicKey
|
this.publicKey = publicKey
|
||||||
|
|
||||||
this.chats = new Map()
|
this.chats = new Map()
|
||||||
|
this.#members = new Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
public async start() {
|
public async start() {
|
||||||
|
@ -52,6 +56,7 @@ export class Community {
|
||||||
this.description = description
|
this.description = description
|
||||||
|
|
||||||
this.observe()
|
this.observe()
|
||||||
|
this.addMembers(this.description.members)
|
||||||
|
|
||||||
// Chats
|
// Chats
|
||||||
await this.observeChatMessages(this.description.chats)
|
await this.observeChatMessages(this.description.chats)
|
||||||
|
@ -62,6 +67,18 @@ export class Community {
|
||||||
return [...this.chats.values()]
|
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 () => {
|
public fetch = async () => {
|
||||||
await this.client.waku.store.queryHistory([this.contentTopic], {
|
await this.client.waku.store.queryHistory([this.contentTopic], {
|
||||||
// oldest message first
|
// 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) => {
|
public handleDescription = (description: CommunityDescription) => {
|
||||||
if (this.description) {
|
if (this.description) {
|
||||||
// already handled
|
// already handled
|
||||||
|
@ -161,6 +191,23 @@ export class Community {
|
||||||
if (Object.keys(addedChats).length) {
|
if (Object.keys(addedChats).length) {
|
||||||
this.observeChatMessages(addedChats)
|
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
|
// 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 { createClient } from './client'
|
||||||
export type { ChatMessage as Message } from './client/chat'
|
export type { ChatMessage as Message } from './client/chat'
|
||||||
export type { Community } from './client/community/community'
|
export type { Community } from './client/community/community'
|
||||||
|
export type { Member } from './client/member'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as secp256k1 from 'ethereum-cryptography/secp256k1'
|
import * as secp256k1 from 'ethereum-cryptography/secp256k1'
|
||||||
|
|
||||||
type ColorHash = number[][]
|
export type ColorHash = number[][]
|
||||||
|
|
||||||
const COLOR_HASH_COLORS_COUNT = 32
|
const COLOR_HASH_COLORS_COUNT = 32
|
||||||
const COLOR_HASH_SEGMENT_MAX_LENGTH = 5
|
const COLOR_HASH_SEGMENT_MAX_LENGTH = 5
|
||||||
|
|
|
@ -9,8 +9,8 @@ import { MemberItem } from './member-item'
|
||||||
import { UserItem } from './user-item'
|
import { UserItem } from './user-item'
|
||||||
|
|
||||||
export function MemberSidebar() {
|
export function MemberSidebar() {
|
||||||
const members = useMembers()
|
|
||||||
const { account } = useAccount()
|
const { account } = useAccount()
|
||||||
|
const members = useMembers()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
|
@ -26,14 +26,12 @@ export function MemberSidebar() {
|
||||||
<MemberGroup label="Online">
|
<MemberGroup label="Online">
|
||||||
{members.map(member => (
|
{members.map(member => (
|
||||||
<MemberItem
|
<MemberItem
|
||||||
key={member}
|
key={member.publicKey}
|
||||||
verified={false}
|
verified={false}
|
||||||
untrustworthy={false}
|
untrustworthy={false}
|
||||||
indicator="online"
|
// indicator=""
|
||||||
chatKey={member}
|
member={member}
|
||||||
>
|
/>
|
||||||
{member}
|
|
||||||
</MemberItem>
|
|
||||||
))}
|
))}
|
||||||
</MemberGroup>
|
</MemberGroup>
|
||||||
{/* <MemberGroup label="Offline"></MemberGroup> */}
|
{/* <MemberGroup label="Offline"></MemberGroup> */}
|
||||||
|
|
|
@ -2,26 +2,27 @@ import React from 'react'
|
||||||
|
|
||||||
import { Avatar, EthAddress, Flex, Text } from '~/src/system'
|
import { Avatar, EthAddress, Flex, Text } from '~/src/system'
|
||||||
|
|
||||||
|
import type { Member } from '~/src/protocol'
|
||||||
import type { AvatarProps } from '~/src/system/avatar'
|
import type { AvatarProps } from '~/src/system/avatar'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: string
|
|
||||||
chatKey: string
|
|
||||||
verified: boolean
|
verified: boolean
|
||||||
untrustworthy: boolean
|
untrustworthy: boolean
|
||||||
indicator?: AvatarProps['indicator']
|
indicator?: AvatarProps['indicator']
|
||||||
|
member: Member
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MemberItem = (props: Props) => {
|
export const MemberItem = (props: Props) => {
|
||||||
const { children, chatKey, indicator, verified, untrustworthy } = props
|
const { member, indicator, verified, untrustworthy } = props
|
||||||
|
const { publicKey, username, colorHash } = member
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap="2" align="center" css={{ height: 56 }}>
|
<Flex gap="2" align="center" css={{ height: 56 }}>
|
||||||
<Avatar size={32} indicator={indicator} />
|
<Avatar size={32} indicator={indicator} colorHash={colorHash} />
|
||||||
<div>
|
<div>
|
||||||
<Flex align="center" gap={1}>
|
<Flex align="center" gap={1}>
|
||||||
<Text size="15" color="accent" truncate>
|
<Text size="15" color="accent" truncate>
|
||||||
{children}
|
{username}
|
||||||
</Text>
|
</Text>
|
||||||
{verified && (
|
{verified && (
|
||||||
<svg
|
<svg
|
||||||
|
@ -59,7 +60,7 @@ export const MemberItem = (props: Props) => {
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<EthAddress size={10} color="gray">
|
<EthAddress size={10} color="gray">
|
||||||
{chatKey}
|
{publicKey}
|
||||||
</EthAddress>
|
</EthAddress>
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
|
@ -3,4 +3,5 @@ export { useAccount } from './use-account'
|
||||||
export type { Chat } from './use-chat'
|
export type { Chat } from './use-chat'
|
||||||
export { useChat } from './use-chat'
|
export { useChat } from './use-chat'
|
||||||
export { useChats } from './use-chats'
|
export { useChats } from './use-chats'
|
||||||
|
export type { Member } from './use-members'
|
||||||
export { useMembers } from './use-members'
|
export { useMembers } from './use-members'
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { useProtocol } from '~/src/protocol'
|
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[] => {
|
return client.community.members
|
||||||
const { community } = useProtocol()
|
|
||||||
|
|
||||||
return Object.keys(community.members)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type { Member }
|
||||||
|
|
Loading…
Reference in New Issue