改 state 为 reducer state, Context 共享数据修改, 完善资料卡对话框逻辑, 完善
This commit is contained in:
@@ -1,9 +1,17 @@
|
|||||||
import { Chat, User } from "lingchair-client-protocol"
|
import { Chat, User, UserMySelf } from "lingchair-client-protocol"
|
||||||
import getClient from "./getClient"
|
import getClient from "./getClient"
|
||||||
|
|
||||||
type CouldCached = User | Chat | null
|
type CouldCached = User | Chat | null
|
||||||
export default class ClientCache {
|
export default class ClientCache {
|
||||||
static caches: { [key: string]: CouldCached } = {}
|
static caches: { [key: string]: CouldCached } = {}
|
||||||
|
|
||||||
|
static async getMySelf() {
|
||||||
|
const k = 'usermyself'
|
||||||
|
if (this.caches[k] != null)
|
||||||
|
return this.caches[k] as UserMySelf | null
|
||||||
|
this.caches[k] = await UserMySelf.getMySelf(getClient())
|
||||||
|
return this.caches[k] as UserMySelf | null
|
||||||
|
}
|
||||||
|
|
||||||
static async getUser(id: string) {
|
static async getUser(id: string) {
|
||||||
const k = 'user_' + id
|
const k = 'user_' + id
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ 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 { BrowserRouter, createBrowserRouter, Link, Outlet, Route, RouterProvider, Routes } from "react-router"
|
import { BrowserRouter, createBrowserRouter, Link, LoaderFunction, Outlet, Route, RouterProvider, Routes } 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"
|
||||||
import { CallbackError, Chat, UserMySelf } from "lingchair-client-protocol"
|
import { CallbackError, Chat, User, UserMySelf } from "lingchair-client-protocol"
|
||||||
import showCircleProgressDialog from "./showCircleProgressDialog.ts"
|
import showCircleProgressDialog from "./showCircleProgressDialog.ts"
|
||||||
import RegisterDialog from "./main-page/RegisterDialog.tsx"
|
import RegisterDialog from "./main-page/RegisterDialog.tsx"
|
||||||
import sleep from "../utils/sleep.ts"
|
import sleep from "../utils/sleep.ts"
|
||||||
@@ -19,8 +19,12 @@ import FavouriteChatsList from "./main-page/FavouriteChatsList.tsx"
|
|||||||
import AddFavourtieChatDialog from "./main-page/AddFavourtieChatDialog.tsx"
|
import AddFavourtieChatDialog from "./main-page/AddFavourtieChatDialog.tsx"
|
||||||
import RecentChatsList from "./main-page/RecentChatsList.tsx"
|
import RecentChatsList from "./main-page/RecentChatsList.tsx"
|
||||||
import UserOrChatInfoDialog from "./routers/UserOrChatInfoDialog.tsx"
|
import UserOrChatInfoDialog from "./routers/UserOrChatInfoDialog.tsx"
|
||||||
|
import UserOrChatInfoDialogLoader from "./routers/UserOrChatInfoDialogDataLoader.ts"
|
||||||
|
import ChatFragmentDialog from "./routers/ChatFragmentDialog.tsx"
|
||||||
|
import EffectOnly from "./EffectOnly.tsx"
|
||||||
|
import MainSharedReducer from "./MainSharedReducer.ts"
|
||||||
|
|
||||||
export default function Main() {
|
function Root() {
|
||||||
const [myProfileCache, setMyProfileCache] = React.useState<UserMySelf>()
|
const [myProfileCache, setMyProfileCache] = React.useState<UserMySelf>()
|
||||||
|
|
||||||
// 多页面切换
|
// 多页面切换
|
||||||
@@ -48,9 +52,10 @@ export default function Main() {
|
|||||||
const [showRegisterDialog, setShowRegisterDialog] = React.useState(false)
|
const [showRegisterDialog, setShowRegisterDialog] = React.useState(false)
|
||||||
const [showAddFavourtieChatDialog, setShowAddFavourtieChatDialog] = React.useState(false)
|
const [showAddFavourtieChatDialog, setShowAddFavourtieChatDialog] = React.useState(false)
|
||||||
|
|
||||||
const [currentSelectedChatId, setCurrentSelectedChatId] = React.useState('')
|
const [state, dispatch] = React.useReducer(MainSharedReducer, {
|
||||||
|
favouriteChats: [],
|
||||||
const [favouriteChats, setFavouriteChats] = React.useState<Chat[]>([])
|
currentSelectedChatId: '',
|
||||||
|
})
|
||||||
|
|
||||||
const sharedContext = {
|
const sharedContext = {
|
||||||
functions_lazy: React.useRef({
|
functions_lazy: React.useRef({
|
||||||
@@ -58,17 +63,14 @@ export default function Main() {
|
|||||||
updateRecentChats: () => { },
|
updateRecentChats: () => { },
|
||||||
updateAllChats: () => { },
|
updateAllChats: () => { },
|
||||||
}),
|
}),
|
||||||
favouriteChats,
|
state,
|
||||||
setFavouriteChats,
|
setFavouriteChats: (chats: Chat[]) => dispatch({ type: 'update_favourite_chat', data: chats }),
|
||||||
|
|
||||||
setShowLoginDialog,
|
setShowLoginDialog,
|
||||||
setShowRegisterDialog,
|
setShowRegisterDialog,
|
||||||
setShowAddFavourtieChatDialog,
|
setShowAddFavourtieChatDialog,
|
||||||
|
|
||||||
currentSelectedChatId,
|
setCurrentSelectedChatId: (id: string) => dispatch({ type: 'update_selected_chat_id', data: id }),
|
||||||
setCurrentSelectedChatId,
|
|
||||||
|
|
||||||
myProfileCache,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useAsyncEffect(async () => {
|
useAsyncEffect(async () => {
|
||||||
@@ -94,7 +96,7 @@ export default function Main() {
|
|||||||
waitingForAuth.open = false
|
waitingForAuth.open = false
|
||||||
})
|
})
|
||||||
|
|
||||||
const Root = (
|
return (
|
||||||
<MainSharedContext.Provider value={sharedContext}>
|
<MainSharedContext.Provider value={sharedContext}>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -212,12 +214,29 @@ export default function Main() {
|
|||||||
</div>
|
</div>
|
||||||
</MainSharedContext.Provider>
|
</MainSharedContext.Provider>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Main() {
|
||||||
const router = createBrowserRouter([{
|
const router = createBrowserRouter([{
|
||||||
path: "/",
|
path: "/",
|
||||||
element: Root,
|
Component: Root,
|
||||||
|
hydrateFallbackElement: <EffectOnly effect={() => {
|
||||||
|
const wait = showCircleProgressDialog("请稍后...")
|
||||||
|
return () => {
|
||||||
|
wait.open = false
|
||||||
|
}
|
||||||
|
}} deps={[]} />,
|
||||||
children: [
|
children: [
|
||||||
{ path: 'info/:type', Component: UserOrChatInfoDialog, },
|
{
|
||||||
|
path: 'info/:type',
|
||||||
|
Component: UserOrChatInfoDialog,
|
||||||
|
loader: UserOrChatInfoDialogLoader,
|
||||||
|
},
|
||||||
|
/* {
|
||||||
|
path: 'chat',
|
||||||
|
Component: ChatFragmentDialog,
|
||||||
|
loader: UserOrChatInfoDialogLoader,
|
||||||
|
}, */
|
||||||
],
|
],
|
||||||
}])
|
}])
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Chat, UserMySelf } from "lingchair-client-protocol"
|
import { Chat, UserMySelf } from "lingchair-client-protocol"
|
||||||
import { createContext } from "use-context-selector"
|
import { createContext } from "use-context-selector"
|
||||||
|
import { SharedState } from "./MainSharedReducer"
|
||||||
|
|
||||||
type Shared = {
|
type Shared = {
|
||||||
functions_lazy: React.MutableRefObject<{
|
functions_lazy: React.MutableRefObject<{
|
||||||
@@ -7,17 +8,13 @@ type Shared = {
|
|||||||
updateRecentChats: () => void
|
updateRecentChats: () => void
|
||||||
updateAllChats: () => void
|
updateAllChats: () => void
|
||||||
}>
|
}>
|
||||||
favouriteChats: Chat[]
|
state: SharedState
|
||||||
setFavouriteChats: React.Dispatch<React.SetStateAction<Chat[]>>
|
|
||||||
|
|
||||||
setShowLoginDialog: React.Dispatch<React.SetStateAction<boolean>>
|
setShowLoginDialog: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
setShowRegisterDialog: React.Dispatch<React.SetStateAction<boolean>>
|
setShowRegisterDialog: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
setShowAddFavourtieChatDialog: React.Dispatch<React.SetStateAction<boolean>>
|
setShowAddFavourtieChatDialog: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
|
|
||||||
currentSelectedChatId: string
|
|
||||||
setCurrentSelectedChatId: React.Dispatch<React.SetStateAction<string>>
|
setCurrentSelectedChatId: React.Dispatch<React.SetStateAction<string>>
|
||||||
|
|
||||||
myProfileCache?: UserMySelf
|
|
||||||
}
|
}
|
||||||
const MainSharedContext = createContext({} as Shared)
|
const MainSharedContext = createContext({} as Shared)
|
||||||
|
|
||||||
|
|||||||
21
client/ui/MainSharedReducer.ts
Normal file
21
client/ui/MainSharedReducer.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Chat, UserMySelf } from "lingchair-client-protocol"
|
||||||
|
|
||||||
|
export interface SharedState {
|
||||||
|
favouriteChats: Chat[]
|
||||||
|
currentSelectedChatId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Action =
|
||||||
|
| { type: 'update_favourite_chat', data: Chat[] }
|
||||||
|
| { type: 'update_selected_chat_id', data: string }
|
||||||
|
|
||||||
|
export default function MainSharedReducer(state: SharedState, action: Action): SharedState {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'update_favourite_chat':
|
||||||
|
return { ...state, favouriteChats: action.data }
|
||||||
|
case 'update_selected_chat_id':
|
||||||
|
return { ...state, currentSelectedChatId: action.data }
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
14
client/ui/ProgressDialogInner.tsx
Normal file
14
client/ui/ProgressDialogInner.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export default function ProgressDialogInner({ children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return <div {...props} style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
...props.style
|
||||||
|
}} >
|
||||||
|
<mdui-circular-progress style={{
|
||||||
|
marginLeft: '3px',
|
||||||
|
}}></mdui-circular-progress>
|
||||||
|
<span style={{
|
||||||
|
marginLeft: '20px',
|
||||||
|
}}>{ children }</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -1,24 +1,97 @@
|
|||||||
import { dialog } from "mdui"
|
import { dialog } from "mdui"
|
||||||
import useRouterDialogRef from "./useRouterDialogRef"
|
import useRouterDialogRef from "./useRouterDialogRef"
|
||||||
import { BlockerFunction, useBlocker, useLocation, useNavigate, useParams, useSearchParams } from "react-router"
|
import { useLoaderData } from "react-router"
|
||||||
import useAsyncEffect from "../../utils/useAsyncEffect"
|
import { CallbackError } from "lingchair-client-protocol"
|
||||||
import { CallbackError, Chat } from "lingchair-client-protocol"
|
|
||||||
import showSnackbar from "../../utils/showSnackbar"
|
import showSnackbar from "../../utils/showSnackbar"
|
||||||
import getClient from "../../getClient"
|
|
||||||
import Avatar from "../Avatar"
|
import Avatar from "../Avatar"
|
||||||
import { useContextSelector } from "use-context-selector"
|
import { useContextSelector } from "use-context-selector"
|
||||||
import MainSharedContext, { Shared } from "../MainSharedContext"
|
import MainSharedContext, { Shared } from "../MainSharedContext"
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import UserOrChatInfoDialogLoader from "./UserOrChatInfoDialogDataLoader"
|
||||||
|
import MainSharedReducer from "../MainSharedReducer"
|
||||||
|
import ClientCache from "../../ClientCache"
|
||||||
|
|
||||||
export default function UserOrChatInfoDialog() {
|
export default function UserOrChatInfoDialog() {
|
||||||
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
||||||
myProfileCache: context.myProfileCache,
|
state: context.state,
|
||||||
favouriteChats: context.favouriteChats,
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const dialogRef = useRouterDialogRef()
|
const dialogRef = useRouterDialogRef()
|
||||||
|
const { chat, id } = useLoaderData<typeof UserOrChatInfoDialogLoader>()
|
||||||
|
|
||||||
const location = useLocation()
|
const favourited = React.useMemo(() => shared.state.favouriteChats.map((v) => v.getId()).indexOf(chat.getId() || '') != -1, [chat, shared.state.favouriteChats])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mdui-dialog close-on-overlay-click close-on-esc ref={dialogRef}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}>
|
||||||
|
<Avatar src={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()}</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>
|
||||||
|
<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={() => {
|
||||||
|
|
||||||
|
}}>打开对话</mdui-list-item>
|
||||||
|
</mdui-list>
|
||||||
|
</mdui-dialog>
|
||||||
|
)
|
||||||
|
|
||||||
|
/* const location = useLocation()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
|
||||||
@@ -42,5 +115,5 @@ export default function UserOrChatInfoDialog() {
|
|||||||
.join('<br><br>')
|
.join('<br><br>')
|
||||||
}}></span>
|
}}></span>
|
||||||
</mdui-dialog>
|
</mdui-dialog>
|
||||||
)
|
) */
|
||||||
}
|
}
|
||||||
|
|||||||
22
client/ui/routers/UserOrChatInfoDialogDataLoader.ts
Normal file
22
client/ui/routers/UserOrChatInfoDialogDataLoader.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { LoaderFunctionArgs } from "react-router"
|
||||||
|
import getClient from "../../getClient"
|
||||||
|
import { Chat } from "lingchair-client-protocol"
|
||||||
|
|
||||||
|
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 {
|
||||||
|
chat,
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user