(WIP) 重构客户端
This commit is contained in:
46
client/ui/main-page/AddFavourtieChatDialog.tsx
Normal file
46
client/ui/main-page/AddFavourtieChatDialog.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import * as React from 'react'
|
||||
import { Button, Dialog, snackbar, TextField } from "mdui"
|
||||
import { data } from 'react-router'
|
||||
import { useContextSelector } from 'use-context-selector'
|
||||
import MainSharedContext, { Shared } from '../MainSharedContext'
|
||||
import showSnackbar from '../../utils/showSnackbar'
|
||||
import { CallbackError } from 'lingchair-client-protocol'
|
||||
import useEventListener from '../../utils/useEventListener'
|
||||
|
||||
export default function AddFavourtieChatDialog({ ...props }: { open: boolean } & React.HTMLAttributes<Dialog>) {
|
||||
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
||||
myProfileCache: context.myProfileCache,
|
||||
setShowAddFavourtieChatDialog: context.setShowAddFavourtieChatDialog,
|
||||
}))
|
||||
|
||||
const dialogRef = React.useRef<Dialog>()
|
||||
useEventListener(dialogRef, 'closed', () => shared.setShowAddFavourtieChatDialog(false))
|
||||
|
||||
const inputTargetRef = React.useRef<TextField>(null)
|
||||
|
||||
async function addFavouriteChat() {
|
||||
try {
|
||||
shared.myProfileCache!.addFavouriteChatsOrThrow([inputTargetRef.current!.value])
|
||||
inputTargetRef.current!.value = ''
|
||||
showSnackbar({
|
||||
message: '添加成功!'
|
||||
})
|
||||
} catch (e) {
|
||||
if (e instanceof CallbackError)
|
||||
showSnackbar({
|
||||
message: '添加收藏对话失败: ' + e.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<mdui-dialog close-on-overlay-click close-on-esc headline="添加收藏对话" {...props} ref={dialogRef}>
|
||||
<mdui-text-field clearable label="对话 / 用户 (ID 或 别名)" ref={inputTargetRef} onKeyDown={(event: KeyboardEvent) => {
|
||||
if (event.key == 'Enter')
|
||||
addFavouriteChat()
|
||||
}}></mdui-text-field>
|
||||
<mdui-button slot="action" variant="text" onClick={() => shared.setShowAddFavourtieChatDialog(false)}>取消</mdui-button>
|
||||
<mdui-button slot="action" variant="text" onClick={() => addFavouriteChat()}>添加</mdui-button>
|
||||
</mdui-dialog>
|
||||
)
|
||||
}
|
||||
@@ -3,19 +3,19 @@ import React from "react"
|
||||
import AllChatsListItem from "./AllChatsListItem.tsx"
|
||||
import useEventListener from "../../utils/useEventListener.ts"
|
||||
import useAsyncEffect from "../../utils/useAsyncEffect.ts"
|
||||
import { CallbackError, Chat, User, UserMySelf } from "lingchair-client-protocol"
|
||||
import { CallbackError, Chat, UserMySelf } from "lingchair-client-protocol"
|
||||
import getClient from "../../getClient.ts"
|
||||
import showSnackbar from "../../utils/showSnackbar.ts"
|
||||
import isMobileUI from "../../utils/isMobileUI.ts"
|
||||
|
||||
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||
display: boolean
|
||||
currentChatId: string
|
||||
openChatInfoDialog: (chat: Chat) => void
|
||||
}
|
||||
import { useContextSelector } from "use-context-selector"
|
||||
import MainSharedContext, { Shared } from "../MainSharedContext.ts"
|
||||
|
||||
export default function AllChatsList({ ...props }: React.HTMLAttributes<HTMLElement>) {
|
||||
|
||||
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
||||
myProfileCache: context.myProfileCache,
|
||||
functions_lazy: context.functions_lazy,
|
||||
}))
|
||||
|
||||
const searchRef = React.useRef<HTMLElement>(null)
|
||||
const [searchText, setSearchText] = React.useState('')
|
||||
const [allChatsList, setAllChatsList] = React.useState<Chat[]>([])
|
||||
@@ -27,16 +27,19 @@ export default function AllChatsList({ ...props }: React.HTMLAttributes<HTMLElem
|
||||
useAsyncEffect(async () => {
|
||||
async function updateAllChats() {
|
||||
try {
|
||||
setAllChatsList(await (await UserMySelf.getMySelfOrThrow(getClient())).getMyAllChatsOrThrow())
|
||||
setAllChatsList(await shared.myProfileCache!.getMyAllChatsOrThrow())
|
||||
} catch (e) {
|
||||
if (e instanceof CallbackError)
|
||||
if (e.code == 401 || e.code == 400)
|
||||
if (e.code != 401 && e.code != 400)
|
||||
showSnackbar({
|
||||
message: '获取所有对话失败: ' + e.message
|
||||
})
|
||||
}
|
||||
}
|
||||
updateAllChats()
|
||||
|
||||
shared.functions_lazy.current.updateAllChats = updateAllChats
|
||||
|
||||
return () => {
|
||||
}
|
||||
})
|
||||
@@ -48,6 +51,7 @@ export default function AllChatsList({ ...props }: React.HTMLAttributes<HTMLElem
|
||||
paddingTop: '0',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
...props?.style,
|
||||
}} {...props}>
|
||||
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||
paddingTop: '12px',
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
import React from "react"
|
||||
import ContactsListItem from "./ContactsListItem.tsx"
|
||||
import { dialog, Dialog, TextField } from "mdui"
|
||||
|
||||
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||
display: boolean
|
||||
openChatInfoDialog: (chat: Chat) => void
|
||||
addContactDialogRef: React.MutableRefObject<Dialog>
|
||||
createGroupDialogRef: React.MutableRefObject<Dialog>
|
||||
setSharedFavouriteChats: React.Dispatch<React.SetStateAction<Chat[]>>
|
||||
currentChatId: string
|
||||
}
|
||||
|
||||
export default function ContactsList({
|
||||
display,
|
||||
openChatInfoDialog,
|
||||
addContactDialogRef,
|
||||
createGroupDialogRef,
|
||||
setSharedFavouriteChats,
|
||||
currentChatId,
|
||||
...props
|
||||
}: Args) {
|
||||
const searchRef = React.useRef<HTMLElement>(null)
|
||||
const [isMultiSelecting, setIsMultiSelecting] = React.useState(false)
|
||||
const [searchText, setSearchText] = React.useState('')
|
||||
const [contactsList, setContactsList] = React.useState<Chat[]>([])
|
||||
const [checkedList, setCheckedList] = React.useState<{ [key: string]: boolean }>({})
|
||||
|
||||
useEventListener(searchRef, 'input', (e) => {
|
||||
setSearchText((e.target as unknown as TextField).value)
|
||||
})
|
||||
|
||||
React.useEffect(() => {
|
||||
async function updateContacts() {
|
||||
const re = await Client.invoke("User.getMyContacts", {
|
||||
token: data.access_token,
|
||||
})
|
||||
if (re.code != 200) {
|
||||
if (re.code != 401 && re.code != 400) checkApiSuccessOrSncakbar(re, "获取收藏对话列表失败")
|
||||
return
|
||||
}
|
||||
const ls = re.data!.contacts_list as Chat[]
|
||||
setContactsList(ls)
|
||||
setSharedFavouriteChats(ls)
|
||||
}
|
||||
updateContacts()
|
||||
EventBus.on('ContactsList.updateContacts', () => updateContacts())
|
||||
return () => {
|
||||
EventBus.off('ContactsList.updateContacts')
|
||||
}
|
||||
// 警告: 不添加 deps 導致無限執行
|
||||
}, [])
|
||||
|
||||
return <mdui-list style={{
|
||||
overflowY: 'auto',
|
||||
paddingLeft: '10px',
|
||||
paddingRight: '10px',
|
||||
paddingTop: '0',
|
||||
display: display ? undefined : 'none',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}} {...props}>
|
||||
<div style={{
|
||||
position: 'sticky',
|
||||
top: '0',
|
||||
backgroundColor: 'rgb(var(--mdui-color-background))',
|
||||
zIndex: '10',
|
||||
}}>
|
||||
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||
paddingTop: '12px',
|
||||
}}></mdui-text-field>
|
||||
<mdui-list-item rounded style={{
|
||||
marginTop: '13px',
|
||||
width: '100%',
|
||||
}} icon="person_add" onClick={() => addContactDialogRef.current!.open = true}>添加收藏对话</mdui-list-item>
|
||||
<mdui-list-item rounded style={{
|
||||
width: '100%',
|
||||
}} icon="refresh" onClick={() => EventBus.emit('ContactsList.updateContacts')}>刷新</mdui-list-item>
|
||||
<mdui-list-item rounded style={{
|
||||
width: '100%',
|
||||
}} icon={isMultiSelecting ? "done" : "edit"} onClick={() => {
|
||||
if (isMultiSelecting)
|
||||
setCheckedList({})
|
||||
setIsMultiSelecting(!isMultiSelecting)
|
||||
}}>{isMultiSelecting ? "关闭多选" : "多选模式"}</mdui-list-item>
|
||||
{
|
||||
isMultiSelecting && <>
|
||||
<mdui-list-item rounded style={{
|
||||
width: '100%',
|
||||
}} icon="delete" onClick={() => dialog({
|
||||
headline: "删除所选",
|
||||
description: "确定要删除所选的收藏对话吗? 这并不会删除您的聊天记录, 也不会丢失对话成员身份",
|
||||
closeOnEsc: true,
|
||||
closeOnOverlayClick: true,
|
||||
actions: [
|
||||
{
|
||||
text: "取消",
|
||||
onClick: () => {
|
||||
return true
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "确定",
|
||||
onClick: async () => {
|
||||
const ls = Object.keys(checkedList).filter((chatId) => checkedList[chatId] == true)
|
||||
const re = await Client.invoke("User.removeContacts", {
|
||||
token: data.access_token,
|
||||
targets: ls,
|
||||
})
|
||||
if (re.code != 200)
|
||||
checkApiSuccessOrSncakbar(re, "删除所选收藏失败")
|
||||
else {
|
||||
setCheckedList({})
|
||||
setIsMultiSelecting(false)
|
||||
EventBus.emit('ContactsList.updateContacts')
|
||||
snackbar({
|
||||
message: "已删除所选",
|
||||
placement: "top",
|
||||
action: "撤销操作",
|
||||
onActionClick: async () => {
|
||||
const re = await Client.invoke("User.addContacts", {
|
||||
token: data.access_token,
|
||||
targets: ls,
|
||||
})
|
||||
if (re.code != 200)
|
||||
checkApiSuccessOrSncakbar(re, "恢复所选收藏失败")
|
||||
EventBus.emit('ContactsList.updateContacts')
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
})}>删除所选</mdui-list-item>
|
||||
</>
|
||||
}
|
||||
<div style={{
|
||||
height: "10px",
|
||||
}}></div>
|
||||
</div>
|
||||
|
||||
{
|
||||
contactsList.filter((chat) =>
|
||||
searchText == '' ||
|
||||
chat.title.includes(searchText) ||
|
||||
chat.id.includes(searchText)
|
||||
).map((v) =>
|
||||
<ContactsListItem
|
||||
active={isMultiSelecting ? checkedList[v.id] == true : (isMobileUI() ? false : currentChatId == v.id)}
|
||||
onClick={() => {
|
||||
if (isMultiSelecting)
|
||||
setCheckedList({
|
||||
...checkedList,
|
||||
[v.id]: !checkedList[v.id],
|
||||
})
|
||||
else
|
||||
openChatInfoDialog(v)
|
||||
}}
|
||||
key={v.id}
|
||||
contact={v} />
|
||||
)
|
||||
}
|
||||
</mdui-list>
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import Chat from "../../api/client_data/Chat.ts"
|
||||
import getUrlForFileByHash from "../../getUrlForFileByHash.ts"
|
||||
import Avatar from "../Avatar.tsx"
|
||||
import React from 'react'
|
||||
|
||||
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||
contact: Chat
|
||||
active?: boolean
|
||||
}
|
||||
|
||||
export default function ContactsListItem({ contact, ...prop }: Args) {
|
||||
const { id, title, avatar_file_hash } = contact
|
||||
const ref = React.useRef<HTMLElement>(null)
|
||||
|
||||
return (
|
||||
<mdui-list-item ref={ref} rounded style={{
|
||||
marginTop: '3px',
|
||||
marginBottom: '3px',
|
||||
width: '100%',
|
||||
}} {...prop as any}>
|
||||
<span style={{
|
||||
width: "100%",
|
||||
}}>{title}</span>
|
||||
<Avatar src={getUrlForFileByHash(avatar_file_hash as string)} text={title} slot="icon" />
|
||||
</mdui-list-item>
|
||||
)
|
||||
}
|
||||
170
client/ui/main-page/FavouriteChatsList.tsx
Normal file
170
client/ui/main-page/FavouriteChatsList.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import React from "react"
|
||||
import FavouriteChatsListItem from "./FavouriteChatsListItem.tsx"
|
||||
import { dialog, TextField } from "mdui"
|
||||
import useAsyncEffect from "../../utils/useAsyncEffect.ts"
|
||||
import useEventListener from "../../utils/useEventListener.ts"
|
||||
import { CallbackError, Chat, UserMySelf } from "lingchair-client-protocol"
|
||||
import showSnackbar from "../../utils/showSnackbar.ts"
|
||||
import getClient from "../../getClient.ts"
|
||||
import { useContextSelector } from "use-context-selector"
|
||||
import MainSharedContext, { Shared } from "../MainSharedContext.ts"
|
||||
import isMobileUI from "../../utils/isMobileUI.ts"
|
||||
|
||||
export default function FavouriteChatsList({ ...props }: React.HTMLAttributes<HTMLElement>) {
|
||||
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
||||
myProfileCache: context.myProfileCache,
|
||||
setShowAddFavourtieChatDialog: context.setShowAddFavourtieChatDialog,
|
||||
functions_lazy: context.functions_lazy,
|
||||
currentSelectedChatId: context.currentSelectedChatId,
|
||||
values_lazy: context.values_lazy,
|
||||
}))
|
||||
|
||||
const searchRef = React.useRef<HTMLElement>(null)
|
||||
const [isMultiSelecting, setIsMultiSelecting] = React.useState(false)
|
||||
const [searchText, setSearchText] = React.useState('')
|
||||
const [favouriteChatsList, setFavouriteChatsList] = React.useState<Chat[]>([])
|
||||
const [checkedList, setCheckedList] = React.useState<{ [key: string]: boolean }>({})
|
||||
|
||||
useEventListener(searchRef, 'input', (e) => {
|
||||
setSearchText((e.target as unknown as TextField).value)
|
||||
})
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
async function updateFavouriteChats() {
|
||||
try {
|
||||
const ls = await shared.myProfileCache!.getMyFavouriteChatsOrThrow()
|
||||
setFavouriteChatsList(ls)
|
||||
shared.favourite_chats
|
||||
} catch (e) {
|
||||
if (e instanceof CallbackError)
|
||||
if (e.code != 401 && e.code != 400)
|
||||
showSnackbar({
|
||||
message: '获取收藏对话失败: ' + e.message
|
||||
})
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
updateFavouriteChats()
|
||||
|
||||
shared.functions_lazy.current.updateFavouriteChats = updateFavouriteChats
|
||||
|
||||
return () => {
|
||||
}
|
||||
}, [shared.myProfileCache])
|
||||
|
||||
return <mdui-list style={{
|
||||
overflowY: 'auto',
|
||||
paddingLeft: '10px',
|
||||
paddingRight: '10px',
|
||||
paddingTop: '0',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
...props?.style,
|
||||
}} {...props}>
|
||||
<div style={{
|
||||
position: 'sticky',
|
||||
top: '0',
|
||||
backgroundColor: 'rgb(var(--mdui-color-background))',
|
||||
zIndex: '10',
|
||||
}}>
|
||||
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||
paddingTop: '12px',
|
||||
}}></mdui-text-field>
|
||||
<mdui-list-item rounded style={{
|
||||
marginTop: '13px',
|
||||
width: '100%',
|
||||
}} icon="person_add" onClick={() => shared.setShowAddFavourtieChatDialog(true)}>添加收藏</mdui-list-item>
|
||||
<mdui-list-item rounded style={{
|
||||
width: '100%',
|
||||
}} icon="refresh" onClick={() => shared.functions_lazy.current.updateFavouriteChats()}>刷新列表</mdui-list-item>
|
||||
<mdui-list-item rounded style={{
|
||||
width: '100%',
|
||||
}} icon={isMultiSelecting ? "done" : "edit"} onClick={() => {
|
||||
if (isMultiSelecting)
|
||||
setCheckedList({})
|
||||
setIsMultiSelecting(!isMultiSelecting)
|
||||
}}>{isMultiSelecting ? "关闭多选" : "多选模式"}</mdui-list-item>
|
||||
{
|
||||
isMultiSelecting && <>
|
||||
<mdui-list-item rounded style={{
|
||||
width: '100%',
|
||||
}} icon="delete" onClick={() => dialog({
|
||||
headline: "移除收藏对话",
|
||||
description: "确定将所选对话从收藏中移除吗? 这不会导致对话被删除.",
|
||||
closeOnEsc: true,
|
||||
closeOnOverlayClick: true,
|
||||
actions: [
|
||||
{
|
||||
text: "取消",
|
||||
onClick: () => {
|
||||
return true
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "确定",
|
||||
onClick: async () => {
|
||||
const ls = Object.keys(checkedList).filter((chatId) => checkedList[chatId] == true)
|
||||
|
||||
try {
|
||||
shared.myProfileCache!.removeFavouriteChatsOrThrow(ls)
|
||||
|
||||
setCheckedList({})
|
||||
setIsMultiSelecting(false)
|
||||
|
||||
shared.functions_lazy.current.updateFavouriteChats()
|
||||
|
||||
showSnackbar({
|
||||
message: "已删除所选",
|
||||
action: "撤销操作",
|
||||
onActionClick: async () => {
|
||||
try {
|
||||
shared.myProfileCache!.addFavouriteChatsOrThrow(ls)
|
||||
} catch (e) {
|
||||
if (e instanceof CallbackError)
|
||||
showSnackbar({
|
||||
message: '撤销删除收藏失败: ' + e.message
|
||||
})
|
||||
}
|
||||
shared.functions_lazy.current.updateFavouriteChats()
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
if (e instanceof CallbackError)
|
||||
showSnackbar({
|
||||
message: '删除收藏对话失败: ' + e.message
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
})}>删除所选</mdui-list-item>
|
||||
</>
|
||||
}
|
||||
<div style={{
|
||||
height: "10px",
|
||||
}}></div>
|
||||
</div>
|
||||
|
||||
{
|
||||
favouriteChatsList.filter((chat) =>
|
||||
searchText == '' ||
|
||||
chat.getTitle().includes(searchText) ||
|
||||
chat.getId().includes(searchText)
|
||||
).map((v) =>
|
||||
<FavouriteChatsListItem
|
||||
active={isMultiSelecting ? checkedList[v.getId()] == true : (isMobileUI() ? false : shared.currentSelectedChatId == v.getId())}
|
||||
onClick={() => {
|
||||
if (isMultiSelecting)
|
||||
setCheckedList({
|
||||
...checkedList,
|
||||
[v.getId()]: !checkedList[v.getId()],
|
||||
})
|
||||
else
|
||||
openChatInfoDialog(v)
|
||||
}}
|
||||
key={v.getId()}
|
||||
chat={v} />
|
||||
)
|
||||
}
|
||||
</mdui-list>
|
||||
}
|
||||
28
client/ui/main-page/FavouriteChatsListItem.tsx
Normal file
28
client/ui/main-page/FavouriteChatsListItem.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Chat } from "lingchair-client-protocol"
|
||||
import Avatar from "../Avatar.tsx"
|
||||
import React from 'react'
|
||||
import getClient from "../../getClient.ts"
|
||||
|
||||
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||
chat: Chat
|
||||
active?: boolean
|
||||
}
|
||||
|
||||
export default function FavouriteChatsListItem({ chat, active, ...prop }: Args) {
|
||||
const title = chat.getTitle()
|
||||
|
||||
const ref = React.useRef<HTMLElement>(null)
|
||||
|
||||
return (
|
||||
<mdui-list-item active={active} ref={ref} rounded style={{
|
||||
marginTop: '3px',
|
||||
marginBottom: '3px',
|
||||
width: '100%',
|
||||
}} {...prop}>
|
||||
<span style={{
|
||||
width: "100%",
|
||||
}}>{title}</span>
|
||||
<Avatar src={getClient().getUrlForFileByHash(chat.getAvatarFileHash() as string)} text={title} slot="icon" />
|
||||
</mdui-list-item>
|
||||
)
|
||||
}
|
||||
@@ -1,18 +1,26 @@
|
||||
import * as React from 'react'
|
||||
import { Button, Dialog, TextField } from "mdui"
|
||||
import { Dialog, TextField } from "mdui"
|
||||
|
||||
import performAuth from '../../performAuth.ts'
|
||||
import showSnackbar from '../../utils/showSnackbar.ts'
|
||||
import MainSharedContext from '../MainSharedContext.ts'
|
||||
import MainSharedContext, { Shared } from '../MainSharedContext.ts'
|
||||
import { useContextSelector } from 'use-context-selector'
|
||||
import useEventListener from '../../utils/useEventListener.ts'
|
||||
|
||||
export default function LoginDialog({ ...props }: { open: boolean } & React.HTMLAttributes<Dialog>) {
|
||||
const shared = React.useContext(MainSharedContext)
|
||||
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
||||
setShowRegisterDialog: context.setShowRegisterDialog,
|
||||
setShowLoginDialog: context.setShowLoginDialog
|
||||
}))
|
||||
|
||||
const dialogRef = React.useRef<Dialog>()
|
||||
useEventListener(dialogRef, 'closed', () => shared.setShowLoginDialog(false))
|
||||
|
||||
const loginInputAccountRef = React.useRef<TextField>(null)
|
||||
const loginInputPasswordRef = React.useRef<TextField>(null)
|
||||
|
||||
return (
|
||||
<mdui-dialog {...props} headline="登录">
|
||||
<mdui-dialog {...props} headline="登录" ref={dialogRef}>
|
||||
|
||||
<mdui-text-field label="用户 ID / 用户名" ref={loginInputAccountRef}></mdui-text-field>
|
||||
<div style={{
|
||||
|
||||
83
client/ui/main-page/RecentChatsList.tsx
Normal file
83
client/ui/main-page/RecentChatsList.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { TextField } from "mdui"
|
||||
import RecentsListItem from "./RecentsListItem.tsx"
|
||||
import React from "react"
|
||||
import RecentChat from "lingchair-client-protocol/RecentChat.ts"
|
||||
import { data } from "react-router"
|
||||
import isMobileUI from "../../utils/isMobileUI.ts"
|
||||
import useAsyncEffect from "../../utils/useAsyncEffect.ts"
|
||||
import useEventListener from "../../utils/useEventListener.ts"
|
||||
import { CallbackError } from "lingchair-client-protocol"
|
||||
import { useContextSelector } from "use-context-selector"
|
||||
import showSnackbar from "../../utils/showSnackbar.ts"
|
||||
import MainSharedContext, { Shared } from "../MainSharedContext.ts"
|
||||
|
||||
export default function RecentChatsList({ ...props }: React.HTMLAttributes<HTMLElement>) {
|
||||
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
||||
myProfileCache: context.myProfileCache,
|
||||
functions_lazy: context.functions_lazy,
|
||||
currentSelectedChatId: context.currentSelectedChatId,
|
||||
}))
|
||||
|
||||
const searchRef = React.useRef<HTMLElement>(null)
|
||||
const [searchText, setSearchText] = React.useState('')
|
||||
const [recentsList, setRecentsList] = React.useState<RecentChat[]>([])
|
||||
|
||||
useEventListener(searchRef, 'input', (e) => {
|
||||
setSearchText((e.target as unknown as TextField).value)
|
||||
})
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
async function updateRecents() {
|
||||
try {
|
||||
setRecentsList(await shared.myProfileCache!.getMyRecentChats())
|
||||
} catch (e) {
|
||||
if (e instanceof CallbackError)
|
||||
if (e.code != 401 && e.code != 400)
|
||||
showSnackbar({
|
||||
message: '获取最近对话失败: ' + e.message
|
||||
})
|
||||
}
|
||||
}
|
||||
updateRecents()
|
||||
|
||||
shared.functions_lazy.current.updateRecentChats = updateRecents
|
||||
|
||||
const id = setInterval(() => updateRecents(), 15 * 1000)
|
||||
return () => {
|
||||
clearInterval(id)
|
||||
}
|
||||
})
|
||||
|
||||
return <mdui-list style={{
|
||||
overflowY: 'auto',
|
||||
paddingRight: '10px',
|
||||
paddingLeft: '10px',
|
||||
paddingTop: '0',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
...props?.style,
|
||||
}} {...props}>
|
||||
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||
paddingTop: '12px',
|
||||
marginBottom: '13px',
|
||||
position: 'sticky',
|
||||
top: '0',
|
||||
backgroundColor: 'rgb(var(--mdui-color-background))',
|
||||
zIndex: '10',
|
||||
}}></mdui-text-field>
|
||||
{
|
||||
recentsList.filter((chat) =>
|
||||
searchText == '' ||
|
||||
chat.getTitle().includes(searchText) ||
|
||||
chat.getId().includes(searchText) ||
|
||||
chat.getContent().includes(searchText)
|
||||
).map((v) =>
|
||||
<RecentsListItem
|
||||
active={isMobileUI() ? false : shared.currentSelectedChatId == v.getId()}
|
||||
openChatFragment={() => openChatFragment(v.getId())}
|
||||
key={v.getId()}
|
||||
recentChat={v} />
|
||||
)
|
||||
}
|
||||
</mdui-list>
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { TextField } from "mdui"
|
||||
import RecentChat from "../../api/client_data/RecentChat.ts"
|
||||
import useEventListener from "../useEventListener.ts"
|
||||
import RecentsListItem from "./RecentsListItem.tsx"
|
||||
import React from "react"
|
||||
import useAsyncEffect from "../useAsyncEffect.ts"
|
||||
import Client from "../../api/Client.ts"
|
||||
import { checkApiSuccessOrSncakbar } from "../snackbar.ts";
|
||||
import data from "../../Data.ts";
|
||||
import EventBus from "../../EventBus.ts";
|
||||
import isMobileUI from "../isMobileUI.ts";
|
||||
|
||||
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||
display: boolean
|
||||
currentChatId: string
|
||||
openChatFragment: (id: string) => void
|
||||
}
|
||||
|
||||
export default function RecentsList({
|
||||
currentChatId,
|
||||
display,
|
||||
openChatFragment,
|
||||
...props
|
||||
}: Args) {
|
||||
const searchRef = React.useRef<HTMLElement>(null)
|
||||
const [searchText, setSearchText] = React.useState('')
|
||||
const [recentsList, setRecentsList] = React.useState<RecentChat[]>([])
|
||||
|
||||
useEventListener(searchRef, 'input', (e) => {
|
||||
setSearchText((e.target as unknown as TextField).value)
|
||||
})
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
async function updateRecents() {
|
||||
const re = await Client.invoke("User.getMyRecentChats", {
|
||||
token: data.access_token,
|
||||
})
|
||||
if (re.code != 200) {
|
||||
if (re.code != 401 && re.code != 400) checkApiSuccessOrSncakbar(re, "获取最近对话列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
setRecentsList(re.data!.recent_chats as RecentChat[])
|
||||
}
|
||||
updateRecents()
|
||||
EventBus.on('RecentsList.updateRecents', () => updateRecents())
|
||||
const id = setInterval(() => updateRecents(), 15 * 1000)
|
||||
return () => {
|
||||
EventBus.off('RecentsList.updateRecents')
|
||||
clearInterval(id)
|
||||
}
|
||||
})
|
||||
|
||||
return <mdui-list style={{
|
||||
overflowY: 'auto',
|
||||
paddingRight: '10px',
|
||||
paddingLeft: '10px',
|
||||
paddingTop: '0',
|
||||
display: display ? undefined : 'none',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}} {...props}>
|
||||
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||
paddingTop: '12px',
|
||||
marginBottom: '13px',
|
||||
position: 'sticky',
|
||||
top: '0',
|
||||
backgroundColor: 'rgb(var(--mdui-color-background))',
|
||||
zIndex: '10',
|
||||
}}></mdui-text-field>
|
||||
{
|
||||
recentsList.filter((chat) =>
|
||||
searchText == '' ||
|
||||
chat.title.includes(searchText) ||
|
||||
chat.id.includes(searchText) ||
|
||||
chat.content.includes(searchText)
|
||||
).map((v) =>
|
||||
<RecentsListItem
|
||||
active={isMobileUI() ? false : currentChatId == v.id}
|
||||
openChatFragment={() => openChatFragment(v.id)}
|
||||
key={v.id}
|
||||
recentChat={v} />
|
||||
)
|
||||
}
|
||||
</mdui-list>
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
import { $ } from "mdui/jq"
|
||||
import RecentChat from "../../api/client_data/RecentChat.ts"
|
||||
import Avatar from "../Avatar.tsx"
|
||||
import React from 'react'
|
||||
import getUrlForFileByHash from "../../getUrlForFileByHash.ts"
|
||||
import getClient from "../../getClient.ts"
|
||||
import RecentChat from "lingchair-client-protocol/RecentChat.ts"
|
||||
|
||||
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||
recentChat: RecentChat
|
||||
openChatFragment: (id: string) => void
|
||||
active?: boolean
|
||||
}
|
||||
|
||||
export default function RecentsListItem({ recentChat, openChatFragment, active }: Args) {
|
||||
const { id, title, avatar_file_hash, content } = recentChat
|
||||
export default function RecentsListItem({ recentChat, active, ...props }: Args) {
|
||||
const { id, title, avatar_file_hash, content } = recentChat.bean
|
||||
|
||||
const itemRef = React.useRef<HTMLElement>(null)
|
||||
React.useEffect(() => {
|
||||
@@ -21,9 +20,9 @@ export default function RecentsListItem({ recentChat, openChatFragment, active }
|
||||
<mdui-list-item rounded style={{
|
||||
marginTop: '3px',
|
||||
marginBottom: '3px',
|
||||
}} onClick={() => openChatFragment(id)} active={active} ref={itemRef}>
|
||||
}} active={active} ref={itemRef} {...props}>
|
||||
{title}
|
||||
<Avatar src={getUrlForFileByHash(avatar_file_hash as string)} text={title} slot="icon" />
|
||||
<Avatar src={getClient().getUrlForFileByHash(avatar_file_hash!)} text={title} slot="icon" />
|
||||
<span slot="description"
|
||||
style={{
|
||||
width: "100%",
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
import * as React from 'react'
|
||||
import { Button, Dialog, TextField } from "mdui"
|
||||
import MainSharedContext from '../MainSharedContext'
|
||||
import MainSharedContext, { Shared } from '../MainSharedContext'
|
||||
import showSnackbar from '../../utils/showSnackbar'
|
||||
import showCircleProgressDialog from '../showCircleProgressDialog'
|
||||
import getClient from '../../getClient'
|
||||
import performAuth from '../../performAuth'
|
||||
import { useContextSelector } from 'use-context-selector'
|
||||
import useEventListener from '../../utils/useEventListener'
|
||||
|
||||
export default function RegisterDialog({ ...props }: { open: boolean } & React.HTMLAttributes<Dialog>) {
|
||||
const shared = React.useContext(MainSharedContext)
|
||||
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
||||
setShowRegisterDialog: context.setShowRegisterDialog
|
||||
}))
|
||||
|
||||
const dialogRef = React.useRef<Dialog>()
|
||||
useEventListener(dialogRef, 'closed', () => shared.setShowRegisterDialog(false))
|
||||
|
||||
const registerInputUserNameRef = React.useRef<TextField>(null)
|
||||
const registerInputNickNameRef = React.useRef<TextField>(null)
|
||||
const registerInputPasswordRef = React.useRef<TextField>(null)
|
||||
|
||||
return (
|
||||
<mdui-dialog headline="注册" {...props}>
|
||||
<mdui-dialog headline="注册" {...props} ref={dialogRef}>
|
||||
|
||||
<mdui-text-field label="用户名 (可选)" ref={registerInputUserNameRef}></mdui-text-field>
|
||||
<div style={{
|
||||
|
||||
Reference in New Issue
Block a user