From f9570f7c4d27b406bce29ffba59cb6656acf5a55 Mon Sep 17 00:00:00 2001 From: uonr Date: Wed, 15 May 2024 21:47:07 +0900 Subject: [PATCH] feat(spa): send state --- apps/spa/components/pane-channel/useSend.tsx | 18 ++++++++++++++---- apps/spa/state/channel.reducer.ts | 12 ++++++++++++ apps/spa/state/channel.types.ts | 2 +- apps/spa/state/chat.actions.tsx | 12 ++++++++++++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/apps/spa/components/pane-channel/useSend.tsx b/apps/spa/components/pane-channel/useSend.tsx index 3f3576d77..efccd98be 100644 --- a/apps/spa/components/pane-channel/useSend.tsx +++ b/apps/spa/components/pane-channel/useSend.tsx @@ -1,6 +1,6 @@ import { ApiError, Message, User } from '@boluo/api'; import { patch, post } from '@boluo/api-browser'; -import { Atom, useStore } from 'jotai'; +import { useStore } from 'jotai'; import { useCallback, useMemo, useRef } from 'react'; import { FormattedMessage } from 'react-intl'; import { Button } from '@boluo/ui/Button'; @@ -13,6 +13,8 @@ import { parse } from '../../interpreter/parser'; import { upload } from '../../media'; import { ComposeActionUnion } from '../../state/compose.actions'; import { useDefaultInGame } from '../../hooks/useDefaultInGame'; +import { ChatActionUnion } from '../../state/chat.actions'; +import { chatAtom } from '../../state/chat.atoms'; export const useSend = (me: User) => { const channelId = useChannelId(); @@ -48,13 +50,14 @@ export const useSend = (me: User) => { const parsed = store.get(parsedAtom); if (store.get(checkComposeAtom) !== null) return; const backupComposeState = compose; - const dispatch = (action: ComposeActionUnion) => store.set(composeAtom, action); + const chatDispatch = (action: ChatActionUnion) => store.set(chatAtom, action); + const composeDispatch = (action: ComposeActionUnion) => store.set(composeAtom, action); const handleRecover = () => { - dispatch({ type: 'recoverState', payload: backupComposeState }); + composeDispatch({ type: 'recoverState', payload: backupComposeState }); setBanner(null); }; const isEditing = compose.editFor !== null; - dispatch({ type: 'sent', payload: { edit: isEditing } }); + composeDispatch({ type: 'sent', payload: { edit: isEditing } }); const { text, entities, whisperToUsernames } = parse(compose.source); let result: Result; let name = nickname; @@ -67,12 +70,17 @@ export const useSend = (me: User) => { name = inputedName; } } + chatDispatch({ + type: 'messageSending', + payload: { channelId, previewId: compose.previewId, parsed, inGame, name, compose }, + }); let uploadResult: Awaited> | null = null; if (compose.media instanceof File) { uploadResult = await upload(compose.media); } if (uploadResult?.isOk === false) { + chatDispatch({ type: 'messageSendFailed', payload: { channelId, previewId: compose.previewId } }); setBanner({ level: 'WARNING', content: ( @@ -116,8 +124,10 @@ export const useSend = (me: User) => { } if (result.isOk) { + chatDispatch({ type: 'messageSent', payload: { channelId, message: result.some } }); return; } + chatDispatch({ type: 'messageSendFailed', payload: { channelId, previewId: compose.previewId } }); setBanner({ level: 'WARNING', content: ( diff --git a/apps/spa/state/channel.reducer.ts b/apps/spa/state/channel.reducer.ts index b2bdb2a8b..968daed6f 100644 --- a/apps/spa/state/channel.reducer.ts +++ b/apps/spa/state/channel.reducer.ts @@ -161,6 +161,14 @@ const handleResetGc = (state: ChannelState, { payload: { pos } }: ChatAction<'re return { ...state, scheduledGc: { countdown: GC_INITIAL_COUNTDOWN, lowerPos: pos } }; }; +const handleMessageSending = (state: ChannelState, { payload }: ChatAction<'messageSending'>): ChannelState => { + return state; +}; + +const handleMessageSent = (state: ChannelState, { payload }: ChatAction<'messageSent'>): ChannelState => { + return state; +}; + const channelReducer$ = (state: ChannelState, action: ChatActionUnion, initialized: boolean): ChannelState => { switch (action.type) { case 'messagePreview': @@ -171,6 +179,10 @@ const channelReducer$ = (state: ChannelState, action: ChatActionUnion, initializ return handleMessageEdited(state, action); case 'messageDeleted': return handleMessageDeleted(state, action); + case 'messageSending': + return handleMessageSending(state, action); + case 'messageSent': + return handleMessageSent(state, action); case 'messagesLoaded': // This action is triggered by the user // and should be ignored if the chat state diff --git a/apps/spa/state/channel.types.ts b/apps/spa/state/channel.types.ts index 9769bb6a2..dd01c1b15 100644 --- a/apps/spa/state/channel.types.ts +++ b/apps/spa/state/channel.types.ts @@ -9,6 +9,6 @@ export type PreviewItem = Preview & { timestamp: number; }; -export type MessageItem = Message & { type: 'MESSAGE'; optimistic?: true; key: string }; +export type MessageItem = Message & { type: 'MESSAGE'; optimistic?: true; sending?: boolean; key: string }; export type ChatItem = PreviewItem | MessageItem; diff --git a/apps/spa/state/chat.actions.tsx b/apps/spa/state/chat.actions.tsx index 599d2041e..dff9fcf23 100644 --- a/apps/spa/state/chat.actions.tsx +++ b/apps/spa/state/chat.actions.tsx @@ -1,6 +1,8 @@ import type { EventBody, Message, Preview, ServerEvent, SpaceWithRelated } from '@boluo/api'; import type { Empty } from '@boluo/utils'; import { MakeAction } from './actions'; +import type { ParseResult } from '../interpreter/parse-result'; +import type { ComposeState } from './compose.reducer'; export type ChatActionMap = { receiveMessage: EventBody & { type: 'NEW_MESSAGE' }; @@ -8,6 +10,16 @@ export type ChatActionMap = { enterSpace: { spaceId: string }; spaceUpdated: SpaceWithRelated; messagesLoaded: { messages: Message[]; before: number | null; channelId: string; fullLoaded: boolean }; + messageSending: { + channelId: string; + previewId: string; + parsed: ParseResult; + inGame: boolean; + name: string; + compose: ComposeState; + }; + messageSendFailed: { channelId: string; previewId: string }; + messageSent: { channelId: string; message: Message }; messageEdited: { message: Message; channelId: string }; connected: { connection: WebSocket; mailboxId: string }; connecting: { mailboxId: string };