我弄了一坨史山, 可能在下一个 commit 会撤销更改, 或者继续完善
This commit is contained in:
@@ -19,6 +19,7 @@ import FavouriteChatsList from "./main-page/FavouriteChatsList.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"
|
||||
@@ -29,6 +30,7 @@ import Split from 'split.js'
|
||||
import data from "../data.ts"
|
||||
import LazyChatFragment from "./chat-fragment/LazyChatFragment.tsx"
|
||||
import AddFavourtieChatDialog from "./routers/AddFavourtieChatDialog.tsx"
|
||||
import RouterDialogsContextWrapper from './routers/RouterDialogsContextWrapper.tsx'
|
||||
|
||||
function Root() {
|
||||
const [myProfileCache, setMyProfileCache] = React.useState<UserMySelf>()
|
||||
@@ -117,6 +119,7 @@ function Root() {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<RouterDialogsContextWrapper>
|
||||
<MainSharedContext.Provider value={sharedContext}>
|
||||
<div style={{
|
||||
display: "flex",
|
||||
@@ -200,9 +203,11 @@ function Root() {
|
||||
* Default: 侧边列表
|
||||
*/
|
||||
<div style={isMobileUI() ? {
|
||||
display: 'flex',
|
||||
height: 'calc(100% - 80px - 67px)',
|
||||
width: '100%',
|
||||
marginLeft: '15px',
|
||||
marginRight: '15px',
|
||||
marginTop: '5px',
|
||||
marginBottom: '5px',
|
||||
} : {
|
||||
paddingRight: '8px',
|
||||
}} id="SideBar">
|
||||
@@ -218,7 +223,7 @@ function Root() {
|
||||
</div>
|
||||
}
|
||||
{
|
||||
<div id="ChatFragment" style={{
|
||||
!isMobileUI() && <div id="ChatFragment" style={{
|
||||
display: "flex",
|
||||
width: '100%'
|
||||
}}>
|
||||
@@ -251,6 +256,7 @@ function Root() {
|
||||
}
|
||||
</div>
|
||||
</MainSharedContext.Provider>
|
||||
</RouterDialogsContextWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -302,6 +308,13 @@ export default function Main() {
|
||||
{
|
||||
path: 'chat',
|
||||
Component: ChatFragmentDialog,
|
||||
children: [
|
||||
{
|
||||
path: 'info',
|
||||
Component: UserOrChatInfoDialog,
|
||||
loader: ChatInfoDialogDataLoader,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}])
|
||||
|
||||
@@ -2,9 +2,8 @@ import { $, Tab, TextField } from "mdui"
|
||||
import useEventListener from "../../utils/useEventListener"
|
||||
import useEffectRef from "../../utils/useEffectRef"
|
||||
import isMobileUI from "../../utils/isMobileUI"
|
||||
import { useLocation, useNavigate } from "react-router"
|
||||
import { Outlet, useLocation, useNavigate, NavigateFunction } from "react-router"
|
||||
import { Chat } from "lingchair-client-protocol"
|
||||
import gotoChatInfo from "../routers/gotoChatInfo"
|
||||
import Preference from "../preference/Preference"
|
||||
import PreferenceHeader from "../preference/PreferenceHeader"
|
||||
import PreferenceLayout from "../preference/PreferenceLayout"
|
||||
@@ -14,6 +13,10 @@ import TextFieldPreference from "../preference/TextFieldPreference"
|
||||
import * as React from 'react'
|
||||
import ChatMessageContainer from "./ChatMessageContainer"
|
||||
|
||||
function gotoChatInfo(nav: NavigateFunction, id: string) {
|
||||
nav('/chat/info?id=' + id)
|
||||
}
|
||||
|
||||
interface MduiTabFitSizeArgs extends React.HTMLAttributes<HTMLElement & Tab> {
|
||||
value: string
|
||||
}
|
||||
@@ -44,7 +47,8 @@ export default function ChatFragment({
|
||||
const chatPanelRef = React.useRef<HTMLElement>()
|
||||
const inputRef = React.useRef<TextField>()
|
||||
|
||||
return <div style={{
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
@@ -84,12 +88,12 @@ export default function ChatFragment({
|
||||
: <MduiTabFitSize value="RequestJoin">{chatInfo.getTitle()}</MduiTabFitSize>
|
||||
}
|
||||
{chatInfo.getType() == 'group' && <MduiTabFitSize value="Settings">设置</MduiTabFitSize>}
|
||||
</mdui-tabs>
|
||||
<div style={{
|
||||
flexGrow: '1',
|
||||
}}></div>
|
||||
<mdui-button-icon icon="open_in_new" onClick={() => {
|
||||
window.open('/chat?id=' + chatInfo.getId(), '_blank')
|
||||
|
||||
}} style={{
|
||||
alignSelf: 'center',
|
||||
marginLeft: '5px',
|
||||
@@ -108,7 +112,6 @@ export default function ChatFragment({
|
||||
marginRight: '5px',
|
||||
}}></mdui-button-icon>
|
||||
</mdui-tabs>
|
||||
</mdui-tabs>
|
||||
<mdui-tab-panel slot="panel" value="RequestJoin" style={{
|
||||
display: tabItemSelected == "RequestJoin" ? "flex" : "none",
|
||||
flexDirection: "column",
|
||||
@@ -292,5 +295,6 @@ export default function ChatFragment({
|
||||
)
|
||||
}
|
||||
</mdui-tab-panel>
|
||||
</div >
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ export default function AllChatsList({ ...props }: React.HTMLAttributes<HTMLElem
|
||||
...props?.style,
|
||||
}} {...props}>
|
||||
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||
padding: isMobileUI() ? '12px' : undefined,
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '13px',
|
||||
position: 'sticky',
|
||||
|
||||
@@ -68,6 +68,7 @@ export default function FavouriteChatsList({ ...props }: React.HTMLAttributes<HT
|
||||
zIndex: '10',
|
||||
}}>
|
||||
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||
padding: isMobileUI() ? '12px' : undefined,
|
||||
paddingTop: '4px',
|
||||
}}></mdui-text-field>
|
||||
<mdui-list-item rounded style={{
|
||||
|
||||
@@ -58,6 +58,7 @@ export default function RecentChatsList({ ...props }: React.HTMLAttributes<HTMLE
|
||||
...props?.style,
|
||||
}} {...props}>
|
||||
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||
padding: isMobileUI() ? '12px' : undefined,
|
||||
paddingTop: '4px',
|
||||
marginBottom: '13px',
|
||||
position: 'sticky',
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { useSearchParams } from "react-router"
|
||||
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()
|
||||
@@ -9,7 +12,7 @@ export default function ChatFragmentDialog() {
|
||||
|
||||
const dialogRef = useRouterDialogRef()
|
||||
|
||||
React.useEffect(() => {
|
||||
useEventListener(dialogRef, 'open', () => {
|
||||
const shadow = dialogRef.current!.shadowRoot as ShadowRoot
|
||||
const panel = shadow.querySelector(".panel") as HTMLElement
|
||||
panel.style.padding = '0'
|
||||
@@ -21,7 +24,15 @@ export default function ChatFragmentDialog() {
|
||||
body.style.display = 'flex'
|
||||
}, [])
|
||||
|
||||
return <mdui-dialog fullscreen ref={dialogRef}>
|
||||
return (<>
|
||||
<mdui-dialog fullscreen ref={dialogRef}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
}}>
|
||||
<LazyChatFragment chatId={id!} openedWithRouter={true} />
|
||||
</div>
|
||||
</mdui-dialog>
|
||||
<Outlet />
|
||||
</>)
|
||||
}
|
||||
|
||||
20
client/ui/routers/ChatInfoDialogDataLoader.ts
Normal file
20
client/ui/routers/ChatInfoDialogDataLoader.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
5
client/ui/routers/RouterDialogsContext.ts
Normal file
5
client/ui/routers/RouterDialogsContext.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as React from 'react'
|
||||
|
||||
const RouterDialogsContext = React.createContext(() => {})
|
||||
|
||||
export default RouterDialogsContext
|
||||
62
client/ui/routers/RouterDialogsContextWrapper.tsx
Normal file
62
client/ui/routers/RouterDialogsContextWrapper.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
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 = []
|
||||
|
||||
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)
|
||||
}
|
||||
}, [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>
|
||||
}
|
||||
@@ -3,31 +3,17 @@ 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 proceedRef = React.useRef<() => void>()
|
||||
const shouldBlock = React.useRef(true)
|
||||
const nav = useNavigate()
|
||||
const blocker = useBlocker(React.useCallback<BlockerFunction>(() => shouldBlock.current, []))
|
||||
|
||||
// 避免用户手动返回导致动画丢失
|
||||
React.useEffect(() => {
|
||||
if (blocker.state === "blocked") {
|
||||
proceedRef.current = blocker.proceed
|
||||
// 这个让姐姐来就好啦
|
||||
dialogRef.current!.open = false
|
||||
}
|
||||
}, [blocker.state])
|
||||
const dialogRef = React.useRef<Dialog>(RouterDialogsContext)
|
||||
const registerRouterDialog = React.useContext(RouterDialogsContext)
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
registerRouterDialog(dialogRef)
|
||||
await sleep(10)
|
||||
dialogRef.current!.open = true
|
||||
dialogRef.current!.addEventListener('closed', async () => {
|
||||
shouldBlock.current = false
|
||||
await sleep(10)
|
||||
proceedRef.current ? proceedRef.current() : nav(-1)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return dialogRef
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user