Compare commits
6 Commits
125938b8be
...
a3d5e93240
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3d5e93240 | ||
|
|
ed494413fd | ||
|
|
557234841d | ||
|
|
ea17ab2ddd | ||
|
|
20ef8a8514 | ||
|
|
124879f11f |
@@ -1,6 +1,6 @@
|
||||
import Client from "../api/Client.ts"
|
||||
import data from "../Data.ts"
|
||||
import ChatFragment from "./chat/ChatFragment.jsx"
|
||||
import ChatFragment from "./chat/ChatFragment.tsx"
|
||||
import ContactsListItem from "./main/ContactsListItem.jsx"
|
||||
import RecentsListItem from "./main/RecentsListItem.jsx"
|
||||
import useEventListener from './useEventListener.ts'
|
||||
@@ -9,7 +9,7 @@ import RecentChat from "../api/client_data/RecentChat.ts"
|
||||
import Avatar from "./Avatar.tsx"
|
||||
|
||||
import * as React from 'react'
|
||||
import { Button, ButtonIcon, Dialog, NavigationRail, TextField } from "mdui"
|
||||
import { Dialog, NavigationRail, TextField } from "mdui"
|
||||
import Split from 'split.js'
|
||||
import 'mdui/jsx.zh-cn.d.ts'
|
||||
import { checkApiSuccessOrSncakbar } from "./snackbar.ts"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Client from "../api/Client.ts"
|
||||
import data from "../Data.ts"
|
||||
import ChatFragment from "./chat/ChatFragment.jsx"
|
||||
import ChatFragment from "./chat/ChatFragment.tsx"
|
||||
import ContactsListItem from "./main/ContactsListItem.jsx"
|
||||
import RecentsListItem from "./main/RecentsListItem.jsx"
|
||||
import useEventListener from './useEventListener.ts'
|
||||
@@ -9,8 +9,7 @@ import RecentChat from "../api/client_data/RecentChat.ts"
|
||||
import Avatar from "./Avatar.tsx"
|
||||
|
||||
import * as React from 'react'
|
||||
import { Button, ButtonIcon, Dialog, NavigationBar, TextField } from "mdui"
|
||||
import Split from 'split.js'
|
||||
import { Dialog, NavigationBar, TextField } from "mdui"
|
||||
import 'mdui/jsx.zh-cn.d.ts'
|
||||
import { checkApiSuccessOrSncakbar } from "./snackbar.ts"
|
||||
|
||||
@@ -60,7 +59,7 @@ export default function AppMobile() {
|
||||
} as unknown as { [key: string]: User[] })
|
||||
const [navigationItemSelected, setNavigationItemSelected] = React.useState('Recents')
|
||||
|
||||
const navigationBarRef: React.MutableRefObject<NavigationRail | null> = React.useRef(null)
|
||||
const navigationBarRef: React.MutableRefObject<NavigationBar | null> = React.useRef(null)
|
||||
useEventListener(navigationBarRef, 'change', (event) => {
|
||||
setNavigationItemSelected((event.target as HTMLElement as NavigationBar).value as string)
|
||||
})
|
||||
@@ -100,7 +99,7 @@ export default function AppMobile() {
|
||||
<div style={{
|
||||
display: "flex",
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
width: 'var(--whitesilk-window-width)',
|
||||
height: 'var(--whitesilk-window-height)',
|
||||
}}>
|
||||
<LoginDialog
|
||||
@@ -121,61 +120,65 @@ export default function AppMobile() {
|
||||
userProfileDialogRef={userProfileDialogRef}
|
||||
user={myUserProfileCache} />
|
||||
|
||||
<mdui-navigation-bar label-visibility="selected" value="Recents" ref={navigationBarRef}>
|
||||
<mdui-navigation-bar scroll-target="#SideBar" label-visibility="selected" value="Recents" ref={navigationBarRef}>
|
||||
<mdui-navigation-bar-item icon="watch_later--outlined" value="Recents">最近</mdui-navigation-bar-item>
|
||||
<mdui-navigation-bar-item icon="contacts--outlined" value="Contacts">聯絡人</mdui-navigation-bar-item>
|
||||
</mdui-navigation-bar>
|
||||
|
||||
{
|
||||
// 最近聊天
|
||||
<mdui-list style={{
|
||||
height: 'calc(var(--whitesilk-window-height) - 80px)',
|
||||
overflowY: 'auto',
|
||||
marginLeft: '10px',
|
||||
marginRight: '10px',
|
||||
width: '100%',
|
||||
display: navigationItemSelected == "Recents" ? undefined : 'none'
|
||||
}}>
|
||||
{
|
||||
recentsList.map((v) =>
|
||||
<RecentsListItem
|
||||
key={v.id}
|
||||
nickName={v.title}
|
||||
avatar={v.avatar}
|
||||
content={v.content} />
|
||||
)
|
||||
}
|
||||
</mdui-list>
|
||||
}
|
||||
{
|
||||
// 联系人列表
|
||||
<mdui-list style={{
|
||||
height: 'calc(var(--whitesilk-window-height) - 80px)',
|
||||
overflowY: 'auto',
|
||||
marginLeft: '10px',
|
||||
marginRight: '10px',
|
||||
width: '100%',
|
||||
display: navigationItemSelected == "Contacts" ? undefined : 'none'
|
||||
}}>
|
||||
<mdui-collapse accordion value={Object.keys(contactsMap)[0]}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
height: 'calc(100% - 80px)',
|
||||
width: '100%',
|
||||
}} id="SideBar">
|
||||
{
|
||||
// 最近聊天
|
||||
<mdui-list style={{
|
||||
overflowY: 'auto',
|
||||
marginLeft: '10px',
|
||||
marginRight: '10px',
|
||||
width: '100%',
|
||||
display: navigationItemSelected == "Recents" ? undefined : 'none'
|
||||
}}>
|
||||
{
|
||||
Object.keys(contactsMap).map((v) =>
|
||||
<mdui-collapse-item key={v} value={v}>
|
||||
<mdui-list-subheader slot="header">{v}</mdui-list-subheader>
|
||||
{
|
||||
contactsMap[v].map((v2) =>
|
||||
<ContactsListItem
|
||||
key={v2.id}
|
||||
nickName={v2.nickname}
|
||||
avatar={v2.avatar} />
|
||||
)
|
||||
}
|
||||
</mdui-collapse-item>
|
||||
recentsList.map((v) =>
|
||||
<RecentsListItem
|
||||
key={v.id}
|
||||
nickName={v.title}
|
||||
avatar={v.avatar}
|
||||
content={v.content} />
|
||||
)
|
||||
}
|
||||
</mdui-collapse>
|
||||
</mdui-list>
|
||||
}
|
||||
</mdui-list>
|
||||
}
|
||||
{
|
||||
// 联系人列表
|
||||
<mdui-list style={{
|
||||
overflowY: 'auto',
|
||||
marginLeft: '10px',
|
||||
marginRight: '10px',
|
||||
width: '100%',
|
||||
display: navigationItemSelected == "Contacts" ? undefined : 'none'
|
||||
}}>
|
||||
<mdui-collapse accordion value={Object.keys(contactsMap)[0]}>
|
||||
{
|
||||
Object.keys(contactsMap).map((v) =>
|
||||
<mdui-collapse-item key={v} value={v}>
|
||||
<mdui-list-subheader slot="header">{v}</mdui-list-subheader>
|
||||
{
|
||||
contactsMap[v].map((v2) =>
|
||||
<ContactsListItem
|
||||
key={v2.id}
|
||||
nickName={v2.nickname}
|
||||
avatar={v2.avatar} />
|
||||
)
|
||||
}
|
||||
</mdui-collapse-item>
|
||||
)
|
||||
}
|
||||
</mdui-collapse>
|
||||
</mdui-list>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import Message from "./Message.jsx"
|
||||
import MessageContainer from "./MessageContainer.jsx"
|
||||
|
||||
import * as React from 'react'
|
||||
|
||||
export default function ChatFragment({ ...props } = {}) {
|
||||
const messageList = React.useState([])
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflowY: 'auto',
|
||||
}} {...props}>
|
||||
<mdui-top-app-bar style={{
|
||||
position: 'sticky',
|
||||
}}>
|
||||
<mdui-button-icon icon="menu"></mdui-button-icon>
|
||||
<mdui-top-app-bar-title>Title</mdui-top-app-bar-title>
|
||||
<mdui-button-icon icon="more_vert"></mdui-button-icon>
|
||||
</mdui-top-app-bar>
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
}}>
|
||||
<div style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}>
|
||||
<mdui-button variant="text">加載更多</mdui-button>
|
||||
</div>
|
||||
<MessageContainer>
|
||||
</MessageContainer>
|
||||
{
|
||||
// 输入框
|
||||
}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
paddingBottom: '0.1rem',
|
||||
paddingTop: '0.1rem',
|
||||
height: '4rem',
|
||||
position: 'sticky',
|
||||
bottom: '2px',
|
||||
marginLeft: '5px',
|
||||
marginRight: '4px',
|
||||
backgroundColor: 'rgb(var(--mdui-color-background))',
|
||||
}}>
|
||||
<mdui-text-field variant="outlined" placeholder="喵呜~" autosize max-rows="1" style={{
|
||||
marginRight: '10px',
|
||||
}}></mdui-text-field>
|
||||
<mdui-button-icon slot="end-icon" icon="more_vert" style={{
|
||||
marginRight: '6px',
|
||||
}}></mdui-button-icon>
|
||||
<mdui-button-icon icon="send" style={{
|
||||
marginRight: '7px',
|
||||
}}></mdui-button-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
84
client/ui/chat/ChatFragment.tsx
Normal file
84
client/ui/chat/ChatFragment.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Tab } from "mdui"
|
||||
import useEventListener from "../useEventListener.ts"
|
||||
import Message from "./Message.jsx"
|
||||
import MessageContainer from "./MessageContainer.jsx"
|
||||
|
||||
import * as React from 'react'
|
||||
|
||||
export default function ChatFragment({ ...props } = {}) {
|
||||
const messageList = React.useState([])
|
||||
|
||||
const [tabItemSelected, setTabItemSelected] = React.useState('Chat')
|
||||
const tabRef: React.MutableRefObject<Tab | null> = React.useRef(null)
|
||||
useEventListener(tabRef, 'change', (event) => {
|
||||
setTabItemSelected((event.target as HTMLElement as Tab).value as string)
|
||||
})
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflowY: 'auto',
|
||||
}} {...props}>
|
||||
<mdui-tabs ref={tabRef} value="Chat" style={{
|
||||
position: 'sticky',
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
}}>
|
||||
<mdui-tab value="Chat">Title</mdui-tab>
|
||||
<mdui-tab value="Settings">設定</mdui-tab>
|
||||
|
||||
<mdui-tab-panel slot="panel" value="Chat" style={{
|
||||
display: tabItemSelected == "Chat" ? "flex" : "none",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
}}>
|
||||
<div style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
marginTop: "15px"
|
||||
}}>
|
||||
<mdui-button variant="text">加載更多</mdui-button>
|
||||
</div>
|
||||
<MessageContainer>
|
||||
</MessageContainer>
|
||||
{
|
||||
// 输入框
|
||||
}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
paddingBottom: '0.1rem',
|
||||
paddingTop: '0.1rem',
|
||||
height: '4rem',
|
||||
position: 'sticky',
|
||||
bottom: '2px',
|
||||
marginLeft: '5px',
|
||||
marginRight: '4px',
|
||||
backgroundColor: 'rgb(var(--mdui-color-background))',
|
||||
}}>
|
||||
<mdui-text-field variant="outlined" placeholder="喵呜~" autosize max-rows={1} style={{
|
||||
marginRight: '10px',
|
||||
}}></mdui-text-field>
|
||||
<mdui-button-icon slot="end-icon" icon="more_vert" style={{
|
||||
marginRight: '6px',
|
||||
}}></mdui-button-icon>
|
||||
<mdui-button-icon icon="send" style={{
|
||||
marginRight: '7px',
|
||||
}}></mdui-button-icon>
|
||||
</div>
|
||||
</mdui-tab-panel>
|
||||
<mdui-tab-panel slot="panel" value="Settings" style={{
|
||||
display: tabItemSelected == "Settings" ? "flex" : "none",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
}}>
|
||||
Work in progress...
|
||||
</mdui-tab-panel>
|
||||
</mdui-tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ export type CallMethod =
|
||||
"User.setAvatar" |
|
||||
"User.getMyInfo" |
|
||||
|
||||
"Chat.getInfo" |
|
||||
"Chat.sendMessage" |
|
||||
"Chat.getMessageHistory"
|
||||
|
||||
|
||||
@@ -6,6 +6,13 @@ export default class UserApi extends BaseApi {
|
||||
return "Chat"
|
||||
}
|
||||
override onInit(): void {
|
||||
this.registerEvent("Chat.getInfo", (args) => {
|
||||
|
||||
return {
|
||||
code: 501,
|
||||
msg: "未實現",
|
||||
}
|
||||
})
|
||||
this.registerEvent("Chat.sendMessage", (args) => {
|
||||
|
||||
return {
|
||||
|
||||
@@ -6,6 +6,7 @@ import config from '../config.ts'
|
||||
import ChatBean from './ChatBean.ts'
|
||||
import { SQLInputValue } from "node:sqlite"
|
||||
import chalk from "chalk"
|
||||
import User from "./User.ts"
|
||||
|
||||
/**
|
||||
* Chat.ts - Wrapper and manager
|
||||
@@ -20,7 +21,11 @@ export default class Chat {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS ${Chat.table_name} (
|
||||
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
/* Chat ID, 哈希 */ id TEXT,
|
||||
/* Chat ID */ id TEXT NOT NULL,
|
||||
/* 標題 (群組) */ title TEXT,
|
||||
/* 頭像 (群組) */ avatar BLOB,
|
||||
/* UserIdA (私信) */ user_a_id TEXT
|
||||
/* UserIdB (私信) */ user_b_id TEXT,
|
||||
/* 设置 */ settings TEXT NOT NULL
|
||||
);
|
||||
`)
|
||||
@@ -31,14 +36,34 @@ export default class Chat {
|
||||
return this.database.prepare(`SELECT * FROM ${Chat.table_name} WHERE ${condition}`).all(...args) as unknown as ChatBean[]
|
||||
}
|
||||
|
||||
static findById(id: string): Chat {
|
||||
static findById(id: string) {
|
||||
const beans = this.findAllBeansByCondition('id = ?', id)
|
||||
if (beans.length == 0)
|
||||
throw new Error(`找不到 id 为 ${id} 的 Chat`)
|
||||
return null
|
||||
else if (beans.length > 1)
|
||||
console.error(chalk.red(`警告: 查询 id = ${id} 时, 查询到多个相同 ID 的 Chat`))
|
||||
return new Chat(beans[0])
|
||||
}
|
||||
|
||||
static create(chatId: string) {
|
||||
const chat = new Chat(
|
||||
Chat.findAllBeansByCondition(
|
||||
'count = ?',
|
||||
Chat.database.prepare(`INSERT INTO ${Chat.table_name} (
|
||||
id,
|
||||
settings
|
||||
) VALUES (?, ?);`).run(
|
||||
chatId,
|
||||
"{}"
|
||||
).lastInsertRowid
|
||||
)[0]
|
||||
)
|
||||
return chat
|
||||
}
|
||||
|
||||
static createFromTwoUsers(userA: User, userB: User) {
|
||||
return this.create([userA.bean.id, userB.bean.id].sort().join('-'))
|
||||
}
|
||||
|
||||
declare bean: ChatBean
|
||||
constructor(bean: ChatBean) {
|
||||
|
||||
Reference in New Issue
Block a user