diff --git a/client/ui/Main.tsx b/client/ui/Main.tsx index d17329c..773cb1d 100644 --- a/client/ui/Main.tsx +++ b/client/ui/Main.tsx @@ -24,12 +24,16 @@ export default function Main() { const [showLoginDialog, setShowLoginDialog] = React.useState(false) const [showRegisterDialog, setShowRegisterDialog] = React.useState(false) + // TODO + const [currentSelectedChatId, setCurrentSelectedChatId] = React.useState(false) + const sharedContext = { ui_functions: React.useRef({ }), setShowLoginDialog, setShowRegisterDialog, + } useAsyncEffect(async () => { @@ -78,7 +82,6 @@ export default function Main() { - @@ -102,7 +105,7 @@ export default function Main() { Recents: "最近对话", Contacts: "收藏对话", AllChats: "所有对话", - })['Recents'] + })[currentShowPage] }
{ + display: boolean + currentChatId: string + openChatInfoDialog: (chat: Chat) => void +} + +export default function AllChatsList({ ...props }: React.HTMLAttributes) { + + const searchRef = React.useRef(null) + const [searchText, setSearchText] = React.useState('') + const [allChatsList, setAllChatsList] = React.useState([]) + + useEventListener(searchRef, 'input', (e) => { + setSearchText((e.target as unknown as TextField).value) + }) + + useAsyncEffect(async () => { + async function updateAllChats() { + try { + setAllChatsList(await (await UserMySelf.getMySelfOrThrow(getClient())).getMyAllChatsOrThrow()) + } catch (e) { + if (e instanceof CallbackError) + if (e.code == 401 || e.code == 400) + showSnackbar({ + message: '获取所有对话失败: ' + e.message + }) + } + } + updateAllChats() + return () => { + } + }) + + return + + { + allChatsList.filter((chat) => + searchText == '' || + chat.getTitle().includes(searchText) || + chat.getId().includes(searchText) + ).map((v) => + { + openChatInfoDialog(v) + }} + chat={v} /> + ) + } + +} \ No newline at end of file diff --git a/client/ui/main-page/AllChatsListItem.tsx b/client/ui/main-page/AllChatsListItem.tsx new file mode 100644 index 0000000..156c6ea --- /dev/null +++ b/client/ui/main-page/AllChatsListItem.tsx @@ -0,0 +1,29 @@ +import { $ } from "mdui/jq" +import Avatar from "../Avatar.tsx" +import React from 'react' +import { Chat } from "lingchair-client-protocol" +import getClient from "../../getClient.ts" + +interface Args extends React.HTMLAttributes { + chat: Chat + active?: boolean +} + +export default function AllChatsListItem({ chat, active, ...prop }: Args) { + const title = chat.getTitle() + + const ref = React.useRef(null) + + return ( + + {title} + + + ) +} diff --git a/client/ui/main-page/ContactsList.tsx b/client/ui/main-page/ContactsList.tsx new file mode 100644 index 0000000..3168bc0 --- /dev/null +++ b/client/ui/main-page/ContactsList.tsx @@ -0,0 +1,164 @@ +import React from "react" +import ContactsListItem from "./ContactsListItem.tsx" +import { dialog, Dialog, TextField } from "mdui" + +interface Args extends React.HTMLAttributes { + display: boolean + openChatInfoDialog: (chat: Chat) => void + addContactDialogRef: React.MutableRefObject + createGroupDialogRef: React.MutableRefObject + setSharedFavouriteChats: React.Dispatch> + currentChatId: string +} + +export default function ContactsList({ + display, + openChatInfoDialog, + addContactDialogRef, + createGroupDialogRef, + setSharedFavouriteChats, + currentChatId, + ...props +}: Args) { + const searchRef = React.useRef(null) + const [isMultiSelecting, setIsMultiSelecting] = React.useState(false) + const [searchText, setSearchText] = React.useState('') + const [contactsList, setContactsList] = React.useState([]) + 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 +
+ + addContactDialogRef.current!.open = true}>添加收藏对话 + EventBus.emit('ContactsList.updateContacts')}>刷新 + { + if (isMultiSelecting) + setCheckedList({}) + setIsMultiSelecting(!isMultiSelecting) + }}>{isMultiSelecting ? "关闭多选" : "多选模式"} + { + isMultiSelecting && <> + 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') + } + }) + } + }, + } + ], + })}>删除所选 + + } +
+
+ + { + contactsList.filter((chat) => + searchText == '' || + chat.title.includes(searchText) || + chat.id.includes(searchText) + ).map((v) => + { + if (isMultiSelecting) + setCheckedList({ + ...checkedList, + [v.id]: !checkedList[v.id], + }) + else + openChatInfoDialog(v) + }} + key={v.id} + contact={v} /> + ) + } +
+} \ No newline at end of file diff --git a/client/ui/main-page/ContactsListItem.tsx b/client/ui/main-page/ContactsListItem.tsx new file mode 100644 index 0000000..a03e2da --- /dev/null +++ b/client/ui/main-page/ContactsListItem.tsx @@ -0,0 +1,27 @@ +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 { + contact: Chat + active?: boolean +} + +export default function ContactsListItem({ contact, ...prop }: Args) { + const { id, title, avatar_file_hash } = contact + const ref = React.useRef(null) + + return ( + + {title} + + + ) +} diff --git a/client/ui/main-page/RecentsList.tsx b/client/ui/main-page/RecentsList.tsx new file mode 100644 index 0000000..2a19fc4 --- /dev/null +++ b/client/ui/main-page/RecentsList.tsx @@ -0,0 +1,86 @@ +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 { + display: boolean + currentChatId: string + openChatFragment: (id: string) => void +} + +export default function RecentsList({ + currentChatId, + display, + openChatFragment, + ...props +}: Args) { + const searchRef = React.useRef(null) + const [searchText, setSearchText] = React.useState('') + const [recentsList, setRecentsList] = React.useState([]) + + 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 + + { + recentsList.filter((chat) => + searchText == '' || + chat.title.includes(searchText) || + chat.id.includes(searchText) || + chat.content.includes(searchText) + ).map((v) => + openChatFragment(v.id)} + key={v.id} + recentChat={v} /> + ) + } + +} \ No newline at end of file diff --git a/client/ui/main-page/RecentsListItem.tsx b/client/ui/main-page/RecentsListItem.tsx new file mode 100644 index 0000000..391d540 --- /dev/null +++ b/client/ui/main-page/RecentsListItem.tsx @@ -0,0 +1,37 @@ +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" + +interface Args extends React.HTMLAttributes { + recentChat: RecentChat + openChatFragment: (id: string) => void + active?: boolean +} + +export default function RecentsListItem({ recentChat, openChatFragment, active }: Args) { + const { id, title, avatar_file_hash, content } = recentChat + + const itemRef = React.useRef(null) + React.useEffect(() => { + $(itemRef.current!.shadowRoot).find('.headline').css('margin-top', '3px') + }) + return ( + openChatFragment(id)} active={active} ref={itemRef}> + {title} + + {content} + + ) +}