我弄了一坨史山, 可能在下一个 commit 会撤销更改, 或者继续完善

This commit is contained in:
CrescentLeaf
2025-12-20 17:30:14 +08:00
parent 76d518f229
commit 989933d07c
10 changed files with 472 additions and 368 deletions

View File

@@ -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,
},
],
},
],
}])

View File

@@ -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",
@@ -293,4 +296,5 @@ export default function ChatFragment({
}
</mdui-tab-panel>
</div>
)
}

View File

@@ -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',

View File

@@ -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={{

View File

@@ -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',

View File

@@ -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 />
</>)
}

View 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,
}
}

View File

@@ -0,0 +1,5 @@
import * as React from 'react'
const RouterDialogsContext = React.createContext(() => {})
export default RouterDialogsContext

View 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>
}

View File

@@ -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
}