mirror of
https://github.com/revoltchat/revite.git
synced 2024-12-25 23:22:06 -05:00
feat(mobx): add message queue store
This commit is contained in:
parent
ec83230c59
commit
faca4ac32b
6 changed files with 118 additions and 76 deletions
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue