彻底放弃客户端路由计划
This commit is contained in:
@@ -14,7 +14,6 @@
|
|||||||
"pinch-zoom-element": "1.1.1",
|
"pinch-zoom-element": "1.1.1",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-router": "7.10.1",
|
|
||||||
"socket.io-client": "4.8.1",
|
"socket.io-client": "4.8.1",
|
||||||
"split.js": "1.3.2",
|
"split.js": "1.3.2",
|
||||||
"ua-parser-js": "2.0.6",
|
"ua-parser-js": "2.0.6",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import useEventListener from "../utils/useEventListener.ts"
|
|||||||
import AvatarMySelf from "./AvatarMySelf.tsx"
|
import AvatarMySelf from "./AvatarMySelf.tsx"
|
||||||
import MainSharedContext from './MainSharedContext.ts'
|
import MainSharedContext from './MainSharedContext.ts'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { createBrowserRouter, Outlet, RouterProvider, useNavigate, useRouteError } from "react-router"
|
|
||||||
import LoginDialog from "./main-page/LoginDialog.tsx"
|
import LoginDialog from "./main-page/LoginDialog.tsx"
|
||||||
import useAsyncEffect from "../utils/useAsyncEffect.ts"
|
import useAsyncEffect from "../utils/useAsyncEffect.ts"
|
||||||
import performAuth from "../performAuth.ts"
|
import performAuth from "../performAuth.ts"
|
||||||
@@ -17,20 +16,12 @@ import showSnackbar from "../utils/showSnackbar.ts"
|
|||||||
import AllChatsList from "./main-page/AllChatsList.tsx"
|
import AllChatsList from "./main-page/AllChatsList.tsx"
|
||||||
import FavouriteChatsList from "./main-page/FavouriteChatsList.tsx"
|
import FavouriteChatsList from "./main-page/FavouriteChatsList.tsx"
|
||||||
import RecentChatsList from "./main-page/RecentChatsList.tsx"
|
import RecentChatsList from "./main-page/RecentChatsList.tsx"
|
||||||
import UserOrChatInfoDialog from "./routers/UserOrChatInfoDialog.tsx"
|
|
||||||
import UserOrChatInfoDialogLoader from "./routers/UserOrChatInfoDialogDataLoader.ts"
|
|
||||||
import ChatInfoDialogDataLoader from "./routers/ChatInfoDialogDataLoader.ts"
|
|
||||||
import ChatFragmentDialog from "./routers/ChatFragmentDialog.tsx"
|
|
||||||
import EffectOnly from "./EffectOnly.tsx"
|
|
||||||
import MainSharedReducer from "./MainSharedReducer.ts"
|
import MainSharedReducer from "./MainSharedReducer.ts"
|
||||||
import gotoUserInfo from "./routers/gotoUserInfo.ts"
|
|
||||||
import EditMyProfileDialog from "./routers/EditMyProfileDialog.tsx"
|
|
||||||
import ProgressDialogFallback from "./ProgressDialogFallback.tsx"
|
|
||||||
import Split from 'split.js'
|
import Split from 'split.js'
|
||||||
import data from "../data.ts"
|
import data from "../data.ts"
|
||||||
import LazyChatFragment from "./chat-fragment/LazyChatFragment.tsx"
|
import LazyChatFragment from "./chat-fragment/LazyChatFragment.tsx"
|
||||||
import AddFavourtieChatDialog from "./routers/AddFavourtieChatDialog.tsx"
|
import DialogContextWrapper from "./app-state/AppStateContextWrapper.tsx"
|
||||||
import RouterDialogsContextWrapper from './routers/RouterDialogsContextWrapper.tsx'
|
import { AppState } from "./app-state/AppStateContext.ts"
|
||||||
|
|
||||||
function Root() {
|
function Root() {
|
||||||
const [myProfileCache, setMyProfileCache] = React.useState<UserMySelf>()
|
const [myProfileCache, setMyProfileCache] = React.useState<UserMySelf>()
|
||||||
@@ -59,8 +50,6 @@ function Root() {
|
|||||||
const [showLoginDialog, setShowLoginDialog] = React.useState(false)
|
const [showLoginDialog, setShowLoginDialog] = React.useState(false)
|
||||||
const [showRegisterDialog, setShowRegisterDialog] = React.useState(false)
|
const [showRegisterDialog, setShowRegisterDialog] = React.useState(false)
|
||||||
|
|
||||||
const nav = useNavigate()
|
|
||||||
|
|
||||||
const [state, dispatch] = React.useReducer(MainSharedReducer, {
|
const [state, dispatch] = React.useReducer(MainSharedReducer, {
|
||||||
favouriteChats: [],
|
favouriteChats: [],
|
||||||
currentSelectedChatId: '',
|
currentSelectedChatId: '',
|
||||||
@@ -118,9 +107,11 @@ function Root() {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const AppStateRef = React.useRef<AppState>()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RouterDialogsContextWrapper>
|
|
||||||
<MainSharedContext.Provider value={sharedContext}>
|
<MainSharedContext.Provider value={sharedContext}>
|
||||||
|
<DialogContextWrapper useRef={AppStateRef}>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
@@ -128,17 +119,13 @@ function Root() {
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
height: 'var(--whitesilk-window-height)',
|
height: 'var(--whitesilk-window-height)',
|
||||||
}}>
|
}}>
|
||||||
{
|
|
||||||
// 将子路由渲染到此处
|
|
||||||
<Outlet />
|
|
||||||
}
|
|
||||||
<LoginDialog open={showLoginDialog} />
|
<LoginDialog open={showLoginDialog} />
|
||||||
<RegisterDialog open={showRegisterDialog} />
|
<RegisterDialog open={showRegisterDialog} />
|
||||||
<mdui-navigation-drawer ref={drawerRef} modal close-on-esc close-on-overlay-click>
|
<mdui-navigation-drawer ref={drawerRef} modal close-on-esc close-on-overlay-click>
|
||||||
<mdui-list style={{
|
<mdui-list style={{
|
||||||
padding: '10px',
|
padding: '10px',
|
||||||
}}>
|
}}>
|
||||||
<mdui-list-item rounded onClick={() => gotoUserInfo(nav, myProfileCache!.getId())}>
|
<mdui-list-item rounded onClick={() => AppStateRef.current!.openUserInfo(myProfileCache!.getId())}>
|
||||||
<span>{myProfileCache?.getNickName()}</span>
|
<span>{myProfileCache?.getNickName()}</span>
|
||||||
<AvatarMySelf slot="icon" />
|
<AvatarMySelf slot="icon" />
|
||||||
</mdui-list-item>
|
</mdui-list-item>
|
||||||
@@ -147,7 +134,7 @@ function Root() {
|
|||||||
margin: '10px',
|
margin: '10px',
|
||||||
}}></mdui-divider>
|
}}></mdui-divider>
|
||||||
<mdui-list-item rounded icon="settings">客户端设置</mdui-list-item>
|
<mdui-list-item rounded icon="settings">客户端设置</mdui-list-item>
|
||||||
<mdui-list-item rounded icon="person_add" onClick={() => nav('/add/favourite_chat')}>添加收藏对话</mdui-list-item>
|
<mdui-list-item rounded icon="person_add" onClick={() => AppStateRef.current!.openAddFavouriteChat()}>添加收藏对话</mdui-list-item>
|
||||||
<mdui-list-item rounded icon="group_add">创建新的群组</mdui-list-item>
|
<mdui-list-item rounded icon="group_add">创建新的群组</mdui-list-item>
|
||||||
</mdui-list>
|
</mdui-list>
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -229,7 +216,7 @@ function Root() {
|
|||||||
}}>
|
}}>
|
||||||
{
|
{
|
||||||
(state.currentSelectedChatId && state.currentSelectedChatId != '')
|
(state.currentSelectedChatId && state.currentSelectedChatId != '')
|
||||||
? <LazyChatFragment openedWithRouter={false} chatId={state.currentSelectedChatId!} />
|
? <LazyChatFragment openedInDialog={false} chatId={state.currentSelectedChatId!} />
|
||||||
: <div style={{
|
: <div style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
@@ -255,69 +242,11 @@ function Root() {
|
|||||||
</mdui-navigation-bar>
|
</mdui-navigation-bar>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</DialogContextWrapper>
|
||||||
</MainSharedContext.Provider>
|
</MainSharedContext.Provider>
|
||||||
</RouterDialogsContextWrapper>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SnackbarErrorBoundary() {
|
|
||||||
const error = useRouteError()
|
|
||||||
return <EffectOnly effect={() => {
|
|
||||||
const d = dialog({
|
|
||||||
headline: "错误",
|
|
||||||
description: error instanceof Error ? ('[' + error.name + '] ' + (error.stack || error.message)) : error + '',
|
|
||||||
closeOnEsc: true,
|
|
||||||
closeOnOverlayClick: true,
|
|
||||||
})
|
|
||||||
return () => {
|
|
||||||
d.open = false
|
|
||||||
}
|
|
||||||
}} deps={[]} />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Main() {
|
export default function Main() {
|
||||||
const router = createBrowserRouter([{
|
return <Root />
|
||||||
path: "/",
|
|
||||||
Component: Root,
|
|
||||||
hydrateFallbackElement: <ProgressDialogFallback text="请稍后..." />,
|
|
||||||
ErrorBoundary: SnackbarErrorBoundary,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'info/:type',
|
|
||||||
Component: UserOrChatInfoDialog,
|
|
||||||
loader: UserOrChatInfoDialogLoader,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'add',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'favourite_chat',
|
|
||||||
Component: AddFavourtieChatDialog,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'settings',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'edit_profile',
|
|
||||||
Component: EditMyProfileDialog,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'chat',
|
|
||||||
Component: ChatFragmentDialog,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'info',
|
|
||||||
Component: UserOrChatInfoDialog,
|
|
||||||
loader: ChatInfoDialogDataLoader,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}])
|
|
||||||
|
|
||||||
return <RouterProvider router={router} />
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { $, Tab, TextField } from "mdui"
|
|||||||
import useEventListener from "../../utils/useEventListener"
|
import useEventListener from "../../utils/useEventListener"
|
||||||
import useEffectRef from "../../utils/useEffectRef"
|
import useEffectRef from "../../utils/useEffectRef"
|
||||||
import isMobileUI from "../../utils/isMobileUI"
|
import isMobileUI from "../../utils/isMobileUI"
|
||||||
import { Outlet, useLocation, useNavigate, NavigateFunction } from "react-router"
|
|
||||||
import { Chat } from "lingchair-client-protocol"
|
import { Chat } from "lingchair-client-protocol"
|
||||||
import Preference from "../preference/Preference"
|
import Preference from "../preference/Preference"
|
||||||
import PreferenceHeader from "../preference/PreferenceHeader"
|
import PreferenceHeader from "../preference/PreferenceHeader"
|
||||||
@@ -12,11 +11,7 @@ import SwitchPreference from "../preference/SwitchPreference"
|
|||||||
import TextFieldPreference from "../preference/TextFieldPreference"
|
import TextFieldPreference from "../preference/TextFieldPreference"
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import ChatMessageContainer from "./ChatMessageContainer"
|
import ChatMessageContainer from "./ChatMessageContainer"
|
||||||
import gotoChatInfo from "../routers/gotoChatInfo"
|
import AppStateContext from "../app-state/AppStateContext"
|
||||||
|
|
||||||
function gotoChatInfo2(nav: NavigateFunction, id: string, useWithRouterChatFragment?: boolean) {
|
|
||||||
useWithRouterChatFragment ? nav('/chat/info?id=' + id) : gotoChatInfo(nav, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MduiTabFitSizeArgs extends React.HTMLAttributes<HTMLElement & Tab> {
|
interface MduiTabFitSizeArgs extends React.HTMLAttributes<HTMLElement & Tab> {
|
||||||
value: string
|
value: string
|
||||||
@@ -32,13 +27,12 @@ function MduiTabFitSize({ children, ...props }: MduiTabFitSizeArgs) {
|
|||||||
|
|
||||||
export default function ChatFragment({
|
export default function ChatFragment({
|
||||||
chatInfo,
|
chatInfo,
|
||||||
openedWithRouter,
|
openedInDialog,
|
||||||
}: {
|
}: {
|
||||||
chatInfo: Chat
|
chatInfo: Chat
|
||||||
openedWithRouter: boolean
|
openedInDialog: boolean
|
||||||
}) {
|
}) {
|
||||||
const nav = useNavigate()
|
const AppState = React.useContext(AppStateContext)
|
||||||
|
|
||||||
const [tabItemSelected, setTabItemSelected] = React.useState('Chat')
|
const [tabItemSelected, setTabItemSelected] = React.useState('Chat')
|
||||||
const tabRef = React.useRef<Tab>()
|
const tabRef = React.useRef<Tab>()
|
||||||
useEventListener(tabRef, 'change', () => {
|
useEventListener(tabRef, 'change', () => {
|
||||||
@@ -66,7 +60,7 @@ export default function ChatFragment({
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
}}>
|
}}>
|
||||||
{
|
{
|
||||||
openedWithRouter && <mdui-button-icon icon="arrow_back" onClick={() => nav(-1)} style={{
|
openedInDialog && <mdui-button-icon icon="arrow_back" onClick={() => AppState.closeChat()} style={{
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
marginLeft: '5px',
|
marginLeft: '5px',
|
||||||
marginRight: '5px',
|
marginRight: '5px',
|
||||||
@@ -107,7 +101,7 @@ export default function ChatFragment({
|
|||||||
marginLeft: '5px',
|
marginLeft: '5px',
|
||||||
marginRight: '5px',
|
marginRight: '5px',
|
||||||
}}></mdui-button-icon>
|
}}></mdui-button-icon>
|
||||||
<mdui-button-icon icon="info" onClick={() => gotoChatInfo2(nav, chatInfo.getId(), openedWithRouter)} style={{
|
<mdui-button-icon icon="info" onClick={() => AppState.openChatInfo(chatInfo.getId())} style={{
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
marginLeft: '5px',
|
marginLeft: '5px',
|
||||||
marginRight: '5px',
|
marginRight: '5px',
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import ChatFragment from "./ChatFragment"
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import EffectOnly from "../EffectOnly"
|
import EffectOnly from "../EffectOnly"
|
||||||
|
|
||||||
export default function LazyChatFragment({ chatId, openedWithRouter }: { chatId: string, openedWithRouter: boolean }) {
|
export default function LazyChatFragment({ chatId, openedInDialog }: { chatId: string, openedInDialog: boolean }) {
|
||||||
return <React.Suspense fallback={<EffectOnly effect={() => {}} deps={[]} />}>
|
return <React.Suspense fallback={<EffectOnly effect={() => {}} deps={[]} />}>
|
||||||
<Await
|
<Await
|
||||||
resolve={React.useMemo(() => Chat.getByIdOrThrow(getClient(), chatId), [chatId])}
|
resolve={React.useMemo(() => Chat.getByIdOrThrow(getClient(), chatId), [chatId])}
|
||||||
children={(chatInfo: Chat) => <ChatFragment chatInfo={chatInfo} openedWithRouter={openedWithRouter} />} />
|
children={(chatInfo: Chat) => <ChatFragment chatInfo={chatInfo} openedInDialog={openedInDialog} />} />
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import isMobileUI from "../../utils/isMobileUI.ts"
|
|||||||
import { useContextSelector } from "use-context-selector"
|
import { useContextSelector } from "use-context-selector"
|
||||||
import MainSharedContext, { Shared } from "../MainSharedContext.ts"
|
import MainSharedContext, { Shared } from "../MainSharedContext.ts"
|
||||||
import ClientCache from "../../ClientCache.ts"
|
import ClientCache from "../../ClientCache.ts"
|
||||||
import gotoChatInfo from "../routers/gotoChatInfo.ts"
|
import AppStateContext from "../app-state/AppStateContext.ts"
|
||||||
import { useNavigate } from "react-router"
|
|
||||||
|
|
||||||
export default function AllChatsList({ ...props }: React.HTMLAttributes<HTMLElement>) {
|
export default function AllChatsList({ ...props }: React.HTMLAttributes<HTMLElement>) {
|
||||||
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
||||||
@@ -22,7 +21,7 @@ export default function AllChatsList({ ...props }: React.HTMLAttributes<HTMLElem
|
|||||||
const [searchText, setSearchText] = React.useState('')
|
const [searchText, setSearchText] = React.useState('')
|
||||||
const [allChatsList, setAllChatsList] = React.useState<Chat[]>([])
|
const [allChatsList, setAllChatsList] = React.useState<Chat[]>([])
|
||||||
|
|
||||||
const nav = useNavigate()
|
const DialogState = React.useContext(AppStateContext)
|
||||||
|
|
||||||
useEventListener(searchRef, 'input', (e) => {
|
useEventListener(searchRef, 'input', (e) => {
|
||||||
setSearchText((e.target as unknown as TextField).value)
|
setSearchText((e.target as unknown as TextField).value)
|
||||||
@@ -76,7 +75,7 @@ export default function AllChatsList({ ...props }: React.HTMLAttributes<HTMLElem
|
|||||||
active={isMobileUI() ? false : shared.state.currentSelectedChatId == v.getId()}
|
active={isMobileUI() ? false : shared.state.currentSelectedChatId == v.getId()}
|
||||||
key={v.getId()}
|
key={v.getId()}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
gotoChatInfo(nav, v.getId())
|
DialogState.openChatInfo(v.getId())
|
||||||
}}
|
}}
|
||||||
chat={v} />
|
chat={v} />
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import isMobileUI from "../../utils/isMobileUI.ts"
|
|||||||
import gotoChatInfo from "../routers/gotoChatInfo.ts"
|
import gotoChatInfo from "../routers/gotoChatInfo.ts"
|
||||||
import ClientCache from "../../ClientCache.ts"
|
import ClientCache from "../../ClientCache.ts"
|
||||||
import { useNavigate } from "react-router"
|
import { useNavigate } from "react-router"
|
||||||
|
import AppStateContext from "../app-state/AppStateContext.ts"
|
||||||
|
|
||||||
export default function FavouriteChatsList({ ...props }: React.HTMLAttributes<HTMLElement>) {
|
export default function FavouriteChatsList({ ...props }: React.HTMLAttributes<HTMLElement>) {
|
||||||
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
||||||
@@ -24,7 +25,7 @@ export default function FavouriteChatsList({ ...props }: React.HTMLAttributes<HT
|
|||||||
const [searchText, setSearchText] = React.useState('')
|
const [searchText, setSearchText] = React.useState('')
|
||||||
const [checkedList, setCheckedList] = React.useState<{ [key: string]: boolean }>({})
|
const [checkedList, setCheckedList] = React.useState<{ [key: string]: boolean }>({})
|
||||||
|
|
||||||
const nav = useNavigate()
|
const DialogState = React.useContext(AppStateContext)
|
||||||
|
|
||||||
useEventListener(searchRef, 'input', (e) => {
|
useEventListener(searchRef, 'input', (e) => {
|
||||||
setSearchText((e.target as unknown as TextField).value)
|
setSearchText((e.target as unknown as TextField).value)
|
||||||
@@ -74,7 +75,7 @@ export default function FavouriteChatsList({ ...props }: React.HTMLAttributes<HT
|
|||||||
<mdui-list-item rounded style={{
|
<mdui-list-item rounded style={{
|
||||||
marginTop: '13px',
|
marginTop: '13px',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}} icon="person_add" onClick={() => nav('/add/favourite_chat')}>添加收藏</mdui-list-item>
|
}} icon="person_add" onClick={() => DialogState.openAddFavouriteChat()}>添加收藏</mdui-list-item>
|
||||||
<mdui-list-item rounded style={{
|
<mdui-list-item rounded style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}} icon="refresh" onClick={() => shared.functions_lazy.current.updateFavouriteChats()}>刷新列表</mdui-list-item>
|
}} icon="refresh" onClick={() => shared.functions_lazy.current.updateFavouriteChats()}>刷新列表</mdui-list-item>
|
||||||
@@ -161,7 +162,7 @@ export default function FavouriteChatsList({ ...props }: React.HTMLAttributes<HT
|
|||||||
[v.getId()]: !checkedList[v.getId()],
|
[v.getId()]: !checkedList[v.getId()],
|
||||||
})
|
})
|
||||||
else
|
else
|
||||||
gotoChatInfo(nav, v.getId())
|
DialogState.openChatInfo(v.getId())
|
||||||
}}
|
}}
|
||||||
key={v.getId()}
|
key={v.getId()}
|
||||||
chat={v} />
|
chat={v} />
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import * as React from 'react'
|
|
||||||
import { Button, Dialog, snackbar, TextField } from "mdui"
|
|
||||||
import { data, useNavigate } 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'
|
|
||||||
import ClientCache from '../../ClientCache'
|
|
||||||
import useRouterDialogRef from './useRouterDialogRef'
|
|
||||||
|
|
||||||
export default function AddFavourtieChatDialog({ ...props }: { open: boolean } & React.HTMLAttributes<Dialog>) {
|
|
||||||
const dialogRef = useRouterDialogRef()
|
|
||||||
const nav = useNavigate()
|
|
||||||
|
|
||||||
const inputTargetRef = React.useRef<TextField>(null)
|
|
||||||
|
|
||||||
async function addFavouriteChat() {
|
|
||||||
try {
|
|
||||||
await (await ClientCache.getMySelf())!.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={() => nav(-1)}>取消</mdui-button>
|
|
||||||
<mdui-button slot="action" variant="text" onClick={() => addFavouriteChat()}>添加</mdui-button>
|
|
||||||
</mdui-dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { useSearchParams, Outlet } from "react-router"
|
|
||||||
import useRouterDialogRef from "./useRouterDialogRef"
|
|
||||||
import * as React from 'react'
|
|
||||||
import LazyChatFragment from "../chat-fragment/LazyChatFragment"
|
|
||||||
import useEventListener from "../../utils/useEventListener"
|
|
||||||
import useAsyncEffect from "../../utils/useAsyncEffect"
|
|
||||||
import sleep from "../../utils/sleep"
|
|
||||||
|
|
||||||
export default function ChatFragmentDialog() {
|
|
||||||
const [searchParams] = useSearchParams()
|
|
||||||
const id = searchParams.get('id')
|
|
||||||
|
|
||||||
const dialogRef = useRouterDialogRef()
|
|
||||||
|
|
||||||
useEventListener(dialogRef, 'open', () => {
|
|
||||||
const shadow = dialogRef.current!.shadowRoot as ShadowRoot
|
|
||||||
const panel = shadow.querySelector(".panel") as HTMLElement
|
|
||||||
panel.style.padding = '0'
|
|
||||||
panel.style.color = 'inherit'
|
|
||||||
panel.style.backgroundColor = 'rgb(var(--mdui-color-background))'
|
|
||||||
panel.style.setProperty('--mdui-color-background', 'inherit')
|
|
||||||
const body = shadow.querySelector(".body") as HTMLElement
|
|
||||||
body.style.height = '100%'
|
|
||||||
body.style.display = 'flex'
|
|
||||||
})
|
|
||||||
|
|
||||||
return (<>
|
|
||||||
<mdui-dialog fullscreen ref={dialogRef}>
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
width: '100%',
|
|
||||||
}}>
|
|
||||||
<LazyChatFragment chatId={id!} openedWithRouter={true} />
|
|
||||||
</div>
|
|
||||||
</mdui-dialog>
|
|
||||||
<Outlet />
|
|
||||||
</>)
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { dialog, Dialog } from "mdui"
|
|
||||||
import Avatar from "../Avatar.tsx"
|
|
||||||
import { CallbackError, Chat } from 'lingchair-client-protocol'
|
|
||||||
import { data, useLocation, useNavigate, useSearchParams } from 'react-router'
|
|
||||||
import useAsyncEffect from '../../utils/useAsyncEffect.ts'
|
|
||||||
import { useContextSelector } from 'use-context-selector'
|
|
||||||
import MainSharedContext, { Shared } from '../MainSharedContext.ts'
|
|
||||||
import getClient from '../../getClient.ts'
|
|
||||||
import useEventListener from '../../utils/useEventListener.ts'
|
|
||||||
import showSnackbar from '../../utils/showSnackbar.ts'
|
|
||||||
|
|
||||||
export default function ChatInfoDialog({ ...props }: React.HTMLAttributes<HTMLElement>) {
|
|
||||||
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
|
||||||
myProfileCache: context.myProfileCache,
|
|
||||||
favouriteChats: context.favouriteChats,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const [chat, setChat] = React.useState<Chat>()
|
|
||||||
const [userId, setUserId] = React.useState<string>()
|
|
||||||
|
|
||||||
const [searchParams] = useSearchParams()
|
|
||||||
let pathName = useLocation().pathname
|
|
||||||
const navigate = useNavigate()
|
|
||||||
function back() {
|
|
||||||
navigate('/')
|
|
||||||
}
|
|
||||||
|
|
||||||
const dialogRef = React.useRef<Dialog>()
|
|
||||||
useEventListener(dialogRef, 'overlay-click', () => back())
|
|
||||||
const id = searchParams.get('id')
|
|
||||||
|
|
||||||
const [favourited, setIsFavourited] = React.useState(false)
|
|
||||||
React.useEffect(() => {
|
|
||||||
setIsFavourited(shared.favouriteChats.map((v) => v.getId()).indexOf(chat?.getId() || '') != -1)
|
|
||||||
}, [chat, shared])
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
console.log("挂载喵!", pathName)
|
|
||||||
return () => console.log("被抛弃了喵!", pathName)
|
|
||||||
}, [pathName])
|
|
||||||
|
|
||||||
const isUser = pathName.startsWith('/info/user')
|
|
||||||
useAsyncEffect(async () => {
|
|
||||||
console.log(id, pathName)
|
|
||||||
try {
|
|
||||||
if (isUser) {
|
|
||||||
setChat(await Chat.getOrCreatePrivateChatOrThrow(getClient(), id!))
|
|
||||||
setUserId(id!)
|
|
||||||
} else
|
|
||||||
setChat(await Chat.getByIdOrThrow(getClient(), id!))
|
|
||||||
dialogRef.current!.open = true
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof CallbackError)
|
|
||||||
showSnackbar({
|
|
||||||
message: '打开资料卡失败: ' + e.message
|
|
||||||
})
|
|
||||||
console.log(e)
|
|
||||||
back()
|
|
||||||
}
|
|
||||||
}, [id, isUser])
|
|
||||||
|
|
||||||
const avatarUrl = getClient().getUrlForFileByHash(chat?.getAvatarFileHash())!
|
|
||||||
|
|
||||||
return (
|
|
||||||
<mdui-dialog ref={dialogRef}>
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
<Avatar src={avatarUrl} text={chat?.getTitle()} style={{
|
|
||||||
width: '50px',
|
|
||||||
height: '50px',
|
|
||||||
}} onClick={() => avatarUrl && openImageViewer(avatarUrl)} />
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
marginLeft: '15px',
|
|
||||||
marginRight: '15px',
|
|
||||||
fontSize: '16.5px',
|
|
||||||
flexDirection: 'column',
|
|
||||||
}}>
|
|
||||||
<span style={{
|
|
||||||
fontSize: '16.5px'
|
|
||||||
}}>{chat?.getTitle()}</span>
|
|
||||||
<span style={{
|
|
||||||
fontSize: '10.5px',
|
|
||||||
marginTop: '3px',
|
|
||||||
color: 'rgb(var(--mdui-color-secondary))',
|
|
||||||
}}>({chat?.getType()}) ID: {chat?.getType() == 'private' ? userId : chat?.getId()}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<mdui-divider style={{
|
|
||||||
marginTop: "10px",
|
|
||||||
}}></mdui-divider>
|
|
||||||
|
|
||||||
<mdui-list>
|
|
||||||
<mdui-list-item icon={favourited ? "favorite_border" : "favorite"} rounded onClick={() => dialog({
|
|
||||||
headline: favourited ? "取消收藏对话" : "收藏对话",
|
|
||||||
description: favourited ? "确定从收藏对话列表中移除吗? (虽然这不会导致聊天记录丢失)" : "确定要添加到收藏对话列表吗?",
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
text: "取消",
|
|
||||||
onClick: () => {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "确定",
|
|
||||||
onClick: () => {
|
|
||||||
; (async () => {
|
|
||||||
const re = await Client.invoke(favourited ? "User.removeContacts" : "User.addContacts", {
|
|
||||||
token: data.access_token,
|
|
||||||
targets: [
|
|
||||||
chat!.id
|
|
||||||
],
|
|
||||||
})
|
|
||||||
if (re.code != 200)
|
|
||||||
checkApiSuccessOrSncakbar(re, favourited ? "取消收藏失败" : "收藏失败")
|
|
||||||
EventBus.emit('ContactsList.updateContacts')
|
|
||||||
})()
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
})}>{favourited ? '取消收藏' : '收藏对话'}</mdui-list-item>
|
|
||||||
<mdui-list-item icon="chat" rounded onClick={() => {
|
|
||||||
chatInfoDialogRef.current!.open = false
|
|
||||||
openChatFragment(chat!.id)
|
|
||||||
}}>打开对话</mdui-list-item>
|
|
||||||
</mdui-list>
|
|
||||||
</mdui-dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { LoaderFunctionArgs } from "react-router"
|
|
||||||
import getClient from "../../getClient"
|
|
||||||
import { Chat } from "lingchair-client-protocol"
|
|
||||||
import ClientCache from "../../ClientCache"
|
|
||||||
|
|
||||||
export default async function ChatInfoDialogDataLoader({ params, request }: LoaderFunctionArgs) {
|
|
||||||
const searchParams = new URL(request.url).searchParams
|
|
||||||
|
|
||||||
let id = searchParams.get('id')
|
|
||||||
const chat = await Chat.getByIdOrThrow(getClient(), id!)
|
|
||||||
|
|
||||||
if (chat.getType() == 'private')
|
|
||||||
id = await chat.getTheOtherUserIdOrThrow()
|
|
||||||
|
|
||||||
return {
|
|
||||||
mySelf: await ClientCache.getMySelf(),
|
|
||||||
chat,
|
|
||||||
id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import { CallbackError, UserMySelf } from "lingchair-client-protocol"
|
|
||||||
import ClientCache from "../../ClientCache"
|
|
||||||
import AvatarMySelf from "../AvatarMySelf"
|
|
||||||
import useRouterDialogRef from "./useRouterDialogRef"
|
|
||||||
import useAsyncEffect from "../../utils/useAsyncEffect"
|
|
||||||
import getClient from "../../getClient"
|
|
||||||
import { useNavigate } from "react-router"
|
|
||||||
import showSnackbar from "../../utils/showSnackbar"
|
|
||||||
import useEventListener from "../../utils/useEventListener"
|
|
||||||
import { TextField } from "mdui"
|
|
||||||
import * as React from 'react'
|
|
||||||
|
|
||||||
export default function EditMyProfileDialog() {
|
|
||||||
const dialogRef = useRouterDialogRef()
|
|
||||||
const nav = useNavigate()
|
|
||||||
|
|
||||||
const [mySelf, setMySelf] = React.useState<UserMySelf>()
|
|
||||||
useAsyncEffect(async () => setMySelf(await ClientCache.getMySelf() as UserMySelf))
|
|
||||||
|
|
||||||
const chooseAvatarFileRef = React.useRef<HTMLInputElement>(null)
|
|
||||||
|
|
||||||
const editNickNameRef = React.useRef<TextField>(null)
|
|
||||||
const editUserNameRef = React.useRef<TextField>(null)
|
|
||||||
|
|
||||||
useEventListener(chooseAvatarFileRef, 'change', async (_) => {
|
|
||||||
const file = chooseAvatarFileRef.current!.files?.[0] as File
|
|
||||||
if (file == null) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
const hash = await getClient().uploadFile({
|
|
||||||
fileName: 'UserAvatar',
|
|
||||||
fileData: file,
|
|
||||||
})
|
|
||||||
await mySelf?.setAvatarFileHashOrThrow(hash)
|
|
||||||
showSnackbar({
|
|
||||||
message: "修改成功, 刷新页面以更新",
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
if (e instanceof CallbackError)
|
|
||||||
showSnackbar({
|
|
||||||
message: '上传头像失败: ' + e.message
|
|
||||||
})
|
|
||||||
showSnackbar({
|
|
||||||
message: '上传头像失败: ' + (e instanceof Error ? e.message : e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<mdui-dialog close-on-overlay-click close-on-esc ref={dialogRef}>
|
|
||||||
<div style={{
|
|
||||||
display: "none"
|
|
||||||
}}>
|
|
||||||
<input type="file" name="选择头像" ref={chooseAvatarFileRef}
|
|
||||||
accept="image/*" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
<AvatarMySelf onClick={() => {
|
|
||||||
chooseAvatarFileRef.current!.value = ''
|
|
||||||
chooseAvatarFileRef.current!.click()
|
|
||||||
}} style={{
|
|
||||||
width: '50px',
|
|
||||||
height: '50px',
|
|
||||||
}} />
|
|
||||||
<mdui-text-field variant="outlined" placeholder="昵称" ref={editNickNameRef} style={{
|
|
||||||
marginLeft: "15px",
|
|
||||||
}} value={mySelf?.getNickName()}></mdui-text-field>
|
|
||||||
</div>
|
|
||||||
<mdui-divider style={{
|
|
||||||
marginTop: "10px",
|
|
||||||
}}></mdui-divider>
|
|
||||||
|
|
||||||
<mdui-text-field style={{ marginTop: "10px", }} variant="outlined" label="用户 ID" value={mySelf?.getId() || ''} readonly onClick={(e: MouseEvent) => {
|
|
||||||
const input = e.target as HTMLInputElement
|
|
||||||
input.select()
|
|
||||||
input.setSelectionRange(0, 1145141919810)
|
|
||||||
}}></mdui-text-field>
|
|
||||||
<mdui-text-field style={{ marginTop: "20px", }} variant="outlined" label="用户名" value={mySelf?.getUserName() || ''} ref={editUserNameRef}></mdui-text-field>
|
|
||||||
|
|
||||||
<mdui-button slot="action" variant="text" onClick={() => nav(-1)}>取消</mdui-button>
|
|
||||||
<mdui-button slot="action" variant="text" onClick={async () => {
|
|
||||||
try {
|
|
||||||
await mySelf?.updateProfileOrThrow({
|
|
||||||
nickname: editNickNameRef.current?.value,
|
|
||||||
username: editUserNameRef.current?.value,
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof CallbackError)
|
|
||||||
showSnackbar({
|
|
||||||
message: '更新资料失败: ' + e.message
|
|
||||||
})
|
|
||||||
}
|
|
||||||
showSnackbar({
|
|
||||||
message: "修改成功, 刷新页面以更新",
|
|
||||||
})
|
|
||||||
}}>更新</mdui-button>
|
|
||||||
</mdui-dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { Dialog } from 'mdui'
|
|
||||||
import * as React from 'react'
|
|
||||||
|
|
||||||
const RouterDialogsContext = React.createContext((ref: React.MutableRefObject<Dialog>) => {})
|
|
||||||
|
|
||||||
export default RouterDialogsContext
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { Dialog } from 'mdui'
|
|
||||||
import * as React from 'react'
|
|
||||||
import RouterDialogsContext from './RouterDialogsContext'
|
|
||||||
import { BlockerFunction, useBlocker, useNavigate } from "react-router"
|
|
||||||
import sleep from "../../utils/sleep"
|
|
||||||
|
|
||||||
const routerDialogsList: React.MutableRefObject<Dialog>[] = []
|
|
||||||
|
|
||||||
export default function RouterDialogsContextWrapper({ children }: React.HTMLAttributes<HTMLElement>) {
|
|
||||||
const proceedRef = React.useRef<() => void>()
|
|
||||||
const nav = useNavigate()
|
|
||||||
// 进入子路由不会拦截上一个路由对话框的关闭
|
|
||||||
// 没有路由对话框不会拦截
|
|
||||||
const blocker = useBlocker(React.useCallback<BlockerFunction>(({ nextLocation, currentLocation }) => {
|
|
||||||
// 只有当有对话框时,才检查路由变化
|
|
||||||
if (routerDialogsList.length === 0) {
|
|
||||||
return false // 没有对话框,允许所有导航
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否是同一个路由
|
|
||||||
if (nextLocation.pathname === currentLocation.pathname) {
|
|
||||||
return false // 相同路由,允许
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否是子路由
|
|
||||||
if (nextLocation.pathname.startsWith(currentLocation.pathname + '/')) {
|
|
||||||
return false // 是子路由,允许
|
|
||||||
}
|
|
||||||
|
|
||||||
// 其他情况:阻止导航
|
|
||||||
return true
|
|
||||||
}, []))
|
|
||||||
|
|
||||||
// 避免用户手动返回导致动画丢失
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (blocker.state === "blocked") {
|
|
||||||
console.log(location)
|
|
||||||
console.log(routerDialogsList[routerDialogsList.length - 1].current)
|
|
||||||
console.log(blocker)
|
|
||||||
proceedRef.current = blocker.proceed
|
|
||||||
// 这个让姐姐来就好啦
|
|
||||||
routerDialogsList.length != 0 && (routerDialogsList[routerDialogsList.length - 1].current!.open = false)
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
blocker.reset?.()
|
|
||||||
}
|
|
||||||
}, [blocker.state])
|
|
||||||
|
|
||||||
// 注册
|
|
||||||
// 理应在 Effect 里
|
|
||||||
function registerRouterDialog(ref: React.MutableRefObject<Dialog>) {
|
|
||||||
routerDialogsList.push(ref)
|
|
||||||
// 正常情况下不可能同时关掉两个对话框
|
|
||||||
// 不过要是真有的话, 再说吧
|
|
||||||
ref.current!.addEventListener('closed', async () => {
|
|
||||||
routerDialogsList.splice(routerDialogsList.length - 1, 1)
|
|
||||||
await sleep(10)
|
|
||||||
proceedRef.current ? proceedRef.current() : nav(-1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return <RouterDialogsContext.Provider value={registerRouterDialog}>
|
|
||||||
{ children }
|
|
||||||
</RouterDialogsContext.Provider>
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
import { dialog } from "mdui"
|
|
||||||
import useRouterDialogRef from "./useRouterDialogRef"
|
|
||||||
import { useLoaderData, useNavigate } from "react-router"
|
|
||||||
import { CallbackError } from "lingchair-client-protocol"
|
|
||||||
import showSnackbar from "../../utils/showSnackbar"
|
|
||||||
import Avatar from "../Avatar"
|
|
||||||
import { useContextSelector } from "use-context-selector"
|
|
||||||
import MainSharedContext, { Shared } from "../MainSharedContext"
|
|
||||||
import * as React from 'react'
|
|
||||||
import UserOrChatInfoDialogLoader from "./UserOrChatInfoDialogDataLoader"
|
|
||||||
import ClientCache from "../../ClientCache"
|
|
||||||
import getClient from "../../getClient"
|
|
||||||
import gotoChat from "./gotoChat"
|
|
||||||
import isMobileUI from "../../utils/isMobileUI"
|
|
||||||
|
|
||||||
export default function UserOrChatInfoDialog() {
|
|
||||||
const favouriteChats = useContextSelector(
|
|
||||||
MainSharedContext,
|
|
||||||
(context: Shared) => context.state.favouriteChats
|
|
||||||
)
|
|
||||||
const setCurrentSelectedChatId = useContextSelector(
|
|
||||||
MainSharedContext,
|
|
||||||
(context: Shared) => context.setCurrentSelectedChatId
|
|
||||||
)
|
|
||||||
|
|
||||||
const nav = useNavigate()
|
|
||||||
|
|
||||||
const dialogRef = useRouterDialogRef()
|
|
||||||
const { chat, id, mySelf } = useLoaderData<typeof UserOrChatInfoDialogLoader>()
|
|
||||||
|
|
||||||
const isMySelf = mySelf?.getId() == id
|
|
||||||
|
|
||||||
const favourited = React.useMemo(() => favouriteChats.map((v) => v.getId()).indexOf(chat.getId() || '') != -1, [chat, favouriteChats])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<mdui-dialog close-on-overlay-click close-on-esc ref={dialogRef}>
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
<Avatar src={getClient().getUrlForFileByHash(chat.getAvatarFileHash())} text={chat.getTitle()} style={{
|
|
||||||
width: '50px',
|
|
||||||
height: '50px',
|
|
||||||
}} />
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
marginLeft: '15px',
|
|
||||||
marginRight: '15px',
|
|
||||||
fontSize: '16.5px',
|
|
||||||
flexDirection: 'column',
|
|
||||||
wordBreak: 'break-word',
|
|
||||||
}}>
|
|
||||||
<span style={{
|
|
||||||
fontSize: '16.5px'
|
|
||||||
}}>{chat.getTitle() + (isMySelf ? ' (我)' : '')}</span>
|
|
||||||
<span style={{
|
|
||||||
fontSize: '10.5px',
|
|
||||||
marginTop: '3px',
|
|
||||||
color: 'rgb(var(--mdui-color-secondary))',
|
|
||||||
}}>({chat.getType()}) ID: {chat.getType() == 'private' ? id : chat.getId()}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<mdui-divider style={{
|
|
||||||
marginTop: "10px",
|
|
||||||
}}></mdui-divider>
|
|
||||||
<mdui-list>
|
|
||||||
{
|
|
||||||
isMySelf && <mdui-list-item icon="edit" rounded onClick={() => nav('/settings/edit_profile')}>
|
|
||||||
编辑资料
|
|
||||||
</mdui-list-item>
|
|
||||||
}
|
|
||||||
{
|
|
||||||
!isMySelf && <mdui-list-item icon={favourited ? "favorite_border" : "favorite"} rounded onClick={() => dialog({
|
|
||||||
headline: favourited ? "取消收藏对话" : "收藏对话",
|
|
||||||
description: favourited ? "确定从收藏对话列表中移除吗? (虽然这不会导致聊天记录丢失)" : "确定要添加到收藏对话列表吗?",
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
text: "取消",
|
|
||||||
onClick: () => {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "确定",
|
|
||||||
onClick: () => {
|
|
||||||
; (async () => {
|
|
||||||
try {
|
|
||||||
if (favourited)
|
|
||||||
await (await ClientCache.getMySelf())!.removeFavouriteChatsOrThrow([chat.getId()])
|
|
||||||
else
|
|
||||||
await (await ClientCache.getMySelf())!.addFavouriteChatsOrThrow([chat.getId()])
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof CallbackError)
|
|
||||||
showSnackbar({
|
|
||||||
message: (favourited ? "取消收藏对话" : "收藏对话") + '失败: ' + e.message
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
})}>{favourited ? '取消收藏' : '收藏对话'}</mdui-list-item>
|
|
||||||
}
|
|
||||||
<mdui-list-item icon="chat" rounded onClick={async () => {
|
|
||||||
await nav(-1)
|
|
||||||
gotoChat(isMobileUI() ? {
|
|
||||||
nav: nav,
|
|
||||||
id: chat.getId(),
|
|
||||||
} : {
|
|
||||||
setter: setCurrentSelectedChatId,
|
|
||||||
id: chat.getId(),
|
|
||||||
})
|
|
||||||
}}>打开对话</mdui-list-item>
|
|
||||||
</mdui-list>
|
|
||||||
</mdui-dialog>
|
|
||||||
)
|
|
||||||
|
|
||||||
/* const location = useLocation()
|
|
||||||
const searchParams = useSearchParams()
|
|
||||||
const params = useParams()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<mdui-dialog close-on-overlay-click close-on-esc ref={dialogRef}>
|
|
||||||
<span style={{
|
|
||||||
wordBreak: "break-word",
|
|
||||||
}} dangerouslySetInnerHTML={{
|
|
||||||
__html: "↓ searchParams<br><br>"
|
|
||||||
+ Object.keys(location)
|
|
||||||
// @ts-ignore 懒
|
|
||||||
.map((k) => `${k} = ${location[k]}`)
|
|
||||||
.join('<br><br>')
|
|
||||||
+ "<br><br>↓ searchParams<br><br>" + Object.keys(searchParams)
|
|
||||||
// @ts-ignore 懒
|
|
||||||
.map((k) => `${k} = ${searchParams[k]}`)
|
|
||||||
.join('<br><br>')
|
|
||||||
+ "<br><br>↓ params<br><br>" + Object.keys(params)
|
|
||||||
// @ts-ignore 懒
|
|
||||||
.map((k) => `${k} = ${params[k]}`)
|
|
||||||
.join('<br><br>')
|
|
||||||
}}></span>
|
|
||||||
</mdui-dialog>
|
|
||||||
) */
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { LoaderFunctionArgs } from "react-router"
|
|
||||||
import getClient from "../../getClient"
|
|
||||||
import { Chat } from "lingchair-client-protocol"
|
|
||||||
import ClientCache from "../../ClientCache"
|
|
||||||
|
|
||||||
export default async function UserOrChatInfoDialogLoader({ params, request }: LoaderFunctionArgs) {
|
|
||||||
const searchParams = new URL(request.url).searchParams
|
|
||||||
|
|
||||||
let id = searchParams.get('id')
|
|
||||||
let chat: Chat
|
|
||||||
if (params.type == 'user')
|
|
||||||
chat = await Chat.getOrCreatePrivateChatOrThrow(getClient(), id!)
|
|
||||||
else
|
|
||||||
chat = await Chat.getByIdOrThrow(getClient(), id!)
|
|
||||||
|
|
||||||
if (chat.getType() == 'private')
|
|
||||||
id = await chat.getTheOtherUserIdOrThrow()
|
|
||||||
|
|
||||||
return {
|
|
||||||
mySelf: await ClientCache.getMySelf(),
|
|
||||||
chat,
|
|
||||||
id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { NavigateFunction } from "react-router"
|
|
||||||
|
|
||||||
export default async function gotoChat({ nav, setter, id }: { nav?: NavigateFunction, setter?: (id: string) => void, id: string }) {
|
|
||||||
await nav?.('/chat?id=' + id)
|
|
||||||
setter?.(id)
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { NavigateFunction } from "react-router"
|
|
||||||
|
|
||||||
export default function gotoChatInfo(nav: NavigateFunction, id: string) {
|
|
||||||
nav('/info/chat?id=' + id)
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { NavigateFunction } from "react-router"
|
|
||||||
|
|
||||||
export default function gotoUserInfo(nav: NavigateFunction, id: string) {
|
|
||||||
nav('/info/user?id=' + id)
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { Dialog } from "mdui"
|
|
||||||
import useAsyncEffect from "../../utils/useAsyncEffect"
|
|
||||||
import sleep from "../../utils/sleep"
|
|
||||||
import { BlockerFunction, useBlocker, useNavigate } from "react-router"
|
|
||||||
import * as React from 'react'
|
|
||||||
import RouterDialogsContext from './RouterDialogsContext'
|
|
||||||
|
|
||||||
export default function useRouterDialogRef() {
|
|
||||||
const dialogRef = React.useRef<Dialog>()
|
|
||||||
const nav = useNavigate()
|
|
||||||
|
|
||||||
useAsyncEffect(async () => {
|
|
||||||
dialogRef.current!.addEventListener('closed', async () => {
|
|
||||||
nav(-1)
|
|
||||||
})
|
|
||||||
await sleep(10)
|
|
||||||
dialogRef.current!.open = true
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return dialogRef
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user