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 Client from "../api/Client.ts"
|
||||||
import data from "../Data.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 ContactsListItem from "./main/ContactsListItem.jsx"
|
||||||
import RecentsListItem from "./main/RecentsListItem.jsx"
|
import RecentsListItem from "./main/RecentsListItem.jsx"
|
||||||
import useEventListener from './useEventListener.ts'
|
import useEventListener from './useEventListener.ts'
|
||||||
@@ -9,7 +9,7 @@ import RecentChat from "../api/client_data/RecentChat.ts"
|
|||||||
import Avatar from "./Avatar.tsx"
|
import Avatar from "./Avatar.tsx"
|
||||||
|
|
||||||
import * as React from 'react'
|
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 Split from 'split.js'
|
||||||
import 'mdui/jsx.zh-cn.d.ts'
|
import 'mdui/jsx.zh-cn.d.ts'
|
||||||
import { checkApiSuccessOrSncakbar } from "./snackbar.ts"
|
import { checkApiSuccessOrSncakbar } from "./snackbar.ts"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Client from "../api/Client.ts"
|
import Client from "../api/Client.ts"
|
||||||
import data from "../Data.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 ContactsListItem from "./main/ContactsListItem.jsx"
|
||||||
import RecentsListItem from "./main/RecentsListItem.jsx"
|
import RecentsListItem from "./main/RecentsListItem.jsx"
|
||||||
import useEventListener from './useEventListener.ts'
|
import useEventListener from './useEventListener.ts'
|
||||||
@@ -9,8 +9,7 @@ import RecentChat from "../api/client_data/RecentChat.ts"
|
|||||||
import Avatar from "./Avatar.tsx"
|
import Avatar from "./Avatar.tsx"
|
||||||
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { Button, ButtonIcon, Dialog, NavigationBar, TextField } from "mdui"
|
import { Dialog, NavigationBar, TextField } from "mdui"
|
||||||
import Split from 'split.js'
|
|
||||||
import 'mdui/jsx.zh-cn.d.ts'
|
import 'mdui/jsx.zh-cn.d.ts'
|
||||||
import { checkApiSuccessOrSncakbar } from "./snackbar.ts"
|
import { checkApiSuccessOrSncakbar } from "./snackbar.ts"
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ export default function AppMobile() {
|
|||||||
} as unknown as { [key: string]: User[] })
|
} as unknown as { [key: string]: User[] })
|
||||||
const [navigationItemSelected, setNavigationItemSelected] = React.useState('Recents')
|
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) => {
|
useEventListener(navigationBarRef, 'change', (event) => {
|
||||||
setNavigationItemSelected((event.target as HTMLElement as NavigationBar).value as string)
|
setNavigationItemSelected((event.target as HTMLElement as NavigationBar).value as string)
|
||||||
})
|
})
|
||||||
@@ -100,7 +99,7 @@ export default function AppMobile() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: '100%',
|
width: 'var(--whitesilk-window-width)',
|
||||||
height: 'var(--whitesilk-window-height)',
|
height: 'var(--whitesilk-window-height)',
|
||||||
}}>
|
}}>
|
||||||
<LoginDialog
|
<LoginDialog
|
||||||
@@ -121,61 +120,65 @@ export default function AppMobile() {
|
|||||||
userProfileDialogRef={userProfileDialogRef}
|
userProfileDialogRef={userProfileDialogRef}
|
||||||
user={myUserProfileCache} />
|
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="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-item icon="contacts--outlined" value="Contacts">聯絡人</mdui-navigation-bar-item>
|
||||||
</mdui-navigation-bar>
|
</mdui-navigation-bar>
|
||||||
|
|
||||||
{
|
<div style={{
|
||||||
// 最近聊天
|
display: 'flex',
|
||||||
<mdui-list style={{
|
height: 'calc(100% - 80px)',
|
||||||
height: 'calc(var(--whitesilk-window-height) - 80px)',
|
width: '100%',
|
||||||
overflowY: 'auto',
|
}} id="SideBar">
|
||||||
marginLeft: '10px',
|
{
|
||||||
marginRight: '10px',
|
// 最近聊天
|
||||||
width: '100%',
|
<mdui-list style={{
|
||||||
display: navigationItemSelected == "Recents" ? undefined : 'none'
|
overflowY: 'auto',
|
||||||
}}>
|
marginLeft: '10px',
|
||||||
{
|
marginRight: '10px',
|
||||||
recentsList.map((v) =>
|
width: '100%',
|
||||||
<RecentsListItem
|
display: navigationItemSelected == "Recents" ? undefined : 'none'
|
||||||
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]}>
|
|
||||||
{
|
{
|
||||||
Object.keys(contactsMap).map((v) =>
|
recentsList.map((v) =>
|
||||||
<mdui-collapse-item key={v} value={v}>
|
<RecentsListItem
|
||||||
<mdui-list-subheader slot="header">{v}</mdui-list-subheader>
|
key={v.id}
|
||||||
{
|
nickName={v.title}
|
||||||
contactsMap[v].map((v2) =>
|
avatar={v.avatar}
|
||||||
<ContactsListItem
|
content={v.content} />
|
||||||
key={v2.id}
|
|
||||||
nickName={v2.nickname}
|
|
||||||
avatar={v2.avatar} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</mdui-collapse-item>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</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>
|
</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.setAvatar" |
|
||||||
"User.getMyInfo" |
|
"User.getMyInfo" |
|
||||||
|
|
||||||
|
"Chat.getInfo" |
|
||||||
"Chat.sendMessage" |
|
"Chat.sendMessage" |
|
||||||
"Chat.getMessageHistory"
|
"Chat.getMessageHistory"
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ export default class UserApi extends BaseApi {
|
|||||||
return "Chat"
|
return "Chat"
|
||||||
}
|
}
|
||||||
override onInit(): void {
|
override onInit(): void {
|
||||||
|
this.registerEvent("Chat.getInfo", (args) => {
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 501,
|
||||||
|
msg: "未實現",
|
||||||
|
}
|
||||||
|
})
|
||||||
this.registerEvent("Chat.sendMessage", (args) => {
|
this.registerEvent("Chat.sendMessage", (args) => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import config from '../config.ts'
|
|||||||
import ChatBean from './ChatBean.ts'
|
import ChatBean from './ChatBean.ts'
|
||||||
import { SQLInputValue } from "node:sqlite"
|
import { SQLInputValue } from "node:sqlite"
|
||||||
import chalk from "chalk"
|
import chalk from "chalk"
|
||||||
|
import User from "./User.ts"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chat.ts - Wrapper and manager
|
* Chat.ts - Wrapper and manager
|
||||||
@@ -20,7 +21,11 @@ export default class Chat {
|
|||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS ${Chat.table_name} (
|
CREATE TABLE IF NOT EXISTS ${Chat.table_name} (
|
||||||
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
/* 序号 */ 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
|
/* 设置 */ 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[]
|
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)
|
const beans = this.findAllBeansByCondition('id = ?', id)
|
||||||
if (beans.length == 0)
|
if (beans.length == 0)
|
||||||
throw new Error(`找不到 id 为 ${id} 的 Chat`)
|
return null
|
||||||
else if (beans.length > 1)
|
else if (beans.length > 1)
|
||||||
console.error(chalk.red(`警告: 查询 id = ${id} 时, 查询到多个相同 ID 的 Chat`))
|
console.error(chalk.red(`警告: 查询 id = ${id} 时, 查询到多个相同 ID 的 Chat`))
|
||||||
return new Chat(beans[0])
|
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
|
declare bean: ChatBean
|
||||||
constructor(bean: ChatBean) {
|
constructor(bean: ChatBean) {
|
||||||
|
|||||||
Reference in New Issue
Block a user