mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-22 23:20:58 -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 const CAN_UPLOAD_AT_ONCE = 4;
|
||||||
|
|
||||||
export default observer(({ channel }: Props) => {
|
export default observer(({ channel }: Props) => {
|
||||||
const drafts = useApplicationState().draft;
|
const state = useApplicationState();
|
||||||
|
|
||||||
const [uploadState, setUploadState] = useState<UploadState>({
|
const [uploadState, setUploadState] = useState<UploadState>({
|
||||||
type: "none",
|
type: "none",
|
||||||
|
@ -149,24 +149,10 @@ export default observer(({ channel }: Props) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Push message content to draft.
|
||||||
const setMessage = useCallback(
|
const setMessage = useCallback(
|
||||||
(content?: string) => {
|
(content?: string) => state.draft.set(channel._id, content),
|
||||||
drafts.set(channel._id, content);
|
[state.draft, channel._id],
|
||||||
|
|
||||||
if (content) {
|
|
||||||
dispatch({
|
|
||||||
type: "SET_DRAFT",
|
|
||||||
channel: channel._id,
|
|
||||||
content,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
dispatch({
|
|
||||||
type: "CLEAR_DRAFT",
|
|
||||||
channel: channel._id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[drafts, channel._id],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -184,10 +170,10 @@ export default observer(({ channel }: Props) => {
|
||||||
.join("\n")}\n\n`
|
.join("\n")}\n\n`
|
||||||
: `${content} `;
|
: `${content} `;
|
||||||
|
|
||||||
if (!drafts.has(channel._id)) {
|
if (!state.draft.has(channel._id)) {
|
||||||
setMessage(text);
|
setMessage(text);
|
||||||
} else {
|
} 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",
|
||||||
append as (...args: unknown[]) => void,
|
append as (...args: unknown[]) => void,
|
||||||
);
|
);
|
||||||
}, [drafts, channel._id, setMessage]);
|
}, [state.draft, channel._id, setMessage]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger send message.
|
* Trigger send message.
|
||||||
|
@ -205,7 +191,7 @@ export default observer(({ channel }: Props) => {
|
||||||
if (uploadState.type === "uploading" || uploadState.type === "sending")
|
if (uploadState.type === "uploading" || uploadState.type === "sending")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const content = drafts.get(channel._id)?.trim() ?? "";
|
const content = state.draft.get(channel._id)?.trim() ?? "";
|
||||||
if (uploadState.type === "attached") return sendFile(content);
|
if (uploadState.type === "attached") return sendFile(content);
|
||||||
if (content.length === 0) return;
|
if (content.length === 0) return;
|
||||||
|
|
||||||
|
@ -258,18 +244,13 @@ export default observer(({ channel }: Props) => {
|
||||||
} else {
|
} else {
|
||||||
playSound("outbound");
|
playSound("outbound");
|
||||||
|
|
||||||
dispatch({
|
state.queue.add(nonce, channel._id, {
|
||||||
type: "QUEUE_ADD",
|
|
||||||
nonce,
|
|
||||||
channel: channel._id,
|
|
||||||
message: {
|
|
||||||
_id: nonce,
|
_id: nonce,
|
||||||
channel: channel._id,
|
channel: channel._id,
|
||||||
author: client.user!._id,
|
author: client.user!._id,
|
||||||
|
|
||||||
content,
|
content,
|
||||||
replies,
|
replies,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
defer(() => renderer.jumpToBottom(SMOOTH_SCROLL_ON_RECEIVE));
|
defer(() => renderer.jumpToBottom(SMOOTH_SCROLL_ON_RECEIVE));
|
||||||
|
@ -281,11 +262,7 @@ export default observer(({ channel }: Props) => {
|
||||||
replies,
|
replies,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dispatch({
|
state.queue.fail(nonce, takeError(error));
|
||||||
type: "QUEUE_FAIL",
|
|
||||||
error: takeError(error),
|
|
||||||
nonce,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -525,7 +502,7 @@ export default observer(({ channel }: Props) => {
|
||||||
id="message"
|
id="message"
|
||||||
maxLength={2000}
|
maxLength={2000}
|
||||||
onKeyUp={onKeyUp}
|
onKeyUp={onKeyUp}
|
||||||
value={drafts.get(channel._id) ?? ""}
|
value={state.draft.get(channel._id) ?? ""}
|
||||||
padding="var(--message-box-padding)"
|
padding="var(--message-box-padding)"
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.ctrlKey && e.key === "Enter") {
|
if (e.ctrlKey && e.key === "Enter") {
|
||||||
|
@ -535,7 +512,10 @@ export default observer(({ channel }: Props) => {
|
||||||
|
|
||||||
if (onKeyDown(e)) return;
|
if (onKeyDown(e)) return;
|
||||||
|
|
||||||
if (e.key === "ArrowUp" && !drafts.has(channel._id)) {
|
if (
|
||||||
|
e.key === "ArrowUp" &&
|
||||||
|
!state.draft.has(channel._id)
|
||||||
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
internalEmit("MessageRenderer", "edit_last");
|
internalEmit("MessageRenderer", "edit_last");
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Message } from "revolt.js/dist/maps/Messages";
|
||||||
|
|
||||||
import { useContext, useEffect } from "preact/hooks";
|
import { useContext, useEffect } from "preact/hooks";
|
||||||
|
|
||||||
import { dispatch } from "../../redux";
|
import { useApplicationState } from "../../mobx/State";
|
||||||
import { connectState } from "../../redux/connector";
|
import { connectState } from "../../redux/connector";
|
||||||
import { QueuedMessage } from "../../redux/reducers/queue";
|
import { QueuedMessage } from "../../redux/reducers/queue";
|
||||||
|
|
||||||
|
@ -17,22 +17,13 @@ type Props = {
|
||||||
|
|
||||||
function StateMonitor(props: Props) {
|
function StateMonitor(props: Props) {
|
||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
|
const state = useApplicationState();
|
||||||
useEffect(() => {
|
|
||||||
dispatch({
|
|
||||||
type: "QUEUE_DROP_ALL",
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function add(msg: Message) {
|
function add(msg: Message) {
|
||||||
if (!msg.nonce) return;
|
if (!msg.nonce) return;
|
||||||
if (!props.messages.find((x) => x.id === msg.nonce)) return;
|
if (!props.messages.find((x) => x.id === msg.nonce)) return;
|
||||||
|
state.queue.remove(msg.nonce);
|
||||||
dispatch({
|
|
||||||
type: "QUEUE_REMOVE",
|
|
||||||
nonce: msg.nonce,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client.addListener("message", add);
|
client.addListener("message", add);
|
||||||
|
|
|
@ -32,6 +32,7 @@ import {
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import { useContext } from "preact/hooks";
|
import { useContext } from "preact/hooks";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../mobx/State";
|
||||||
import { dispatch } from "../redux";
|
import { dispatch } from "../redux";
|
||||||
import { connectState } from "../redux/connector";
|
import { connectState } from "../redux/connector";
|
||||||
import {
|
import {
|
||||||
|
@ -141,6 +142,7 @@ export default function ContextMenus() {
|
||||||
const userId = client.user!._id;
|
const userId = client.user!._id;
|
||||||
const status = useContext(StatusContext);
|
const status = useContext(StatusContext);
|
||||||
const isOnline = status === ClientStatus.ONLINE;
|
const isOnline = status === ClientStatus.ONLINE;
|
||||||
|
const state = useApplicationState();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
function contextClick(data?: Action) {
|
function contextClick(data?: Action) {
|
||||||
|
@ -196,11 +198,7 @@ export default function ContextMenus() {
|
||||||
{
|
{
|
||||||
const nonce = data.message.id;
|
const nonce = data.message.id;
|
||||||
const fail = (error: string) =>
|
const fail = (error: string) =>
|
||||||
dispatch({
|
state.queue.fail(nonce, error);
|
||||||
type: "QUEUE_FAIL",
|
|
||||||
nonce,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
client.channels
|
client.channels
|
||||||
.get(data.message.channel)!
|
.get(data.message.channel)!
|
||||||
|
@ -211,19 +209,13 @@ export default function ContextMenus() {
|
||||||
})
|
})
|
||||||
.catch(fail);
|
.catch(fail);
|
||||||
|
|
||||||
dispatch({
|
state.queue.start(nonce);
|
||||||
type: "QUEUE_START",
|
|
||||||
nonce,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "cancel_message":
|
case "cancel_message":
|
||||||
{
|
{
|
||||||
dispatch({
|
state.queue.remove(data.message.id);
|
||||||
type: "QUEUE_REMOVE",
|
|
||||||
nonce: data.message.id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import Draft from "./stores/Draft";
|
||||||
import Experiments from "./stores/Experiments";
|
import Experiments from "./stores/Experiments";
|
||||||
import Layout from "./stores/Layout";
|
import Layout from "./stores/Layout";
|
||||||
import LocaleOptions from "./stores/LocaleOptions";
|
import LocaleOptions from "./stores/LocaleOptions";
|
||||||
|
import MessageQueue from "./stores/MessageQueue";
|
||||||
import NotificationOptions from "./stores/NotificationOptions";
|
import NotificationOptions from "./stores/NotificationOptions";
|
||||||
import ServerConfig from "./stores/ServerConfig";
|
import ServerConfig from "./stores/ServerConfig";
|
||||||
|
|
||||||
|
@ -24,6 +25,7 @@ export default class State {
|
||||||
layout: Layout;
|
layout: Layout;
|
||||||
config: ServerConfig;
|
config: ServerConfig;
|
||||||
notifications: NotificationOptions;
|
notifications: NotificationOptions;
|
||||||
|
queue: MessageQueue;
|
||||||
|
|
||||||
private persistent: [string, Persistent<unknown>][] = [];
|
private persistent: [string, Persistent<unknown>][] = [];
|
||||||
|
|
||||||
|
@ -38,6 +40,7 @@ export default class State {
|
||||||
this.layout = new Layout();
|
this.layout = new Layout();
|
||||||
this.config = new ServerConfig();
|
this.config = new ServerConfig();
|
||||||
this.notifications = new NotificationOptions();
|
this.notifications = new NotificationOptions();
|
||||||
|
this.queue = new MessageQueue();
|
||||||
|
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
this.registerListeners = this.registerListeners.bind(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 { internalSubscribe, internalEmit } from "../../../lib/eventEmitter";
|
||||||
import { ChannelRenderer } from "../../../lib/renderer/Singleton";
|
import { ChannelRenderer } from "../../../lib/renderer/Singleton";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
import { connectState } from "../../../redux/connector";
|
import { connectState } from "../../../redux/connector";
|
||||||
import { QueuedMessage } from "../../../redux/reducers/queue";
|
import { QueuedMessage } from "../../../redux/reducers/queue";
|
||||||
|
|
||||||
|
@ -33,7 +34,6 @@ import MessageEditor from "./MessageEditor";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
highlight?: string;
|
highlight?: string;
|
||||||
queue: QueuedMessage[];
|
|
||||||
renderer: ChannelRenderer;
|
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 client = useClient();
|
||||||
const userId = client.user!._id;
|
const userId = client.user!._id;
|
||||||
|
const queue = useApplicationState().queue;
|
||||||
|
|
||||||
const [editing, setEditing] = useState<string | undefined>(undefined);
|
const [editing, setEditing] = useState<string | undefined>(undefined);
|
||||||
const stopEditing = () => {
|
const stopEditing = () => {
|
||||||
|
@ -192,8 +193,7 @@ const MessageRenderer = observer(({ renderer, queue, highlight }: Props) => {
|
||||||
|
|
||||||
const nonces = renderer.messages.map((x) => x.nonce);
|
const nonces = renderer.messages.map((x) => x.nonce);
|
||||||
if (renderer.atBottom) {
|
if (renderer.atBottom) {
|
||||||
for (const msg of queue) {
|
for (const msg of queue.get(renderer.channel._id)) {
|
||||||
if (msg.channel !== renderer.channel._id) continue;
|
|
||||||
if (nonces.includes(msg.id)) continue;
|
if (nonces.includes(msg.id)) continue;
|
||||||
|
|
||||||
if (previous) {
|
if (previous) {
|
||||||
|
@ -237,11 +237,3 @@ const MessageRenderer = observer(({ renderer, queue, highlight }: Props) => {
|
||||||
|
|
||||||
return <>{render}</>;
|
return <>{render}</>;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default memo(
|
|
||||||
connectState<Omit<Props, "queue">>(MessageRenderer, (state) => {
|
|
||||||
return {
|
|
||||||
queue: state.queue,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
Loading…
Reference in a new issue