feat: 收發消息
This commit is contained in:
@@ -1,14 +1,14 @@
|
|||||||
import { Tab } from "mdui"
|
import { Tab, TextField } from "mdui"
|
||||||
import useEventListener from "../useEventListener.ts"
|
import useEventListener from "../useEventListener.ts"
|
||||||
import Element_Message from "./Message.jsx"
|
import Element_Message from "./Message.tsx"
|
||||||
import MessageContainer from "./MessageContainer.jsx"
|
import MessageContainer from "./MessageContainer.tsx"
|
||||||
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import Client from "../../api/Client.ts"
|
import Client from "../../api/Client.ts"
|
||||||
import Message from "../../api/client_data/Message.ts"
|
import Message from "../../api/client_data/Message.ts"
|
||||||
import Chat from "../../api/client_data/Chat.ts"
|
import Chat from "../../api/client_data/Chat.ts"
|
||||||
import data from "../../Data.ts"
|
import data from "../../Data.ts"
|
||||||
import { checkApiSuccessOrSncakbar } from "../snackbar.ts"
|
import { checkApiSuccessOrSncakbar, snackbar } from "../snackbar.ts"
|
||||||
import useAsyncEffect from "../useAsyncEffect.ts"
|
import useAsyncEffect from "../useAsyncEffect.ts"
|
||||||
|
|
||||||
interface Args extends React.HTMLAttributes<HTMLElement> {
|
interface Args extends React.HTMLAttributes<HTMLElement> {
|
||||||
@@ -23,6 +23,7 @@ export default function ChatFragment({ target, ...props }: Args) {
|
|||||||
|
|
||||||
const [tabItemSelected, setTabItemSelected] = React.useState('Chat')
|
const [tabItemSelected, setTabItemSelected] = React.useState('Chat')
|
||||||
const tabRef = React.useRef<Tab>(null)
|
const tabRef = React.useRef<Tab>(null)
|
||||||
|
const chatPanelRef = React.useRef<HTMLElement>(null)
|
||||||
useEventListener(tabRef, 'change', () => {
|
useEventListener(tabRef, 'change', () => {
|
||||||
setTabItemSelected(tabRef.current?.value || "Chat")
|
setTabItemSelected(tabRef.current?.value || "Chat")
|
||||||
})
|
})
|
||||||
@@ -35,27 +36,70 @@ export default function ChatFragment({ target, ...props }: Args) {
|
|||||||
if (re.code != 200)
|
if (re.code != 200)
|
||||||
return checkApiSuccessOrSncakbar(re, "對話錯誤")
|
return checkApiSuccessOrSncakbar(re, "對話錯誤")
|
||||||
setChatInfo(re.data as Chat)
|
setChatInfo(re.data as Chat)
|
||||||
|
|
||||||
|
loadMore()
|
||||||
}, [target])
|
}, [target])
|
||||||
|
|
||||||
let page = 0
|
const page = React.useRef(0)
|
||||||
async function loadMore() {
|
async function loadMore() {
|
||||||
const re = await Client.invoke("Chat.getMessageHistory", {
|
const re = await Client.invoke("Chat.getMessageHistory", {
|
||||||
token: data.access_token,
|
token: data.access_token,
|
||||||
target,
|
target,
|
||||||
page,
|
page: page.current,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (checkApiSuccessOrSncakbar(re, "拉取歷史記錄失敗")) return
|
if (checkApiSuccessOrSncakbar(re, "拉取歷史記錄失敗")) return
|
||||||
page++
|
const returnMsgs = (re.data!.messages as Message[]).reverse()
|
||||||
setMessagesList(messagesList.concat())
|
if (returnMsgs.length == 0)
|
||||||
|
return snackbar({
|
||||||
|
message: "已經沒有消息了哦~",
|
||||||
|
placement: 'top',
|
||||||
|
})
|
||||||
|
setMessagesList(returnMsgs.concat(messagesList))
|
||||||
|
|
||||||
|
if (page.current == 0 + 1)
|
||||||
|
setTimeout(() => chatPanelRef.current!.scrollTo({
|
||||||
|
top: 10000000000,
|
||||||
|
behavior: "smooth",
|
||||||
|
}), 100)
|
||||||
|
|
||||||
|
page.current++
|
||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
interface OnMessageData {
|
||||||
return () => {
|
chat: string
|
||||||
|
msg: Message
|
||||||
|
}
|
||||||
|
Client.on('Client.onMessage', (data: unknown) => {
|
||||||
|
const { chat, msg } = (data as OnMessageData)
|
||||||
|
if (target == chat) {
|
||||||
|
setMessagesList(messagesList.concat([msg]))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
return () => {
|
||||||
|
Client.off('Client.onMessage')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const inputRef = React.useRef<TextField>(null)
|
||||||
|
|
||||||
|
async function sendMessage() {
|
||||||
|
const text = inputRef.current!.value
|
||||||
|
|
||||||
|
const re = await Client.invoke("Chat.sendMessage", {
|
||||||
|
token: data.access_token,
|
||||||
|
target,
|
||||||
|
text,
|
||||||
|
}, 5000)
|
||||||
|
if (checkApiSuccessOrSncakbar(re, "發送失敗")) return
|
||||||
|
inputRef.current!.value = ''
|
||||||
|
|
||||||
|
chatPanelRef.current!.scrollTo({
|
||||||
|
top: 10000000000,
|
||||||
|
behavior: "smooth",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -76,7 +120,7 @@ export default function ChatFragment({ target, ...props }: Args) {
|
|||||||
}</mdui-tab>
|
}</mdui-tab>
|
||||||
<mdui-tab value="Settings">設定</mdui-tab>
|
<mdui-tab value="Settings">設定</mdui-tab>
|
||||||
|
|
||||||
<mdui-tab-panel slot="panel" value="Chat" style={{
|
<mdui-tab-panel slot="panel" value="Chat" ref={chatPanelRef} style={{
|
||||||
display: tabItemSelected == "Chat" ? "flex" : "none",
|
display: tabItemSelected == "Chat" ? "flex" : "none",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
@@ -84,11 +128,24 @@ export default function ChatFragment({ target, ...props }: Args) {
|
|||||||
<div style={{
|
<div style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
marginTop: "15px"
|
paddingTop: "15px",
|
||||||
|
|
||||||
}}>
|
}}>
|
||||||
<mdui-button variant="text">加載更多</mdui-button>
|
<mdui-button variant="text" onClick={() => loadMore()}>加載更多</mdui-button>
|
||||||
</div>
|
</div>
|
||||||
<MessageContainer>
|
<MessageContainer style={{
|
||||||
|
paddingTop: "15px",
|
||||||
|
flexGrow: '1',
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
messagesList.map((msg) =>
|
||||||
|
<Element_Message
|
||||||
|
key={msg.id}
|
||||||
|
userId={msg.user_id}>
|
||||||
|
{msg.text}
|
||||||
|
</Element_Message>
|
||||||
|
)
|
||||||
|
}
|
||||||
</MessageContainer>
|
</MessageContainer>
|
||||||
{
|
{
|
||||||
// 输入框
|
// 输入框
|
||||||
@@ -96,16 +153,19 @@ export default function ChatFragment({ target, ...props }: Args) {
|
|||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingBottom: '0.1rem',
|
paddingBottom: '2px',
|
||||||
paddingTop: '0.1rem',
|
paddingTop: '0.1rem',
|
||||||
height: '4rem',
|
height: '4rem',
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
bottom: '2px',
|
bottom: '0',
|
||||||
marginLeft: '5px',
|
marginLeft: '5px',
|
||||||
marginRight: '4px',
|
marginRight: '4px',
|
||||||
backgroundColor: 'rgb(var(--mdui-color-background))',
|
backgroundColor: 'rgb(var(--mdui-color-background))',
|
||||||
}}>
|
}}>
|
||||||
<mdui-text-field variant="outlined" placeholder="喵呜~" autosize max-rows={1} style={{
|
<mdui-text-field variant="outlined" placeholder="喵呜~" autosize ref={inputRef as any} max-rows={1} onKeyDown={(event) => {
|
||||||
|
if (event.ctrlKey && event.key == 'Enter')
|
||||||
|
sendMessage()
|
||||||
|
}} style={{
|
||||||
marginRight: '10px',
|
marginRight: '10px',
|
||||||
}}></mdui-text-field>
|
}}></mdui-text-field>
|
||||||
<mdui-button-icon slot="end-icon" icon="more_vert" style={{
|
<mdui-button-icon slot="end-icon" icon="more_vert" style={{
|
||||||
@@ -113,7 +173,7 @@ export default function ChatFragment({ target, ...props }: Args) {
|
|||||||
}}></mdui-button-icon>
|
}}></mdui-button-icon>
|
||||||
<mdui-button-icon icon="send" style={{
|
<mdui-button-icon icon="send" style={{
|
||||||
marginRight: '7px',
|
marginRight: '7px',
|
||||||
}}></mdui-button-icon>
|
}} onClick={() => sendMessage()}></mdui-button-icon>
|
||||||
</div>
|
</div>
|
||||||
</mdui-tab-panel>
|
</mdui-tab-panel>
|
||||||
<mdui-tab-panel slot="panel" value="Settings" style={{
|
<mdui-tab-panel slot="panel" value="Settings" style={{
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import EventCallbackFunction from "../typedef/EventCallbackFunction.ts"
|
import EventCallbackFunction from "../typedef/EventCallbackFunction.ts"
|
||||||
import ApiManager from "./ApiManager.ts"
|
import ApiManager from "./ApiManager.ts"
|
||||||
import { CallMethod } from './ApiDeclare.ts'
|
import { CallMethod, ClientEvent } from './ApiDeclare.ts'
|
||||||
import User from "../data/User.ts"
|
import User from "../data/User.ts"
|
||||||
import Token from "./Token.ts"
|
import Token from "./Token.ts"
|
||||||
|
import * as SocketIo from "socket.io"
|
||||||
|
|
||||||
export default abstract class BaseApi {
|
export default abstract class BaseApi {
|
||||||
abstract getName(): string
|
abstract getName(): string
|
||||||
@@ -34,4 +35,7 @@ export default abstract class BaseApi {
|
|||||||
if (!name.startsWith(this.getName() + ".")) throw Error("注冊的事件應該與接口集合命名空間相匹配: " + name)
|
if (!name.startsWith(this.getName() + ".")) throw Error("注冊的事件應該與接口集合命名空間相匹配: " + name)
|
||||||
ApiManager.addEventListener(name, func)
|
ApiManager.addEventListener(name, func)
|
||||||
}
|
}
|
||||||
|
emitToClient(client: SocketIo.Socket, name: ClientEvent, args: { [key: string]: unknown }) {
|
||||||
|
client.emit("The_White_Silk", name, args)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Chat from "../data/Chat.ts";
|
|||||||
import ChatPrivate from "../data/ChatPrivate.ts";
|
import ChatPrivate from "../data/ChatPrivate.ts";
|
||||||
import MessagesManager from "../data/MessagesManager.ts";
|
import MessagesManager from "../data/MessagesManager.ts";
|
||||||
import User from "../data/User.ts"
|
import User from "../data/User.ts"
|
||||||
|
import ApiManager from "./ApiManager.ts";
|
||||||
import BaseApi from "./BaseApi.ts"
|
import BaseApi from "./BaseApi.ts"
|
||||||
import TokenManager from "./TokenManager.ts"
|
import TokenManager from "./TokenManager.ts"
|
||||||
|
|
||||||
@@ -57,10 +58,10 @@ export default class ChatApi extends BaseApi {
|
|||||||
* 發送訊息
|
* 發送訊息
|
||||||
* @param token 令牌
|
* @param token 令牌
|
||||||
* @param target 目標對話
|
* @param target 目標對話
|
||||||
* @param
|
* @param text 消息内容
|
||||||
*/
|
*/
|
||||||
this.registerEvent("Chat.sendMessage", (args, { deviceId }) => {
|
this.registerEvent("Chat.sendMessage", (args, { deviceId }) => {
|
||||||
if (this.checkArgsMissing(args, ['token', 'target'])) return {
|
if (this.checkArgsMissing(args, ['token', 'target', 'text'])) return {
|
||||||
msg: "參數缺失",
|
msg: "參數缺失",
|
||||||
code: 400,
|
code: 400,
|
||||||
}
|
}
|
||||||
@@ -71,9 +72,42 @@ export default class ChatApi extends BaseApi {
|
|||||||
msg: "令牌無效",
|
msg: "令牌無效",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chat = Chat.findById(args.target as string)
|
||||||
|
if (chat == null) return {
|
||||||
|
code: 404,
|
||||||
|
msg: "對話不存在",
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = {
|
||||||
|
text: args.text as string,
|
||||||
|
user_id: token.author,
|
||||||
|
}
|
||||||
|
const id = MessagesManager.getInstanceForChat(chat).addMessage(msg)
|
||||||
|
|
||||||
|
const users: string[] = []
|
||||||
|
if (chat.bean.type == 'private') {
|
||||||
|
users.push(token.author as string)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const user of users) {
|
||||||
|
if (ApiManager.checkUserIsOnline(user)) {
|
||||||
|
const sockets = ApiManager.getUserClientSockets(user)
|
||||||
|
for (const socket of Object.keys(sockets))
|
||||||
|
this.emitToClient(sockets[socket], 'Client.onMessage', {
|
||||||
|
chat: chat.bean.id,
|
||||||
|
msg: {
|
||||||
|
...msg,
|
||||||
|
id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// TODO: EventStore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 501,
|
code: 200,
|
||||||
msg: "未實現",
|
msg: "成功",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
@@ -104,7 +138,7 @@ export default class ChatApi extends BaseApi {
|
|||||||
code: 200,
|
code: 200,
|
||||||
msg: "成功",
|
msg: "成功",
|
||||||
data: {
|
data: {
|
||||||
messages: MessagesManager.getInstanceForChat(chat).getMessagesWithPage(),
|
messages: MessagesManager.getInstanceForChat(chat).getMessagesWithPage(15, args.page as number),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user