feat(mobx): add message queue store

This commit is contained in:
Paul 2021-12-12 15:33:47 +00:00
parent ec83230c59
commit faca4ac32b
6 changed files with 118 additions and 76 deletions

View file

@ -116,7 +116,7 @@ const RE_SED = new RegExp("^s/([^])*/([^])*$");
export const CAN_UPLOAD_AT_ONCE = 4;
export default observer(({ channel }: Props) => {
const drafts = useApplicationState().draft;
const state = useApplicationState();
const [uploadState, setUploadState] = useState<UploadState>({
type: "none",
@ -149,24 +149,10 @@ export default observer(({ channel }: Props) => {
);
}
// Push message content to draft.
const setMessage = useCallback(
(content?: string) => {
drafts.set(channel._id, content);
if (content) {
dispatch({
type: "SET_DRAFT",
channel: channel._id,
content,
});
} else {
dispatch({
type: "CLEAR_DRAFT",
channel: channel._id,
});
}
},
[drafts, channel._id],
(content?: string) => state.draft.set(channel._id, content),
[state.draft, channel._id],
);
useEffect(() => {
@ -184,10 +170,10 @@ export default observer(({ channel }: Props) => {
.join("\n")}\n\n`
: `${content} `;
if (!drafts.has(channel._id)) {
if (!state.draft.has(channel._id)) {
setMessage(text);
} else {
setMessage(`${drafts.get(channel._id)}\n${text}`);
setMessage(`${state.draft.get(channel._id)}\n${text}`);
}
}
@ -196,7 +182,7 @@ export default observer(({ channel }: Props) => {
"append",
append as (...args: unknown[]) => void,
);
}, [drafts, channel._id, setMessage]);
}, [state.draft, channel._id, setMessage]);
/**
* Trigger send message.
@ -205,7 +191,7 @@ export default observer(({ channel }: Props) => {
if (uploadState.type === "uploading" || uploadState.type === "sending")
return;
const content = drafts.get(channel._id)?.trim() ?? "";
const content = state.draft.get(channel._id)?.trim() ?? "";
if (uploadState.type === "attached") return sendFile(content);
if (content.length === 0) return;
@ -258,18 +244,13 @@ export default observer(({ channel }: Props) => {
} else {
playSound("outbound");
dispatch({
type: "QUEUE_ADD",
nonce,
state.queue.add(nonce, channel._id, {
_id: nonce,
channel: channel._id,
message: {
_id: nonce,
channel: channel._id,
author: client.user!._id,
author: client.user!._id,
content,
replies,
},
content,
replies,
});
defer(() => renderer.jumpToBottom(SMOOTH_SCROLL_ON_RECEIVE));
@ -281,11 +262,7 @@ export default observer(({ channel }: Props) => {
replies,
});
} catch (error) {
dispatch({
type: "QUEUE_FAIL",
error: takeError(error),
nonce,
});
state.queue.fail(nonce, takeError(error));
}
}
}
@ -525,7 +502,7 @@ export default observer(({ channel }: Props) => {
id="message"
maxLength={2000}
onKeyUp={onKeyUp}
value={drafts.get(channel._id) ?? ""}
value={state.draft.get(channel._id) ?? ""}
padding="var(--message-box-padding)"
onKeyDown={(e) => {
if (e.ctrlKey && e.key === "Enter") {
@ -535,7 +512,10 @@ export default observer(({ channel }: Props) => {
if (onKeyDown(e)) return;
if (e.key === "ArrowUp" && !drafts.has(channel._id)) {
if (
e.key === "ArrowUp" &&
!state.draft.has(channel._id)
) {
e.preventDefault();
internalEmit("MessageRenderer", "edit_last");
return;

View file

@ -5,7 +5,7 @@ import { Message } from "revolt.js/dist/maps/Messages";
import { useContext, useEffect } from "preact/hooks";
import { dispatch } from "../../redux";
import { useApplicationState } from "../../mobx/State";
import { connectState } from "../../redux/connector";
import { QueuedMessage } from "../../redux/reducers/queue";
@ -17,22 +17,13 @@ type Props = {
function StateMonitor(props: Props) {
const client = useContext(AppContext);
useEffect(() => {
dispatch({
type: "QUEUE_DROP_ALL",
});
}, []);
const state = useApplicationState();
useEffect(() => {
function add(msg: Message) {
if (!msg.nonce) return;
if (!props.messages.find((x) => x.id === msg.nonce)) return;
dispatch({
type: "QUEUE_REMOVE",
nonce: msg.nonce,
});
state.queue.remove(msg.nonce);
}
client.addListener("message", add);

View file

@ -32,6 +32,7 @@ import {
import { Text } from "preact-i18n";
import { useContext } from "preact/hooks";
import { useApplicationState } from "../mobx/State";
import { dispatch } from "../redux";
import { connectState } from "../redux/connector";
import {
@ -141,6 +142,7 @@ export default function ContextMenus() {
const userId = client.user!._id;
const status = useContext(StatusContext);
const isOnline = status === ClientStatus.ONLINE;
const state = useApplicationState();
const history = useHistory();
function contextClick(data?: Action) {
@ -196,11 +198,7 @@ export default function ContextMenus() {
{
const nonce = data.message.id;
const fail = (error: string) =>
dispatch({
type: "QUEUE_FAIL",
nonce,
error,
});
state.queue.fail(nonce, error);
client.channels
.get(data.message.channel)!
@ -211,19 +209,13 @@ export default function ContextMenus() {
})
.catch(fail);
dispatch({
type: "QUEUE_START",
nonce,
});
state.queue.start(nonce);
}
break;
case "cancel_message":
{
dispatch({
type: "QUEUE_REMOVE",
nonce: data.message.id,
});
state.queue.remove(data.message.id);
}
break;

View file

@ -10,6 +10,7 @@ import Draft from "./stores/Draft";
import Experiments from "./stores/Experiments";
import Layout from "./stores/Layout";
import LocaleOptions from "./stores/LocaleOptions";
import MessageQueue from "./stores/MessageQueue";
import NotificationOptions from "./stores/NotificationOptions";
import ServerConfig from "./stores/ServerConfig";
@ -24,6 +25,7 @@ export default class State {
layout: Layout;
config: ServerConfig;
notifications: NotificationOptions;
queue: MessageQueue;
private persistent: [string, Persistent<unknown>][] = [];
@ -38,6 +40,7 @@ export default class State {
this.layout = new Layout();
this.config = new ServerConfig();
this.notifications = new NotificationOptions();
this.queue = new MessageQueue();
makeAutoObservable(this);
this.registerListeners = this.registerListeners.bind(this);

View file

@ -0,0 +1,84 @@
import {
action,
computed,
IObservableArray,
makeAutoObservable,
observable,
} from "mobx";
import Store from "../interfaces/Store";
export enum QueueStatus {
SENDING = "sending",
ERRORED = "errored",
}
export interface Reply {
id: string;
mention: boolean;
}
export type QueuedMessageData = {
_id: string;
author: string;
channel: string;
content: string;
replies: Reply[];
};
export interface QueuedMessage {
id: string;
channel: string;
data: QueuedMessageData;
status: QueueStatus;
error?: string;
}
/**
* Handles waiting for messages to send and send failure.
*/
export default class MessageQueue implements Store {
private messages: IObservableArray<QueuedMessage>;
/**
* Construct new MessageQueue store.
*/
constructor() {
this.messages = observable.array([]);
makeAutoObservable(this);
}
get id() {
return "queue";
}
@action add(id: string, channel: string, data: QueuedMessageData) {
this.messages.push({
id,
channel,
data,
status: QueueStatus.SENDING,
});
}
@action fail(id: string, error: string) {
const entry = this.messages.find((x) => x.id === id)!;
entry.status = QueueStatus.ERRORED;
entry.error = error;
}
@action start(id: string) {
const entry = this.messages.find((x) => x.id === id)!;
entry.status = QueueStatus.SENDING;
}
@action remove(id: string) {
const entry = this.messages.find((x) => x.id === id)!;
this.messages.remove(entry);
}
@computed get(channel: string) {
return this.messages.filter((x) => x.channel === channel);
}
}

View file

@ -16,6 +16,7 @@ import { useEffect, useState } from "preact/hooks";
import { internalSubscribe, internalEmit } from "../../../lib/eventEmitter";
import { ChannelRenderer } from "../../../lib/renderer/Singleton";
import { useApplicationState } from "../../../mobx/State";
import { connectState } from "../../../redux/connector";
import { QueuedMessage } from "../../../redux/reducers/queue";
@ -33,7 +34,6 @@ import MessageEditor from "./MessageEditor";
interface Props {
highlight?: string;
queue: QueuedMessage[];
renderer: ChannelRenderer;
}
@ -48,9 +48,10 @@ const BlockedMessage = styled.div`
}
`;
const MessageRenderer = observer(({ renderer, queue, highlight }: Props) => {
export default observer(({ renderer, highlight }: Props) => {
const client = useClient();
const userId = client.user!._id;
const queue = useApplicationState().queue;
const [editing, setEditing] = useState<string | undefined>(undefined);
const stopEditing = () => {
@ -192,8 +193,7 @@ const MessageRenderer = observer(({ renderer, queue, highlight }: Props) => {
const nonces = renderer.messages.map((x) => x.nonce);
if (renderer.atBottom) {
for (const msg of queue) {
if (msg.channel !== renderer.channel._id) continue;
for (const msg of queue.get(renderer.channel._id)) {
if (nonces.includes(msg.id)) continue;
if (previous) {
@ -237,11 +237,3 @@ const MessageRenderer = observer(({ renderer, queue, highlight }: Props) => {
return <>{render}</>;
});
export default memo(
connectState<Omit<Props, "queue">>(MessageRenderer, (state) => {
return {
queue: state.queue,
};
}),
);