Compare commits
3 Commits
19ed8c0357
...
56f651f084
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56f651f084 | ||
|
|
6a1ae692f9 | ||
|
|
8fad24ecb4 |
@@ -16,7 +16,7 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app" style="display: flex; width: 100%;"></div>
|
||||||
|
|
||||||
<script type="module" src="./init.ts"></script>
|
<script type="module" src="./init.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"@rollup/wasm-node": "4.48.0",
|
"@rollup/wasm-node": "4.48.0",
|
||||||
"@types/react": "18.3.1",
|
"@types/react": "18.3.1",
|
||||||
"@types/react-dom": "18.3.1",
|
"@types/react-dom": "18.3.1",
|
||||||
|
"@types/split.js": "^1.4.0",
|
||||||
"@vitejs/plugin-react": "4.7.0",
|
"@vitejs/plugin-react": "4.7.0",
|
||||||
"chalk": "5.4.1",
|
"chalk": "5.4.1",
|
||||||
"vite": "7.2.6",
|
"vite": "7.2.6",
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ 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, LoaderFunction, Outlet, Route, RouterProvider, Routes, useNavigate } from "react-router"
|
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"
|
||||||
import { CallbackError, Chat, User, UserMySelf } from "lingchair-client-protocol"
|
import { CallbackError, Chat, 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"
|
||||||
import { $, Dialog, NavigationDrawer } from "mdui"
|
import { $, dialog, NavigationDrawer } from "mdui"
|
||||||
import getClient from "../getClient.ts"
|
import getClient from "../getClient.ts"
|
||||||
import showSnackbar from "../utils/showSnackbar.ts"
|
import showSnackbar from "../utils/showSnackbar.ts"
|
||||||
import AllChatsList from "./main-page/AllChatsList.tsx"
|
import AllChatsList from "./main-page/AllChatsList.tsx"
|
||||||
@@ -25,6 +25,10 @@ import EffectOnly from "./EffectOnly.tsx"
|
|||||||
import MainSharedReducer from "./MainSharedReducer.ts"
|
import MainSharedReducer from "./MainSharedReducer.ts"
|
||||||
import gotoUserInfo from "./routers/gotoUserInfo.ts"
|
import gotoUserInfo from "./routers/gotoUserInfo.ts"
|
||||||
import EditMyProfileDialog from "./routers/EditMyProfileDialog.tsx"
|
import EditMyProfileDialog from "./routers/EditMyProfileDialog.tsx"
|
||||||
|
import ProgressDialogFallback from "./ProgressDialogFallback.tsx"
|
||||||
|
import Split from 'split.js'
|
||||||
|
import data from "../data.ts"
|
||||||
|
import LazyChatFragment from "./chat-fragment/LazyChatFragment.tsx"
|
||||||
|
|
||||||
function Root() {
|
function Root() {
|
||||||
const [myProfileCache, setMyProfileCache] = React.useState<UserMySelf>()
|
const [myProfileCache, setMyProfileCache] = React.useState<UserMySelf>()
|
||||||
@@ -100,13 +104,27 @@ function Root() {
|
|||||||
waitingForAuth.open = false
|
waitingForAuth.open = false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!isMobileUI()) {
|
||||||
|
const split = Split(['#SideBar', '#ChatFragment'], {
|
||||||
|
sizes: data.split_sizes ? data.split_sizes : [25, 75],
|
||||||
|
minSize: [200, 400],
|
||||||
|
gutterSize: 2,
|
||||||
|
onDragEnd: function () {
|
||||||
|
data.split_sizes = split.getSizes()
|
||||||
|
data.apply()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainSharedContext.Provider value={sharedContext}>
|
<MainSharedContext.Provider value={sharedContext}>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
flexDirection: isMobileUI() ? 'column' : 'row',
|
flexDirection: isMobileUI() ? 'column' : 'row',
|
||||||
width: `calc(var(--whitesilk-window-width))${isMobileUI() ? '' : ' - 80px'}`,
|
width: '100%',
|
||||||
height: 'var(--whitesilk-window-height)',
|
height: 'var(--whitesilk-window-height)',
|
||||||
}}>
|
}}>
|
||||||
{
|
{
|
||||||
@@ -128,7 +146,8 @@ function Root() {
|
|||||||
<mdui-divider style={{
|
<mdui-divider style={{
|
||||||
margin: '10px',
|
margin: '10px',
|
||||||
}}></mdui-divider>
|
}}></mdui-divider>
|
||||||
<mdui-list-item rounded icon="person_add">添加收藏对话</mdui-list-item>
|
<mdui-list-item rounded icon="settings">客户端设置</mdui-list-item>
|
||||||
|
<mdui-list-item rounded icon="person_add" onClick={() => setShowAddFavourtieChatDialog(true)}>添加收藏对话</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={{
|
||||||
@@ -187,7 +206,9 @@ function Root() {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
height: 'calc(100% - 80px - 67px)',
|
height: 'calc(100% - 80px - 67px)',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
} : {}} id="SideBar">
|
} : {
|
||||||
|
paddingRight: '8px',
|
||||||
|
}} id="SideBar">
|
||||||
<RecentChatsList style={{
|
<RecentChatsList style={{
|
||||||
display: currentShowPage == 'Recents' ? undefined : 'none'
|
display: currentShowPage == 'Recents' ? undefined : 'none'
|
||||||
}} />
|
}} />
|
||||||
@@ -199,6 +220,24 @@ function Root() {
|
|||||||
}} />
|
}} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
<div id="ChatFragment" style={{
|
||||||
|
display: "flex",
|
||||||
|
width: '100%'
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
(state.currentSelectedChatId && state.currentSelectedChatId != '')
|
||||||
|
? <LazyChatFragment openedWithRouter={false} chatId={state.currentSelectedChatId!} />
|
||||||
|
: <div style={{
|
||||||
|
width: '100%',
|
||||||
|
textAlign: 'center',
|
||||||
|
alignSelf: 'center',
|
||||||
|
}}>
|
||||||
|
选择以开始对话......
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Mobile: 底部导航栏提供列表切换
|
* Mobile: 底部导航栏提供列表切换
|
||||||
@@ -218,16 +257,27 @@ function Root() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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([{
|
const router = createBrowserRouter([{
|
||||||
path: "/",
|
path: "/",
|
||||||
Component: Root,
|
Component: Root,
|
||||||
hydrateFallbackElement: <EffectOnly effect={() => {
|
hydrateFallbackElement: <ProgressDialogFallback text="请稍后..." />,
|
||||||
const wait = showCircleProgressDialog("请稍后...")
|
ErrorBoundary: SnackbarErrorBoundary,
|
||||||
return () => {
|
|
||||||
wait.open = false
|
|
||||||
}
|
|
||||||
}} deps={[]} />,
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'info/:type',
|
path: 'info/:type',
|
||||||
@@ -243,11 +293,10 @@ export default function Main() {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* {
|
{
|
||||||
path: 'chat',
|
path: 'chat',
|
||||||
Component: ChatFragmentDialog,
|
Component: ChatFragmentDialog,
|
||||||
loader: UserOrChatInfoDialogLoader,
|
},
|
||||||
}, */
|
|
||||||
],
|
],
|
||||||
}])
|
}])
|
||||||
|
|
||||||
|
|||||||
11
client/ui/ProgressDialogFallback.tsx
Normal file
11
client/ui/ProgressDialogFallback.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import EffectOnly from "./EffectOnly"
|
||||||
|
import showCircleProgressDialog from "./showCircleProgressDialog"
|
||||||
|
|
||||||
|
export default function ProgressDialogFallback({ text }: { text: string }) {
|
||||||
|
return <EffectOnly effect={() => {
|
||||||
|
const wait = showCircleProgressDialog(text)
|
||||||
|
return () => {
|
||||||
|
wait.open = false
|
||||||
|
}
|
||||||
|
}} deps={[]} />
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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>
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,320 @@
|
|||||||
|
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 { 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"
|
||||||
|
import PreferenceUpdater from "../preference/PreferenceUpdater"
|
||||||
|
import SwitchPreference from "../preference/SwitchPreference"
|
||||||
|
import TextFieldPreference from "../preference/TextFieldPreference"
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
interface MduiTabFitSizeArgs extends React.HTMLAttributes<HTMLElement & Tab> {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
function MduiTabFitSize({ children, ...props }: MduiTabFitSizeArgs) {
|
||||||
|
return <mdui-tab {...props} style={{
|
||||||
|
...props?.style,
|
||||||
|
minWidth: 'fit-content',
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</mdui-tab>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ChatFragment({
|
||||||
|
chatInfo,
|
||||||
|
openedWithRouter,
|
||||||
|
}: {
|
||||||
|
chatInfo: Chat
|
||||||
|
openedWithRouter: boolean
|
||||||
|
}) {
|
||||||
|
const nav = useNavigate()
|
||||||
|
|
||||||
|
const [tabItemSelected, setTabItemSelected] = React.useState('None')
|
||||||
|
const tabRef = React.useRef<Tab>()
|
||||||
|
useEventListener(tabRef, 'change', () => {
|
||||||
|
tabRef.current != null && setTabItemSelected(tabRef.current!.value as string)
|
||||||
|
})
|
||||||
|
|
||||||
|
const chatPanelRef = React.useRef<HTMLElement>()
|
||||||
|
const inputRef = React.useRef<TextField>()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return <div style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
overflowY: 'auto',
|
||||||
|
}}>
|
||||||
|
<mdui-tabs ref={useEffectRef<HTMLElement>((ref) => {
|
||||||
|
$(ref.current!.shadowRoot).append(`<style>.container::after { height: 0 !important; }</style>`)
|
||||||
|
$(tabRef.current!.shadowRoot).append(`<style>.container::after { height: 0 !important; }</style>`)
|
||||||
|
; (!isMobileUI()) && $(tabRef.current!.shadowRoot).append(`<style>.no-scroll-bar::-webkit-scrollbar{width:0px !important}*::-webkit-scrollbar{width:7px;height:10px}*::-webkit-scrollbar-track{width:6px;background:rgba(#101f1c,0.1);-webkit-border-radius:2em;-moz-border-radius:2em;border-radius:2em}*::-webkit-scrollbar-thumb{background-color:rgba(144,147,153,0.5);background-clip:padding-box;min-height:28px;-webkit-border-radius:2em;-moz-border-radius:2em;border-radius:2em;transition:background-color 0.3s;cursor:pointer}*::-webkit-scrollbar-thumb:hover{background-color:rgba(144,147,153,0.3)}</style>`)
|
||||||
|
}, [])} style={{
|
||||||
|
position: 'sticky',
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
openedWithRouter && <mdui-button-icon icon="arrow_back" onClick={() => nav(-1)} style={{
|
||||||
|
alignSelf: 'center',
|
||||||
|
marginLeft: '5px',
|
||||||
|
marginRight: '5px',
|
||||||
|
}}></mdui-button-icon>
|
||||||
|
}
|
||||||
|
<mdui-tabs ref={tabRef} value={tabItemSelected} style={{
|
||||||
|
position: 'sticky',
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
height: "100%",
|
||||||
|
overflowX: 'auto',
|
||||||
|
}}></mdui-tabs>{
|
||||||
|
chatInfo.isMember() ? <>
|
||||||
|
<MduiTabFitSize value="Chat">{chatInfo.getTitle()}</MduiTabFitSize>
|
||||||
|
{chatInfo.getType() == 'group' && chatInfo.isAdmin() && <MduiTabFitSize value="NewMemberRequests">加入请求</MduiTabFitSize>}
|
||||||
|
{chatInfo.getType() == 'group' && <MduiTabFitSize value="GroupMembers">群组成员</MduiTabFitSize>}
|
||||||
|
</>
|
||||||
|
: <MduiTabFitSize value="RequestJoin">{chatInfo.getTitle()}</MduiTabFitSize>
|
||||||
|
}
|
||||||
|
{chatInfo.getType() == 'group' && <MduiTabFitSize value="Settings">设置</MduiTabFitSize>}
|
||||||
|
<MduiTabFitSize value="None" style={{ display: 'none' }}></MduiTabFitSize>
|
||||||
|
<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',
|
||||||
|
marginRight: '5px',
|
||||||
|
}}></mdui-button-icon>
|
||||||
|
<mdui-button-icon icon="refresh" onClick={() => {
|
||||||
|
|
||||||
|
}} style={{
|
||||||
|
alignSelf: 'center',
|
||||||
|
marginLeft: '5px',
|
||||||
|
marginRight: '5px',
|
||||||
|
}}></mdui-button-icon>
|
||||||
|
<mdui-button-icon icon="info" onClick={() => gotoChatInfo(nav, chatInfo.getId())} style={{
|
||||||
|
alignSelf: 'center',
|
||||||
|
marginLeft: '5px',
|
||||||
|
marginRight: '5px',
|
||||||
|
}}></mdui-button-icon>
|
||||||
|
</mdui-tabs>
|
||||||
|
<mdui-tab-panel slot="panel" value="RequestJoin" style={{
|
||||||
|
display: tabItemSelected == "RequestJoin" ? "flex" : "none",
|
||||||
|
flexDirection: "column",
|
||||||
|
height: "100%",
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}>
|
||||||
|
<div>
|
||||||
|
{/* 非群成员 */}
|
||||||
|
</div>
|
||||||
|
</mdui-tab-panel>
|
||||||
|
<mdui-tab-panel slot="panel" value="Chat" ref={chatPanelRef} style={{
|
||||||
|
display: tabItemSelected == "Chat" ? "flex" : "none",
|
||||||
|
flexDirection: "column",
|
||||||
|
height: "100%",
|
||||||
|
}} onScroll={async (e: WheelEvent) => {
|
||||||
|
const scrollTop = (e.target as HTMLDivElement).scrollTop
|
||||||
|
if (scrollTop == 0) {
|
||||||
|
// 加载更多
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingTop: "15px",
|
||||||
|
}}>
|
||||||
|
{/* 这里显示一些提示 */}
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '20px',
|
||||||
|
paddingTop: "15px",
|
||||||
|
flexGrow: '1',
|
||||||
|
}}>
|
||||||
|
{/* 消息放在这里 */}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
// 输入框
|
||||||
|
}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingBottom: '2px',
|
||||||
|
paddingTop: '0.1rem',
|
||||||
|
position: 'sticky',
|
||||||
|
bottom: '0',
|
||||||
|
paddingLeft: '5px',
|
||||||
|
paddingRight: '4px',
|
||||||
|
backgroundColor: 'rgb(var(--mdui-color-surface))',
|
||||||
|
}} onDrop={(e) => {
|
||||||
|
// 文件拽入
|
||||||
|
}}>
|
||||||
|
<mdui-text-field variant="outlined" placeholder="(。・ω・。)" autosize ref={inputRef} max-rows={6} onChange={() => {
|
||||||
|
if (inputRef.current?.value.trim() == '') {
|
||||||
|
// 清空缓存的文件
|
||||||
|
}
|
||||||
|
}} onKeyDown={(event: KeyboardEvent) => {
|
||||||
|
if (event.ctrlKey && event.key == 'Enter') {
|
||||||
|
// 发送消息
|
||||||
|
}
|
||||||
|
}} onPaste={(event: ClipboardEvent) => {
|
||||||
|
for (const item of event.clipboardData?.items || []) {
|
||||||
|
if (item.kind == 'file') {
|
||||||
|
event.preventDefault()
|
||||||
|
const file = item.getAsFile() as File
|
||||||
|
// 添加文件
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}} style={{
|
||||||
|
marginRight: '10px',
|
||||||
|
marginTop: '3px',
|
||||||
|
marginBottom: '3px',
|
||||||
|
}}></mdui-text-field>
|
||||||
|
<mdui-button-icon slot="end-icon" icon="attach_file" style={{
|
||||||
|
marginRight: '6px',
|
||||||
|
}} onClick={() => {
|
||||||
|
// 添加文件
|
||||||
|
}}></mdui-button-icon>
|
||||||
|
<mdui-button-icon icon="send" style={{
|
||||||
|
marginRight: '7px',
|
||||||
|
}} onClick={() => {
|
||||||
|
// 发送消息
|
||||||
|
}}></mdui-button-icon>
|
||||||
|
<div style={{
|
||||||
|
display: 'none'
|
||||||
|
}}>
|
||||||
|
<input accept="*/*" type="file" name="添加文件" multiple ></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mdui-tab-panel>
|
||||||
|
{
|
||||||
|
chatInfo.getType() == 'group' && <mdui-tab-panel slot="panel" value="GroupMembers" style={{
|
||||||
|
display: tabItemSelected == "GroupMembers" ? "flex" : "none",
|
||||||
|
flexDirection: "column",
|
||||||
|
height: "100%",
|
||||||
|
}}>
|
||||||
|
{/* <GroupMembersList chat={chatInfo} /> */}
|
||||||
|
</mdui-tab-panel>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
chatInfo.getType() == 'group' && <mdui-tab-panel slot="panel" value="NewMemberRequests" style={{
|
||||||
|
display: tabItemSelected == "NewMemberRequests" ? "flex" : "none",
|
||||||
|
flexDirection: "column",
|
||||||
|
height: "100%",
|
||||||
|
}}>
|
||||||
|
{/* {chatInfo.isAdmin() && <JoinRequestsList chat={chatInfo} />} */}
|
||||||
|
</mdui-tab-panel>
|
||||||
|
}
|
||||||
|
<mdui-tab-panel slot="panel" value="Settings" style={{
|
||||||
|
display: tabItemSelected == "Settings" ? "flex" : "none",
|
||||||
|
flexDirection: "column",
|
||||||
|
height: "100%",
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
display: 'none'
|
||||||
|
}}>
|
||||||
|
<input accept="image/*" type="file" name="上传对话头像"></input>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
/* chatInfo.getType() == 'group' && <PreferenceLayout>
|
||||||
|
<PreferenceUpdater.Provider value={groupPreferenceStore.createUpdater()}>
|
||||||
|
<PreferenceHeader
|
||||||
|
title="群组资料" />
|
||||||
|
<Preference
|
||||||
|
title="上传新的头像"
|
||||||
|
icon="image"
|
||||||
|
disabled={!chatInfo.isAdmin()}
|
||||||
|
onClick={() => {
|
||||||
|
uploadChatAvatarRef.current!.click()
|
||||||
|
}} />
|
||||||
|
<TextFieldPreference
|
||||||
|
title="设置群名称"
|
||||||
|
icon="edit"
|
||||||
|
id="group_title"
|
||||||
|
state={groupPreferenceStore.state.group_title || ''}
|
||||||
|
disabled={!chatInfo.isAdmin()} />
|
||||||
|
<TextFieldPreference
|
||||||
|
title="设置群别名"
|
||||||
|
icon="edit"
|
||||||
|
id="group_name"
|
||||||
|
description="以便于添加, 可留空"
|
||||||
|
state={groupPreferenceStore.state.group_name || ''}
|
||||||
|
disabled={!chatInfo.isAdmin()} />
|
||||||
|
<PreferenceHeader
|
||||||
|
title="入群设定" />
|
||||||
|
<SwitchPreference
|
||||||
|
title="允许入群"
|
||||||
|
icon="person_add"
|
||||||
|
id="allow_new_member_join"
|
||||||
|
disabled={!chatInfo.isAdmin()}
|
||||||
|
state={groupPreferenceStore.state.allow_new_member_join || false} />
|
||||||
|
<SwitchPreference
|
||||||
|
title="允许成员邀请"
|
||||||
|
description="目前压根没有这项功能, 甚至还不能查看成员列表, 以后再说吧"
|
||||||
|
id="allow_new_member_from_invitation"
|
||||||
|
icon="_"
|
||||||
|
disabled={true || !chatInfo.isAdmin()}
|
||||||
|
state={groupPreferenceStore.state.allow_new_member_from_invitation || false} />
|
||||||
|
<SelectPreference
|
||||||
|
title="入群验证方式"
|
||||||
|
icon="_"
|
||||||
|
id="new_member_join_method"
|
||||||
|
selections={{
|
||||||
|
disabled: "无需验证",
|
||||||
|
allowed_by_admin: "只需要管理员批准 (WIP)",
|
||||||
|
answered_and_allowed_by_admin: "需要回答问题并获得管理员批准 (WIP)",
|
||||||
|
}}
|
||||||
|
disabled={!chatInfo.isAdmin() || !groupPreferenceStore.state.allow_new_member_join}
|
||||||
|
state={groupPreferenceStore.state.new_member_join_method || 'disabled'} />
|
||||||
|
{
|
||||||
|
groupPreferenceStore.state.new_member_join_method == 'answered_and_allowed_by_admin'
|
||||||
|
&& <TextFieldPreference
|
||||||
|
title="设置问题"
|
||||||
|
icon="_"
|
||||||
|
id="answered_and_allowed_by_admin_question"
|
||||||
|
description="WIP"
|
||||||
|
state={groupPreferenceStore.state.answered_and_allowed_by_admin_question || ''}
|
||||||
|
disabled={true || !chatInfo.isAdmin()} />
|
||||||
|
}
|
||||||
|
</PreferenceUpdater.Provider>
|
||||||
|
</PreferenceLayout> */
|
||||||
|
}
|
||||||
|
{
|
||||||
|
chatInfo.getType() == 'private' && (
|
||||||
|
<div>
|
||||||
|
未制作
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</mdui-tab-panel>
|
||||||
|
<mdui-tab-panel slot="panel" value="None" style={{
|
||||||
|
display: tabItemSelected == "None" ? "flex" : "none",
|
||||||
|
flexDirection: "column",
|
||||||
|
height: "100%",
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}>
|
||||||
|
<mdui-circular-progress></mdui-circular-progress>
|
||||||
|
</div>
|
||||||
|
</mdui-tab-panel>
|
||||||
|
</div >
|
||||||
|
}
|
||||||
|
|||||||
22
client/ui/chat-fragment/LazyChatFragment.tsx
Normal file
22
client/ui/chat-fragment/LazyChatFragment.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Chat } from "lingchair-client-protocol"
|
||||||
|
import { Await } from "react-router"
|
||||||
|
import getClient from "../../getClient"
|
||||||
|
import ChatFragment from "./ChatFragment"
|
||||||
|
import * as React from 'react'
|
||||||
|
import showSnackbar from "../../utils/showSnackbar"
|
||||||
|
import EffectOnly from "../EffectOnly"
|
||||||
|
|
||||||
|
export default function LazyChatFragment({ chatId, openedWithRouter }: { chatId: string, openedWithRouter: boolean }) {
|
||||||
|
return <React.Suspense fallback={<EffectOnly effect={() => {
|
||||||
|
const s = showSnackbar({
|
||||||
|
message: '请稍后',
|
||||||
|
})
|
||||||
|
return () => {
|
||||||
|
s.open = false
|
||||||
|
}
|
||||||
|
}} deps={[]} />}>
|
||||||
|
<Await
|
||||||
|
resolve={React.useMemo(() => Chat.getByIdOrThrow(getClient(), chatId), [chatId])}
|
||||||
|
children={(chatInfo: Chat) => <ChatFragment chatInfo={chatInfo} openedWithRouter={openedWithRouter} />} />
|
||||||
|
</React.Suspense>
|
||||||
|
}
|
||||||
@@ -58,7 +58,7 @@ export default function AllChatsList({ ...props }: React.HTMLAttributes<HTMLElem
|
|||||||
...props?.style,
|
...props?.style,
|
||||||
}} {...props}>
|
}} {...props}>
|
||||||
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||||
paddingTop: '12px',
|
paddingTop: '4px',
|
||||||
paddingBottom: '13px',
|
paddingBottom: '13px',
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: '0',
|
top: '0',
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export default function FavouriteChatsList({ ...props }: React.HTMLAttributes<HT
|
|||||||
zIndex: '10',
|
zIndex: '10',
|
||||||
}}>
|
}}>
|
||||||
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||||
paddingTop: '12px',
|
paddingTop: '4px',
|
||||||
}}></mdui-text-field>
|
}}></mdui-text-field>
|
||||||
<mdui-list-item rounded style={{
|
<mdui-list-item rounded style={{
|
||||||
marginTop: '13px',
|
marginTop: '13px',
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default function RecentChatsList({ ...props }: React.HTMLAttributes<HTMLE
|
|||||||
...props?.style,
|
...props?.style,
|
||||||
}} {...props}>
|
}} {...props}>
|
||||||
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
|
||||||
paddingTop: '12px',
|
paddingTop: '4px',
|
||||||
marginBottom: '13px',
|
marginBottom: '13px',
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: '0',
|
top: '0',
|
||||||
|
|||||||
16
client/ui/preference/Preference.tsx
Normal file
16
client/ui/preference/Preference.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { ListItem } from "mdui"
|
||||||
|
|
||||||
|
interface Args extends React.HTMLAttributes<ListItem> {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
icon: string
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Preference({ title, icon, disabled, description, ...props }: Args) {
|
||||||
|
// @ts-ignore: 为什么 ...props 要说参数不兼容呢?
|
||||||
|
return <mdui-list-item disabled={disabled ? true : undefined} rounded icon={icon} {...props}>
|
||||||
|
{title}
|
||||||
|
{description && <span slot="description">{description}</span>}
|
||||||
|
</mdui-list-item>
|
||||||
|
}
|
||||||
5
client/ui/preference/PreferenceHeader.tsx
Normal file
5
client/ui/preference/PreferenceHeader.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export default function PreferenceHeader({ title }: {
|
||||||
|
title: string
|
||||||
|
}) {
|
||||||
|
return <mdui-list-subheader>{title}</mdui-list-subheader>
|
||||||
|
}
|
||||||
8
client/ui/preference/PreferenceLayout.tsx
Normal file
8
client/ui/preference/PreferenceLayout.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function PreferenceLayout({ children, ...props }: React.HTMLAttributes<HTMLElement>) {
|
||||||
|
return <mdui-list style={{
|
||||||
|
marginLeft: '15px',
|
||||||
|
marginRight: '15px',
|
||||||
|
}} {...props}>
|
||||||
|
{children}
|
||||||
|
</mdui-list>
|
||||||
|
}
|
||||||
27
client/ui/preference/PreferenceStore.ts
Normal file
27
client/ui/preference/PreferenceStore.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export default class PreferenceStore<T extends object> {
|
||||||
|
declare onUpdate: (value: T, oldvalue: T) => void
|
||||||
|
declare state: T
|
||||||
|
declare setState: React.Dispatch<React.SetStateAction<T>>
|
||||||
|
constructor() {
|
||||||
|
const _ = React.useState({} as T)
|
||||||
|
this.state = _[0]
|
||||||
|
this.setState = _[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
createUpdater() {
|
||||||
|
return (key: string, value: unknown) => {
|
||||||
|
const oldvalue = this.state
|
||||||
|
const newValue = {
|
||||||
|
...this.state,
|
||||||
|
[key]: value,
|
||||||
|
}
|
||||||
|
this.setState(newValue)
|
||||||
|
this.onUpdate?.(newValue, oldvalue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setOnUpdate(onUpdate: (value: T, oldvalue: T) => void) {
|
||||||
|
this.onUpdate = onUpdate
|
||||||
|
}
|
||||||
|
}
|
||||||
6
client/ui/preference/PreferenceUpdater.ts
Normal file
6
client/ui/preference/PreferenceUpdater.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
const PreferenceUpdater = React.createContext<(key: string, value: unknown) => void>(null as any)
|
||||||
|
|
||||||
|
export default PreferenceUpdater
|
||||||
43
client/ui/preference/SelectPreference.tsx
Normal file
43
client/ui/preference/SelectPreference.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Dropdown } from 'mdui'
|
||||||
|
import PreferenceUpdater from "./PreferenceUpdater.ts"
|
||||||
|
import useEventListener from '../../utils/useEventListener.ts'
|
||||||
|
|
||||||
|
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||||
|
title: string
|
||||||
|
icon: string
|
||||||
|
id: string
|
||||||
|
disabled?: boolean
|
||||||
|
selections: { [id: string]: string }
|
||||||
|
state: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SelectPreference({ title, icon, id: preferenceId, selections, state, disabled }: Args) {
|
||||||
|
const updater = React.useContext(PreferenceUpdater)
|
||||||
|
|
||||||
|
const dropDownRef = React.useRef<Dropdown>(null)
|
||||||
|
const [isDropDownOpen, setDropDownOpen] = React.useState(false)
|
||||||
|
|
||||||
|
useEventListener(dropDownRef, 'closed', () => {
|
||||||
|
setDropDownOpen(false)
|
||||||
|
})
|
||||||
|
return <mdui-list-item icon={icon} rounded disabled={disabled ? true : undefined} onClick={() => setDropDownOpen(!isDropDownOpen)}>
|
||||||
|
<mdui-dropdown ref={dropDownRef} trigger="manual" open={isDropDownOpen}>
|
||||||
|
<span slot="trigger">{title}</span>
|
||||||
|
<mdui-menu onClick={(e: MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setDropDownOpen(false)
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
Object.keys(selections).map((id) =>
|
||||||
|
// @ts-ignore: selected 确实存在, 但是并不对外公开使用
|
||||||
|
<mdui-menu-item key={id} selected={state == id ? true : undefined} onClick={() => {
|
||||||
|
updater(preferenceId, id)
|
||||||
|
}}>{selections[id]}</mdui-menu-item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</mdui-menu>
|
||||||
|
</mdui-dropdown>
|
||||||
|
<span slot="description">{selections[state]}</span>
|
||||||
|
</mdui-list-item>
|
||||||
|
}
|
||||||
30
client/ui/preference/SwitchPreference.tsx
Normal file
30
client/ui/preference/SwitchPreference.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Switch } from 'mdui'
|
||||||
|
import React from 'react'
|
||||||
|
import PreferenceUpdater from "./PreferenceUpdater.ts"
|
||||||
|
|
||||||
|
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||||
|
title: string
|
||||||
|
id: string
|
||||||
|
description?: string
|
||||||
|
icon: string
|
||||||
|
state: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SwitchPreference({ title, icon, id, disabled, description, state }: Args) {
|
||||||
|
const updater = React.useContext(PreferenceUpdater)
|
||||||
|
|
||||||
|
const switchRef = React.useRef<Switch>(null)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
switchRef.current!.checked = state
|
||||||
|
}, [state])
|
||||||
|
|
||||||
|
return <mdui-list-item disabled={disabled ? true : undefined} rounded icon={icon} onClick={() => {
|
||||||
|
updater(id, !state)
|
||||||
|
}}>
|
||||||
|
{title}
|
||||||
|
{description && <span slot="description">{description}</span>}
|
||||||
|
<mdui-switch slot="end-icon" checked-icon="" ref={switchRef} onClick={(e) => e.preventDefault()}></mdui-switch>
|
||||||
|
</mdui-list-item>
|
||||||
|
}
|
||||||
37
client/ui/preference/TextFieldPreference.tsx
Normal file
37
client/ui/preference/TextFieldPreference.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { prompt } from 'mdui'
|
||||||
|
import PreferenceUpdater from "./PreferenceUpdater.ts"
|
||||||
|
|
||||||
|
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
icon: string
|
||||||
|
id: string
|
||||||
|
state: string
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TextFieldPreference({ title, icon, description, id, state, disabled }: Args) {
|
||||||
|
const updater = React.useContext(PreferenceUpdater)
|
||||||
|
|
||||||
|
return <mdui-list-item icon={icon} rounded disabled={disabled ? true : undefined} onClick={() => {
|
||||||
|
prompt({
|
||||||
|
headline: title,
|
||||||
|
confirmText: "确定",
|
||||||
|
cancelText: "取消",
|
||||||
|
onConfirm: (value) => {
|
||||||
|
updater(id, value)
|
||||||
|
},
|
||||||
|
onCancel: () => { },
|
||||||
|
textFieldOptions: {
|
||||||
|
label: description,
|
||||||
|
value: state,
|
||||||
|
},
|
||||||
|
closeOnEsc: true,
|
||||||
|
closeOnOverlayClick: true,
|
||||||
|
})
|
||||||
|
}}>
|
||||||
|
{title}
|
||||||
|
{description && <span slot="description">{description}</span>}
|
||||||
|
</mdui-list-item>
|
||||||
|
}
|
||||||
@@ -1,8 +1,27 @@
|
|||||||
|
import { useSearchParams } from "react-router"
|
||||||
import useRouterDialogRef from "./useRouterDialogRef"
|
import useRouterDialogRef from "./useRouterDialogRef"
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import LazyChatFragment from "../chat-fragment/LazyChatFragment"
|
||||||
|
|
||||||
export default function ChatFragmentDialog() {
|
export default function ChatFragmentDialog() {
|
||||||
|
const [searchParams] = useSearchParams()
|
||||||
|
const id = searchParams.get('id')
|
||||||
|
|
||||||
const dialogRef = useRouterDialogRef()
|
const dialogRef = useRouterDialogRef()
|
||||||
|
|
||||||
return <mdui-dialog fullscreen ref={dialogRef}></mdui-dialog>
|
React.useEffect(() => {
|
||||||
|
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}>
|
||||||
|
<LazyChatFragment chatId={id!} openedWithRouter={true} />
|
||||||
|
</mdui-dialog>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,22 @@ 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 UserOrChatInfoDialogLoader from "./UserOrChatInfoDialogDataLoader"
|
||||||
import MainSharedReducer from "../MainSharedReducer"
|
|
||||||
import ClientCache from "../../ClientCache"
|
import ClientCache from "../../ClientCache"
|
||||||
import getClient from "../../getClient"
|
import getClient from "../../getClient"
|
||||||
|
import gotoChat from "./gotoChat"
|
||||||
|
import isMobileUI from "../../utils/isMobileUI"
|
||||||
|
|
||||||
export default function UserOrChatInfoDialog() {
|
export default function UserOrChatInfoDialog() {
|
||||||
const shared = useContextSelector(MainSharedContext, (context: Shared) => ({
|
const favouriteChats = useContextSelector(
|
||||||
state: context.state,
|
MainSharedContext,
|
||||||
}))
|
(context: Shared) => context.state.favouriteChats
|
||||||
|
)
|
||||||
|
const setCurrentSelectedChatId = useContextSelector(
|
||||||
|
MainSharedContext,
|
||||||
|
(context: Shared) => context.setCurrentSelectedChatId
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(setCurrentSelectedChatId, favouriteChats)
|
||||||
|
|
||||||
const nav = useNavigate()
|
const nav = useNavigate()
|
||||||
|
|
||||||
@@ -24,7 +32,7 @@ export default function UserOrChatInfoDialog() {
|
|||||||
|
|
||||||
const isMySelf = mySelf?.getId() == id
|
const isMySelf = mySelf?.getId() == id
|
||||||
|
|
||||||
const favourited = React.useMemo(() => shared.state.favouriteChats.map((v) => v.getId()).indexOf(chat.getId() || '') != -1, [chat, shared.state.favouriteChats])
|
const favourited = React.useMemo(() => favouriteChats.map((v) => v.getId()).indexOf(chat.getId() || '') != -1, [chat, favouriteChats])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mdui-dialog close-on-overlay-click close-on-esc ref={dialogRef}>
|
<mdui-dialog close-on-overlay-click close-on-esc ref={dialogRef}>
|
||||||
@@ -46,7 +54,7 @@ export default function UserOrChatInfoDialog() {
|
|||||||
}}>
|
}}>
|
||||||
<span style={{
|
<span style={{
|
||||||
fontSize: '16.5px'
|
fontSize: '16.5px'
|
||||||
}}>{chat.getTitle()}</span>
|
}}>{chat.getTitle() + (isMySelf ? ' (我)' : '')}</span>
|
||||||
<span style={{
|
<span style={{
|
||||||
fontSize: '10.5px',
|
fontSize: '10.5px',
|
||||||
marginTop: '3px',
|
marginTop: '3px',
|
||||||
@@ -96,8 +104,15 @@ export default function UserOrChatInfoDialog() {
|
|||||||
],
|
],
|
||||||
})}>{favourited ? '取消收藏' : '收藏对话'}</mdui-list-item>
|
})}>{favourited ? '取消收藏' : '收藏对话'}</mdui-list-item>
|
||||||
}
|
}
|
||||||
<mdui-list-item icon="chat" rounded onClick={() => {
|
<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-item>
|
||||||
</mdui-list>
|
</mdui-list>
|
||||||
</mdui-dialog>
|
</mdui-dialog>
|
||||||
|
|||||||
6
client/ui/routers/gotoChat.ts
Normal file
6
client/ui/routers/gotoChat.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
11
client/utils/useEffectRef.ts
Normal file
11
client/utils/useEffectRef.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Dialog } from "mdui"
|
||||||
|
import { BlockerFunction, useBlocker, useNavigate } from "react-router"
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
export default function useEffectRef<T = undefined>(effect: (ref: React.MutableRefObject<T | undefined>) => void | (() => void), deps?: React.DependencyList) {
|
||||||
|
const ref = React.useRef<T>()
|
||||||
|
React.useEffect(() => {
|
||||||
|
return effect(ref)
|
||||||
|
}, deps)
|
||||||
|
return ref
|
||||||
|
}
|
||||||
@@ -103,6 +103,7 @@ let Dialog = class Dialog extends MduiElement {
|
|||||||
]);
|
]);
|
||||||
// 打开
|
// 打开
|
||||||
// 要区分是否首次渲染,首次渲染不触发事件,不执行动画;非首次渲染,触发事件,执行动画
|
// 要区分是否首次渲染,首次渲染不触发事件,不执行动画;非首次渲染,触发事件,执行动画
|
||||||
|
this.panelRef.value.style.transformOrigin = 'center center';
|
||||||
if (this.open) {
|
if (this.open) {
|
||||||
if (hasUpdated) {
|
if (hasUpdated) {
|
||||||
const eventProceeded = this.emit('open', { cancelable: true });
|
const eventProceeded = this.emit('open', { cancelable: true });
|
||||||
@@ -136,8 +137,34 @@ let Dialog = class Dialog extends MduiElement {
|
|||||||
this.panelRef.value.focus({ preventScroll: true });
|
this.panelRef.value.focus({ preventScroll: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const duration = getDuration(this, 'medium4');
|
// const duration = getDuration(this, 'medium4');
|
||||||
|
const duration = hasUpdated ? getDuration(this, 'medium4') : 0;
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
// 遮罩层淡入
|
||||||
|
animateTo(this.overlayRef.value, [
|
||||||
|
{ opacity: 0 },
|
||||||
|
{ opacity: 1 }
|
||||||
|
], {
|
||||||
|
duration: hasUpdated ? duration * 0.7 : 0,
|
||||||
|
easing: easingLinear,
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 面板缩放淡入
|
||||||
|
animateTo(this.panelRef.value, [
|
||||||
|
{
|
||||||
|
opacity: 0,
|
||||||
|
transform: 'scale(0.8)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
opacity: 1,
|
||||||
|
transform: 'scale(1)'
|
||||||
|
}
|
||||||
|
], {
|
||||||
|
duration: hasUpdated ? duration : 0,
|
||||||
|
easing: easingEmphasizedDecelerate,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
/* await Promise.all([
|
||||||
animateTo(this.overlayRef.value, [{ opacity: 0 }, { opacity: 1, offset: 0.3 }, { opacity: 1 }], {
|
animateTo(this.overlayRef.value, [{ opacity: 0 }, { opacity: 1, offset: 0.3 }, { opacity: 1 }], {
|
||||||
duration: hasUpdated ? duration : 0,
|
duration: hasUpdated ? duration : 0,
|
||||||
easing: easingLinear,
|
easing: easingLinear,
|
||||||
@@ -162,7 +189,7 @@ let Dialog = class Dialog extends MduiElement {
|
|||||||
duration: hasUpdated ? duration : 0,
|
duration: hasUpdated ? duration : 0,
|
||||||
easing: easingLinear,
|
easing: easingLinear,
|
||||||
})),
|
})),
|
||||||
]);
|
]); */
|
||||||
if (hasUpdated) {
|
if (hasUpdated) {
|
||||||
this.emit('opened');
|
this.emit('opened');
|
||||||
}
|
}
|
||||||
@@ -174,8 +201,32 @@ let Dialog = class Dialog extends MduiElement {
|
|||||||
}
|
}
|
||||||
this.modalHelper.deactivate();
|
this.modalHelper.deactivate();
|
||||||
await stopAnimation();
|
await stopAnimation();
|
||||||
const duration = getDuration(this, 'short4');
|
// const duration = getDuration(this, 'short4');
|
||||||
|
const duration = getDuration(this, 'short3');
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
// 遮罩层淡出
|
||||||
|
animateTo(this.overlayRef.value, [
|
||||||
|
{ opacity: 1 },
|
||||||
|
{ opacity: 0 }
|
||||||
|
], {
|
||||||
|
duration,
|
||||||
|
easing: easingLinear,
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 面板缩放淡出
|
||||||
|
animateTo(this.panelRef.value, [
|
||||||
|
{
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
opacity: 0,
|
||||||
|
}
|
||||||
|
], {
|
||||||
|
duration,
|
||||||
|
easing: easingEmphasizedAccelerate,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
/* await Promise.all([
|
||||||
animateTo(this.overlayRef.value, [{ opacity: 1 }, { opacity: 0 }], {
|
animateTo(this.overlayRef.value, [{ opacity: 1 }, { opacity: 0 }], {
|
||||||
duration,
|
duration,
|
||||||
easing: easingLinear,
|
easing: easingLinear,
|
||||||
@@ -186,7 +237,7 @@ let Dialog = class Dialog extends MduiElement {
|
|||||||
], { duration, easing: easingEmphasizedAccelerate }),
|
], { duration, easing: easingEmphasizedAccelerate }),
|
||||||
animateTo(this.panelRef.value, [{ opacity: 1 }, { opacity: 1, offset: 0.75 }, { opacity: 0 }], { duration, easing: easingLinear }),
|
animateTo(this.panelRef.value, [{ opacity: 1 }, { opacity: 1, offset: 0.75 }, { opacity: 0 }], { duration, easing: easingLinear }),
|
||||||
...children.map((child) => animateTo(child, [{ opacity: 1 }, { opacity: 0, offset: 0.75 }, { opacity: 0 }], { duration, easing: easingLinear })),
|
...children.map((child) => animateTo(child, [{ opacity: 1 }, { opacity: 0, offset: 0.75 }, { opacity: 0 }], { duration, easing: easingLinear })),
|
||||||
]);
|
]); */
|
||||||
this.style.display = 'none';
|
this.style.display = 'none';
|
||||||
unlockScreen(this);
|
unlockScreen(this);
|
||||||
// 对话框关闭后,恢复焦点到原有的元素上
|
// 对话框关闭后,恢复焦点到原有的元素上
|
||||||
|
|||||||
Reference in New Issue
Block a user