connect UI to protocol

This commit is contained in:
Pavel Prichodko 2022-06-10 16:11:32 +02:00 committed by Felicio Mununga
parent 65d9c33bb8
commit d0f4104b53
No known key found for this signature in database
GPG Key ID: 0EB8D75C775AB6F1
28 changed files with 319 additions and 306 deletions

View File

@ -1,3 +1,4 @@
// @ts-nocheck
import React, { useMemo, useRef, useState } from 'react'
import styled from 'styled-components'

View File

@ -1,3 +1,4 @@
// @ts-nocheck
import React, { useMemo, useState } from 'react'
import styled from 'styled-components'

View File

@ -1,3 +1,4 @@
// @ts-nocheck
import React, { useEffect, useMemo, useRef, useState } from 'react'
import styled from 'styled-components'

View File

@ -2,42 +2,42 @@ import React from 'react'
import { BellIcon } from '~/src/icons/bell-icon'
import { ContextMenu, DropdownMenu } from '~/src/system'
import { useAlertDialog } from '~/src/system/dialog/alert-dialog'
import { useDialog } from '~/src/system/dialog/dialog'
// import { useAlertDialog } from '~/src/system/dialog/alert-dialog'
// import { useDialog } from '~/src/system/dialog/dialog'
import { UserProfileDialog } from '../user-profile-dialog'
import { EditGroupChatDialog } from './edit-group-chat-dialog'
// import { UserProfileDialog } from '../user-profile-dialog'
// import { EditGroupChatDialog } from './edit-group-chat-dialog'
interface Props {
type: 'dropdown' | 'context'
chatType: 'channel' | 'chat' | 'group-chat'
// chatType: 'channel' | 'chat' | 'group-chat'
}
export const ChatMenu = (props: Props) => {
const { type, chatType } = props
const { type } = props
const Menu = type === 'dropdown' ? DropdownMenu : ContextMenu
const userProfileDialog = useDialog(UserProfileDialog)
const editGroupChatDialog = useDialog(EditGroupChatDialog)
// const userProfileDialog = useDialog(UserProfileDialog)
// const editGroupChatDialog = useDialog(EditGroupChatDialog)
const deleteChatDialog = useAlertDialog({
title: 'Delete Chat',
description: 'Are you sure you want to delete this chat?',
actionLabel: 'Delete',
actionVariant: 'danger',
cancelLabel: 'Keep',
})
const leaveGroupDialog = useAlertDialog({
title: 'Leave Group',
description: 'Are you sure you want to leave this group chat?',
actionLabel: 'Leave',
actionVariant: 'danger',
cancelLabel: 'Stay',
})
// const deleteChatDialog = useAlertDialog({
// title: 'Delete Chat',
// description: 'Are you sure you want to delete this chat?',
// actionLabel: 'Delete',
// actionVariant: 'danger',
// cancelLabel: 'Keep',
// })
// const leaveGroupDialog = useAlertDialog({
// title: 'Leave Group',
// description: 'Are you sure you want to leave this group chat?',
// actionLabel: 'Leave',
// actionVariant: 'danger',
// cancelLabel: 'Stay',
// })
const commonMenuItems = (
<>
return (
<Menu>
<Menu.TriggerItem label="Mute Chat" icon={<BellIcon />}>
<Menu.Item>For 15 min</Menu.Item>
<Menu.Item>For 1 hour</Menu.Item>
@ -52,55 +52,55 @@ export const ChatMenu = (props: Props) => {
<Menu.Item>Last 3 days</Menu.Item>
<Menu.Item>Last 7 days</Menu.Item>
</Menu.TriggerItem>
</>
)
if (chatType === 'channel') {
return <Menu>{commonMenuItems}</Menu>
}
if (chatType === 'group-chat') {
return (
<Menu>
<Menu.Item icon={<BellIcon />}>Add / remove from group</Menu.Item>
<Menu.Item
icon={<BellIcon />}
onSelect={() => editGroupChatDialog.open({})}
>
Edit name and image
</Menu.Item>
<Menu.Separator />
{commonMenuItems}
<Menu.Separator />
<Menu.Item
icon={<BellIcon />}
danger
onSelect={() => leaveGroupDialog.open()}
>
Leave Chat
</Menu.Item>
</Menu>
)
}
return (
<Menu>
<Menu.Item
icon={<BellIcon />}
onSelect={() => userProfileDialog.open({ name: 'Satoshi' })}
>
View Profile
</Menu.Item>
<Menu.Separator />
{commonMenuItems}
<Menu.Separator />
<Menu.Item
icon={<BellIcon />}
danger
onSelect={() => deleteChatDialog.open()}
>
Delete Chat
</Menu.Item>
</Menu>
)
// if (chatType === 'channel') {
// return <Menu>{commonMenuItems}</Menu>
// }
// if (chatType === 'group-chat') {
// return (
// <Menu>
// <Menu.Item icon={<BellIcon />}>Add / remove from group</Menu.Item>
// <Menu.Item
// icon={<BellIcon />}
// onSelect={() => editGroupChatDialog.open({})}
// >
// Edit name and image
// </Menu.Item>
// <Menu.Separator />
// {commonMenuItems}
// <Menu.Separator />
// <Menu.Item
// icon={<BellIcon />}
// danger
// onSelect={() => leaveGroupDialog.open()}
// >
// Leave Chat
// </Menu.Item>
// </Menu>
// )
// }
// return (
// <Menu>
// <Menu.Item
// icon={<BellIcon />}
// onSelect={() => userProfileDialog.open({ name: 'Satoshi' })}
// >
// View Profile
// </Menu.Item>
// <Menu.Separator />
// {commonMenuItems}
// <Menu.Separator />
// <Menu.Item
// icon={<BellIcon />}
// danger
// onSelect={() => deleteChatDialog.open()}
// >
// Delete Chat
// </Menu.Item>
// </Menu>
// )
}

View File

@ -17,7 +17,7 @@ export const ChannelItem = (props: Props) => {
return (
<ContextMenuTrigger>
<SidebarItem {...sidebarItemProps}>#{children}</SidebarItem>
<ChatMenu type="context" chatType="channel" />
<ChatMenu type="context" />
</ContextMenuTrigger>
)
}

View File

@ -1,43 +1,28 @@
import React from 'react'
import { Box } from '~/src/system'
import { useChats } from '~/src/protocol'
import { Box } from '~/src/system'
import { ChannelGroup } from './channel-group'
// import { ChannelGroup } from './channel-group'
import { ChannelItem } from './channel-item'
export const Channels = () => {
const chats = useChats()
return (
<Box css={{padding:'8px 0'}}>
{chats.map((chat) => (
<Box css={{ padding: '24px 0', overflow: 'auto' }}>
{chats.map(chat => (
<ChannelItem
key={chat.id}
to={`/${chat.id}`}
unread={false}
muted={false}
name={chat.identity?.displayName}
color={chat.identity?.color}
>
{chat.identity!.displayName}
</ChannelItem>
))}
{/* {Object.entries(community.chats).map(([group, channels]) => (
<ChannelGroup key={group} name={group}>
{channels.map(channel => (
<ChannelItem
key={group + channel}
to={`/${channel}`}
unread={channel === 'general'}
muted={channel === 'random'}
>
{channel}
</ChannelItem>
))}
</ChannelGroup>
))} */}
</Box>
)
}

View File

@ -1,12 +1,11 @@
import React from 'react'
import { useCommunity } from '~/src/protocol'
import { useProtocol } from '~/src/protocol'
import { Button, CopyInput, Dialog, Flex, Grid, Text } from '~/src/system'
export const CommunityDialog = () => {
const { identity, publicKey='0xTODO' } = useCommunity()
const { displayName, description} = identity
const { community } = useProtocol()
const { displayName, description } = community.identity!
return (
<Dialog title={displayName}>
@ -16,7 +15,7 @@ export const CommunityDialog = () => {
<Dialog.Separator />
<Dialog.Body>
<Grid gap={3}>
<CopyInput label="Community Public Key" value={publicKey} />
<CopyInput label="Community Public Key" value="0xTODO" />
<Text size="13" color="gray">
To access this community, paste community public key in Status
desktop or mobile app.
@ -54,7 +53,7 @@ export const CommunityDialog = () => {
fill="currentColor"
/>
</svg>
<Button href="https://status.im/get">Download Status for Mac</Button>
<Button href="https://status.im/get">Download Status</Button>
</Flex>
</Dialog.Body>
</Dialog>

View File

@ -1,22 +1,23 @@
import React from 'react'
import { useCommunity, useMembers } from '~/src/protocol'
import { useMembers, useProtocol } from '~/src/protocol'
import { styled } from '~/src/styles/config'
import { Avatar, DialogTrigger, Text } from '~/src/system'
import { CommunityDialog } from './community-dialog'
export const CommunityInfo = () => {
const community = useCommunity()
const { community } = useProtocol()
const members = useMembers()
console.log("file: index.tsx > line 11 > CommunityInfo > community", community)
const { displayName, color } = community.identity!
return (
<DialogTrigger>
<Button>
<Avatar size={36} />
<Avatar size={36} name={displayName} color={color} />
<div>
<Text>{community.identity?.displayName}</Text>
<Text>{displayName}</Text>
<Text color="gray" size={12}>
{members.length} members
</Text>

View File

@ -19,10 +19,10 @@ export const GetStarted = () => {
// TODO: Add skip logic
}
const [account, { createAccount }] = useAccount()
const { account, createAccount } = useAccount()
return (
<Flex direction="column" align="center" gap={5}>
<Flex direction="column" align="center" gap={5} css={{ padding: '30px 0' }}>
<svg
width={65}
height={64}

View File

@ -10,7 +10,7 @@ interface Props {
export const ThrowawayProfileFoundDialog = (props: Props) => {
const { onSkip } = props
const [account] = useAccount()
const { account } = useAccount()
const handleLoadThrowawayProfile = () => {
// TODO: load throwaway profile
@ -24,8 +24,8 @@ if (!account) {
<Dialog title="Throwaway Profile Found">
<Dialog.Body gap="5">
<Flex direction="column" align="center" gap="2">
<Avatar size={64} src={account.imageUrl} />
<Heading weight="600">{account.name}</Heading>
<Avatar size={64} />
<Heading weight="600">{account.username}</Heading>
<Text color="gray">
Chatkey: 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377
</Text>

View File

@ -1,7 +1,6 @@
import React from 'react'
import { ChatMenu } from '~/src/components/chat-menu'
import { useChannel } from '~/src/protocol'
import { ContextMenuTrigger } from '~/src/system'
import { SidebarItem } from '../sidebar-item'
@ -15,12 +14,10 @@ interface Props extends SidebarItemProps {
export const ChatItem = (props: Props) => {
const { children, ...sidebarItemProps } = props
const chat = useChannel(children)
return (
<ContextMenuTrigger>
<SidebarItem {...sidebarItemProps}>{children}</SidebarItem>
<ChatMenu type="context" chatType={chat.type} />
<ChatMenu type="context" />
</ContextMenuTrigger>
)
}

View File

@ -1,3 +1,4 @@
// @ts-nocheck
import React from 'react'
import { EditIcon } from '~/src/icons/edit-icon'

View File

@ -12,10 +12,12 @@ interface Props {
muted: boolean
unread: boolean
children: React.ReactNode
name?: string
color?: string
}
const SidebarItem = (props: Props, ref: Ref<HTMLAnchorElement>) => {
const { muted, unread, children, ...buttonProps } = props
const { muted, unread, children, name, color, ...buttonProps } = props
return (
<Link
@ -23,7 +25,7 @@ const SidebarItem = (props: Props, ref: Ref<HTMLAnchorElement>) => {
state={muted ? 'muted' : unread ? 'unread' : undefined}
{...buttonProps}
>
<Avatar size={24} />
<Avatar size={24} name={name} color={color} />
{children}
</Link>
)

View File

@ -1,25 +1,23 @@
import React from 'react'
import { useAppState } from '~/src/contexts/app-context'
import { useAccount } from '~/src/protocol'
import { styled } from '~/src/styles/config'
import { Separator } from '~/src/system'
import { Channels } from './components/channels'
import { CommunityInfo } from './components/community-info'
import { GetStarted } from './components/get-started'
import { useAccount } from '~/src/protocol'
// import { Messages } from './components/messages'
export const MainSidebar = () => {
const { options } = useAppState()
const { account } = useAccount()
if (options.enableSidebar === false) {
return null
}
const [account] = useAccount()
return (
<Wrapper>
<CommunityInfo />
@ -28,7 +26,7 @@ export const MainSidebar = () => {
<Messages /> */}
{!account && (
<>
<Separator css={{ margin: '16px 0' }} />
<Separator />
<GetStarted />
</>
)}

View File

@ -3,25 +3,32 @@ import React from 'react'
import { useAccount } from '~/src/protocol'
import { Avatar, Dialog, EmojiHash, Flex, Heading, Text } from '~/src/system'
export const DisconnectDialog = () => {
const [account] = useAccount()
import type { Account } from '~/src/protocol'
interface Props {
account: Account
}
export const DisconnectDialog = (props: Props) => {
const { deleteAccount } = useAccount()
const { account } = props
return (
<Dialog title="Disconnect">
<Dialog.Body gap="5">
<Text>Do you want to disconnect your profile from this browser?</Text>
<Flex direction="column" align="center" gap="2">
<Avatar size={64} src={account.imageUrl} />
<Heading weight="600">{account.name}</Heading>
<Text color="gray">
Chatkey: {account.chatKey}
</Text>
<Avatar size={64} />
<Heading weight="600">{account.username}</Heading>
<Text color="gray">Chatkey: {account.chatKey}</Text>
<EmojiHash />
</Flex>
</Dialog.Body>
<Dialog.Actions>
<Dialog.Cancel>Stay Connected</Dialog.Cancel>
<Dialog.Action variant="danger">Disconnect</Dialog.Action>
<Dialog.Action variant="danger" onClick={deleteAccount}>
Disconnect
</Dialog.Action>
</Dialog.Actions>
</Dialog>
)

View File

@ -1,15 +1,16 @@
import React from 'react'
import { useAccount, useMembers } from '~/src/protocol'
import { styled } from '~/src/styles/config'
import { Grid, Heading } from '~/src/system'
import { MemberGroup } from './member-group'
import { MemberItem } from './member-item'
import { UserItem } from './user-item'
import { useMembers } from '~/src/protocol'
export function MemberSidebar() {
const members = useMembers()
const { account } = useAccount()
return (
<Wrapper>
@ -17,9 +18,11 @@ export function MemberSidebar() {
Members
</Heading>
<Grid gap="2">
{account && (
<MemberGroup label="You">
<UserItem />
<UserItem account={account} />
</MemberGroup>
)}
<MemberGroup label="Online">
{members.map(member => (
<MemberItem

View File

@ -17,10 +17,7 @@ export const MemberItem = (props: Props) => {
return (
<Flex gap="2" align="center" css={{ height: 56 }}>
<Avatar
size={32}
indicator={indicator}
/>
<Avatar size={32} indicator={indicator} />
<div>
<Flex align="center" gap={1}>
<Text size="15" color="accent" truncate>

View File

@ -1,27 +1,27 @@
import React from 'react'
import { useAccount } from '~/src/protocol'
import { styled } from '~/src/styles/config'
import { Avatar, DialogTrigger, EthAddress, Flex, Text } from '~/src/system'
import { DisconnectDialog } from './disconnect-dialog'
export const UserItem = () => {
const [account] = useAccount()
console.log("file: user-item.tsx > line 11 > UserItem > account", account)
import type { Account } from '~/src/protocol'
if (!account) {
return null
interface Props {
account: Account
}
export const UserItem = (props: Props) => {
const { account } = props
return (
<Flex align="center" justify="between">
<Flex gap="2" align="center" css={{ height: 56 }}>
<Avatar size={32} src={account.imageUrl} />
<Avatar size={32} />
<div>
<Flex align="center" gap={1}>
<Text size="15" color="accent">
{account.name}
{account.username}
</Text>
</Flex>
<EthAddress size={10} color="gray">
@ -30,7 +30,6 @@ export const UserItem = () => {
</div>
</Flex>
<DialogTrigger>
<DisconnectButton>
<svg
@ -50,10 +49,7 @@ export const UserItem = () => {
/>
</svg>
</DisconnectButton>
{account && (
<DisconnectDialog />
)}
<DisconnectDialog account={account} />
</DialogTrigger>
</Flex>
)

View File

@ -14,27 +14,27 @@ interface Props {
}
export const emojis: Record<Reaction, { url: string; symbol: string }> = {
heart: {
LOVE: {
symbol: '❤️',
url: 'https://twemoji.maxcdn.com/v/latest/svg/2764.svg',
},
'thumbs-up': {
THUMBS_UP: {
symbol: '👍️',
url: 'https://twemoji.maxcdn.com/v/latest/svg/1f44d.svg',
},
'thumbs-down': {
THUMBS_DOWN: {
symbol: '👎️',
url: 'https://twemoji.maxcdn.com/v/latest/svg/1f44e.svg',
},
smile: {
LAUGH: {
symbol: '😆',
url: 'https://twemoji.maxcdn.com/v/latest/svg/1f606.svg',
},
sad: {
SAD: {
symbol: '😭',
url: 'https://twemoji.maxcdn.com/v/latest/svg/1f62d.svg',
},
angry: {
ANGRY: {
symbol: '😡',
url: 'https://twemoji.maxcdn.com/v/latest/svg/1f621.svg',
},

View File

@ -1,35 +1,25 @@
import React, { useState } from 'react'
import { useCommunity } from '~/src/protocol/use-community'
import { useProtocol } from '~/src/protocol'
import { Avatar, Checkbox, Dialog, Flex, Text } from '~/src/system'
export const WelcomeDialog = () => {
const { name, imageUrl, requestNeeded } = useCommunity()
const { community } = useProtocol()
const { identity } = community
const [agreed, setAgreed] = useState(false)
return (
<Dialog title={`Welcome to ${name}`} size={640}>
<Dialog title={`Welcome to ${identity?.displayName}`} size={640}>
<Dialog.Body gap="4">
<Flex justify="center">
<Avatar size="64" src={imageUrl} />
<Avatar
size="64"
src={identity?.displayName}
color={identity?.color}
/>
</Flex>
<Text>
CryptoKitties sed ut perspiciatis unde omnis iste natus error sit
voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque
ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo.
<br />
<br />
Ut enim ad minim veniam Excepteur sint occaecat cupidatat non proident
Duis aute irure Dolore eu fugiat nulla pariatur 🚗 consectetur
adipiscing elit.
<br />
<br />
Nemo enim 😋 ipsam voluptatem quia voluptas sit aspernatur aut odit
aut fugit, sed quia consequuntur magni dolores eos qui ratione
voluptatem sequi nesciunt.
</Text>
<Text>{identity?.description}</Text>
<Flex>
<Checkbox checked={agreed} onChange={setAgreed}>
I agree with the above
@ -38,7 +28,8 @@ export const WelcomeDialog = () => {
</Dialog.Body>
<Dialog.Actions>
<Dialog.Action disabled={agreed === false}>
{requestNeeded ? 'Request to Join' : `Join ${name}`}
Request to Join
{/* {requestNeeded ? 'Request to Join' : `Join ${name}`} */}
</Dialog.Action>
</Dialog.Actions>
</Dialog>

View File

@ -1,27 +1,26 @@
import React from 'react'
import { useMatch } from 'react-router-dom'
// import { PinIcon } from '~/src/icons/pin-icon'
import { Avatar, Flex, Text } from '~/src/system'
import { PinIcon } from '~/src/icons/pin-icon'
import { Avatar, DialogTrigger, Flex, Text } from '~/src/system'
import { PinnedMessagesDialog } from './pinned-messages-dialog'
import type { Channel } from '~/src/protocol'
// import { PinnedMessagesDialog } from './pinned-messages-dialog'
import type { Chat } from '~/src/protocol'
interface Props {
chat: Channel
chat: Chat
}
export const ChatInfo = (props: Props) => {
const { chat } = props
console.log("file: index.tsx > line 18 > ChatInfo > chat", chat)
// if (chat.type == 'channel') {
return (
<Flex align="center" gap="2">
<Avatar size={36} />
<Avatar
size={36}
name={chat.identity?.displayName}
color={chat.identity?.color}
/>
<div>
<Text>#{chat.identity?.displayName}</Text>
<Flex align="center">

View File

@ -14,10 +14,11 @@ interface Props {
mode?: 'normal' | 'editing'
value?: string
editing?: boolean
onSubmit: (value: string) => void
}
export const ChatInput = (props: Props) => {
const { value, editing } = props
const { value, editing, onSubmit } = props
const [inputValue, setInputValue] = useState(value ?? '')
const { state } = useChatContext()
@ -32,6 +33,13 @@ export const ChatInput = (props: Props) => {
setInputValue(event.target.value)
}
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter' && event.shiftKey === false) {
onSubmit(inputValue)
setInputValue('')
}
}
return (
<Wrapper>
<Box css={{ paddingBottom: 6 }}>
@ -47,6 +55,7 @@ export const ChatInput = (props: Props) => {
placeholder="Message"
value={inputValue}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
<Flex>
<IconButton label="Pick emoji">

View File

@ -25,7 +25,7 @@ export const InputReply = (props: Props) => {
<ReplyIcon />
</Icon>
<Text size="13" weight="500" truncate={false}>
{message.contact.name}
TODO: Add name
</Text>
</Flex>
@ -36,14 +36,14 @@ export const InputReply = (props: Props) => {
<CrossIcon />
</IconButton>
</Flex>
{message.type === 'text' && (
{message.contentType === 'TEXT_PLAIN' && (
<Flex>
<Text size="13" truncate>
{message.text}
</Text>
</Flex>
)}
{message.type === 'image' && (
{message.contentType === 'IMAGE' && (
<Image
src={message.imageUrl}
width={56}
@ -53,23 +53,6 @@ export const InputReply = (props: Props) => {
alt="message"
/>
)}
{message.type === 'image-text' && (
<Box>
<Flex>
<Text size="13" truncate>
{message.text}
</Text>
</Flex>
<Image
src={message.imageUrl}
width={56}
height={56}
fit="cover"
radius="bubble"
alt="message"
/>
</Box>
)}
</Wrapper>
)
}

View File

@ -2,11 +2,11 @@ import React from 'react'
import { ReactionPopover } from '~/src/components/reaction-popover'
import { PencilIcon } from '~/src/icons/pencil-icon'
import { PinIcon } from '~/src/icons/pin-icon'
// import { PinIcon } from '~/src/icons/pin-icon'
import { ReactionIcon } from '~/src/icons/reaction-icon'
import { ReplyIcon } from '~/src/icons/reply-icon'
import { TrashIcon } from '~/src/icons/trash-icon'
import { UnpinIcon } from '~/src/icons/unpin-icon'
// import { UnpinIcon } from '~/src/icons/unpin-icon'
import { styled } from '~/src/styles/config'
import {
AlertDialog,
@ -15,7 +15,7 @@ import {
Tooltip,
} from '~/src/system'
import type { Reactions } from '~/src/protocol/use-messages'
import type { Reaction, Reactions } from '~/src/protocol/use-messages'
interface Props {
owner: boolean
@ -23,6 +23,7 @@ interface Props {
onReplyClick: () => void
onEditClick: () => void
onPinClick: () => void
onReactionClick: (reaction: Reaction) => void
reacting: boolean
onReactingChange: (reacting: boolean) => void
reactions: Reactions
@ -31,10 +32,11 @@ interface Props {
export const Actions = (props: Props) => {
const {
owner,
pinned,
// pinned,
onReplyClick,
onEditClick,
onPinClick,
// onPinClick,
onReactionClick,
reacting,
onReactingChange,
reactions,
@ -47,7 +49,7 @@ export const Actions = (props: Props) => {
open={reacting}
onOpenChange={onReactingChange}
onClick={emoji => {
console.log(emoji)
onReactionClick(emoji)
onReactingChange(false)
}}
>
@ -79,7 +81,7 @@ export const Actions = (props: Props) => {
</IconButton>
</Tooltip>
)}
<Tooltip label={pinned ? 'Unpin' : 'Pin'}>
{/* <Tooltip label={pinned ? 'Unpin' : 'Pin'}>
<IconButton
label={pinned ? 'Unpin message' : 'Pin message'}
intent="info"
@ -88,7 +90,7 @@ export const Actions = (props: Props) => {
>
{pinned ? <UnpinIcon /> : <PinIcon />}
</IconButton>
</Tooltip>
</Tooltip> */}
{owner && (
<AlertDialogTrigger>
<Tooltip label="Delete">

View File

@ -1,9 +1,12 @@
import React, { useState } from 'react'
import snarkdown from 'snarkdown'
import { UserProfileDialog } from '~/src/components/user-profile-dialog'
import { useChatContext } from '~/src/contexts/chat-context'
import { BellIcon } from '~/src/icons/bell-icon'
import { PinIcon } from '~/src/icons/pin-icon'
import { useProtocol } from '~/src/protocol/provider'
import { styled } from '~/src/styles/config'
import {
Avatar,
@ -25,10 +28,11 @@ import { Actions } from './actions'
import { MessageReply } from './message-reply'
import { MessageReactions } from './reactions'
import type { MessageType } from '~/src/protocol/use-messages'
import type { Message, Reaction } from '~/src/protocol/use-messages'
interface Props {
message: MessageType
message: Message
previousMessage?: Message
}
// const MessageLink = forwardRef(function MessageLink(
@ -53,15 +57,17 @@ interface Props {
// })
export const ChatMessage = (props: Props) => {
const { message } = props
console.log("🚀 > message", message)
const { client } = useProtocol()
const { message } = props
// const { type, contact, owner, mention, pinned, reply, reactions } = message
const owner = false
const mention = false
const pinned = false
const reply = false
const { contentType, text, displayName, reactions } = message
const { messageId, chatId, contentType, clock, displayName, reactions } =
message
const [editing, setEditing] = useState(false)
const [reacting, setReacting] = useState(false)
@ -70,26 +76,34 @@ export const ChatMessage = (props: Props) => {
const userProfileDialog = useDialog(UserProfileDialog)
const handleReplyClick = () => {
dispatch({
type: 'SET_REPLY',
const handleMessageSubmit = (message: string) => {
client.community.sendTextMessage(
chatId,
message,
})
'0x0fa999097568d1fdcc39108a08d75340bd2cee5ec59c36799007150d0a9fc896'
)
}
const handleReaction = (reaction: Reaction) => {
client.community.sendReaction(chatId, messageId, reaction)
}
const handleReplyClick = () => {
dispatch({ type: 'SET_REPLY', message })
}
const handlePinClick = () => {
// console.log(pinned)
}
const handleReaction = (reaction: string) => {
console.log(reaction)
// TODO: pin message
}
const renderMessage = () => {
if (editing) {
return (
<Box>
<ChatInput value={message?.text ?? ''} />
<ChatInput
value={message?.text ?? ''}
onSubmit={handleMessageSubmit}
/>
<Flex gap={2}>
<Button
variant="outline"
@ -98,7 +112,9 @@ export const ChatMessage = (props: Props) => {
>
Cancel
</Button>
<Button size="small">Save</Button>
<Button size="small" onClick={handleMessageSubmit}>
Save
</Button>
</Flex>
</Box>
)
@ -118,37 +134,30 @@ export const ChatMessage = (props: Props) => {
// </AlertDialogTrigger>{' '}
return <Text>{message.text}</Text>
}
case 'image': {
case 'EMOJI': {
return (
<Text css={{ fontSize: '3rem', lineHeight: 1.1, letterSpacing: -2 }}>
{message.text}
</Text>
)
}
case 'IMAGE': {
const blob = new Blob([message.image.payload], { type: 'image/jpeg' })
// TODO?: call URL.revokeObjectURL()
return (
<Flex gap={1} css={{ paddingTop: '$2' }}>
<Image
width={147}
width={150}
alt="message"
height={196}
src={message.imageUrl}
height={150}
src={URL.createObjectURL(blob)}
radius="bubble"
fit="cover"
/>
</Flex>
)
}
case 'image-text': {
const { text, imageUrl } = message
return (
<>
<Text>{text}</Text>
<Flex gap={1} css={{ paddingTop: '$1' }}>
<Image
width={147}
alt="message"
height={196}
src={imageUrl}
radius="bubble"
/>
</Flex>
</>
)
}
}
}
@ -161,12 +170,11 @@ export const ChatMessage = (props: Props) => {
<Box>
<DropdownMenuTrigger>
<button type="button">
{/* <Avatar size={44} src={contact.imageUrl} /> */}
<Avatar size={44} />
</button>
<DropdownMenu>
<Flex direction="column" align="center" gap="1">
{/* <Avatar size="36" src={contact.imageUrl} /> */}
{/* <Text>{contact.name}</Text> */}
<Avatar size="36" />
<Text>{displayName}</Text>
<EmojiHash />
</Flex>
@ -197,20 +205,22 @@ export const ChatMessage = (props: Props) => {
</Box>
<Box css={{ flex: 1 }}>
{pinned && (
{/* {pinned && (
<Flex gap={1}>
<PinIcon width={8} />
<Text size="13">Pinned by carmen.eth</Text>
<Text size="13">Pinned by {contact.name}</Text>
</Flex>
)}
)} */}
<Flex gap="1" align="center">
<Text color="primary" weight="500" size="15">
{displayName}
{/* {contact.name} */}
</Text>
<Text size="10" color="gray">
10:00 AM
{new Date(Number(clock)).toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
})}
</Text>
</Flex>
@ -229,6 +239,7 @@ export const ChatMessage = (props: Props) => {
onEditClick={() => setEditing(true)}
onReplyClick={handleReplyClick}
onPinClick={handlePinClick}
onReactionClick={handleReaction}
reacting={reacting}
onReactingChange={setReacting}
reactions={reactions}
@ -236,7 +247,7 @@ export const ChatMessage = (props: Props) => {
</Wrapper>
<ContextMenu>
<ContextMenu.Item onSelect={handleReplyClick}>Reply</ContextMenu.Item>
<ContextMenu.Item onSelect={handlePinClick}>Pin</ContextMenu.Item>
{/* <ContextMenu.Item onSelect={handlePinClick}>Pin</ContextMenu.Item> */}
</ContextMenu>
</ContextMenuTrigger>
</>

View File

@ -7,7 +7,7 @@ import { useAppState } from '~/src/contexts/app-context'
import { BellIcon } from '~/src/icons/bell-icon'
import { DotsIcon } from '~/src/icons/dots-icon'
import { GroupIcon } from '~/src/icons/group-icon'
import { useChannel } from '~/src/protocol'
import { useChat } from '~/src/protocol'
import { styled } from '~/src/styles/config'
import { DropdownMenuTrigger, Flex, IconButton, Separator } from '~/src/system'
@ -23,7 +23,7 @@ export const Navbar = (props: Props) => {
const { state, dispatch } = useAppState()
const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion
const chat = useChannel(params.id!)
const chat = useChat(params.id!)
return (
<NavbarWrapper>
@ -44,7 +44,7 @@ export const Navbar = (props: Props) => {
<IconButton label="Options">
<DotsIcon />
</IconButton>
<ChatMenu type="dropdown" chatType="channel" />
<ChatMenu type="dropdown" />
</DropdownMenuTrigger>
<Separator orientation="vertical" css={{ height: 24 }} />

View File

@ -1,3 +1,5 @@
// TODO: handle non-existing chat ID
import React, { useEffect, useRef } from 'react'
import { useMatch } from 'react-router-dom'
@ -5,7 +7,8 @@ import { useMatch } from 'react-router-dom'
import { MemberSidebar } from '~/src/components/member-sidebar'
import { useAppState } from '~/src/contexts/app-context'
import { ChatProvider } from '~/src/contexts/chat-context'
import { useChannel } from '~/src/protocol'
import { useChat } from '~/src/protocol'
import { useProtocol } from '~/src/protocol/provider'
import { useMessages } from '~/src/protocol/use-messages'
import { styled } from '~/src/styles/config'
import { Avatar, Flex, Heading, Text } from '~/src/system'
@ -14,59 +17,85 @@ import { ChatInput } from './components/chat-input'
import { ChatMessage } from './components/chat-message'
import { Navbar } from './components/navbar'
const ChatStart = () => {
// TODO: unify this with the useChat hook
const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion
interface ChatStartProps {
chatId: string
}
const chat = useChannel(params.id!)
const ChatStart = (props: ChatStartProps) => {
const { chatId } = props
const { identity } = useChat(chatId)
return (
<Flex direction="column" gap="3" align="center" css={{ marginBottom: 50 }}>
{/* <Avatar size={120} src={chat.imageUrl} /> */}
<Heading>{chat.identity?.displayName}</Heading>
<Avatar size={120} name={identity?.displayName} color={identity?.color} />
<Heading>{identity?.displayName}</Heading>
<Text>
Welcome to the beginning of the #{chat.identity?.displayName} channel!
Welcome to the beginning of the #{identity?.displayName} channel!
</Text>
</Flex>
)
}
const Content = () => {
const contentRef = useRef<HTMLDivElement>(null)
interface ContentProps {
chatId: string
}
const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion
const Content = (props: ContentProps) => {
const { chatId } = props
const contentRef = useRef<HTMLDivElement>(null)
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
contentRef.current!.scrollTop = contentRef.current!.scrollHeight ?? 0
}, [])
}, [chatId])
const messages = useMessages(params.id!)
const messages = useMessages(chatId)
return (
<ContentWrapper ref={contentRef}>
<ChatStart />
{messages.data.map(message => (
<ChatMessage key={message.messageId} message={message} />
{/* <Button onClick={messages.fetchMore}>Fetch more</Button> */}
<ChatStart chatId={chatId} />
{messages.data.map((message, index) => (
<ChatMessage
key={message.messageId}
message={message}
previousMessage={messages.data[index - 1]}
/>
))}
</ContentWrapper>
)
}
export const Chat = () => {
const { client } = useProtocol()
const { state, options } = useAppState()
const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion
const chatId = params.id!
const chat = useChat(chatId)
// TODO: Update condition based on a chat type
const enableMembers = options.enableMembers // && (chat.type === 'group' || chat.type === 'channel')
const enableMembers = options.enableMembers ?? false // && (chat.type === 'group' || chat.type === 'channel')
const showMembers = enableMembers && state.showMembers
const handleMessageSubmit = (message: string) => {
client.community.sendTextMessage(
chatId,
message
// '0x0fa999097568d1fdcc39108a08d75340bd2cee5ec59c36799007150d0a9fc896'
)
}
return (
<ChatProvider>
<Wrapper>
<Main>
<Navbar enableMembers={enableMembers} />
<Content />
<ChatInput />
<Content chatId={chatId} />
<ChatInput onSubmit={handleMessageSubmit} />
</Main>
{showMembers && <MemberSidebar />}
</Wrapper>