1
0
mirror of https://github.com/spacebarchat/client.git synced 2024-11-25 03:32:54 +01:00

send messages and typing events

This commit is contained in:
Puyodead1 2023-09-10 16:36:44 -04:00
parent aa7a8327e4
commit 0be4d972d0
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
6 changed files with 101 additions and 34 deletions

View File

@ -4,7 +4,6 @@ import React from "react";
import Moment from "react-moment";
import styled from "styled-components";
import { ContextMenuContext } from "../../contexts/ContextMenuContext";
import { useAppStore } from "../../stores/AppStore";
import { MessageLike } from "../../stores/objects/Message";
import { calendarStrings } from "../../utils/i18n";
import Avatar from "../Avatar";
@ -98,8 +97,6 @@ interface Props {
* Component for rendering a single message
*/
function Message({ message, isHeader, isSending, isFailed }: Props) {
const app = useAppStore();
const contextMenu = React.useContext(ContextMenuContext);
const [contextMenuItems, setContextMenuItems] = React.useState<IContextMenuItem[]>([
{
@ -237,9 +234,7 @@ function Message({ message, isHeader, isSending, isFailed }: Props) {
{renderMessageContent()}
{"files" in message && message.files?.length !== 0 && (
<div>
<AttachmentUploadProgress message={message} />
</div>
<AttachmentUploadProgress message={message} />
)}
</MessageContentContainer>
</Container>

View File

@ -1,15 +1,23 @@
import Channel from "../../stores/objects/Channel";
import { ChannelType } from "@spacebarchat/spacebar-api-types/v9";
import {
ChannelType,
MessageType,
RESTPostAPIChannelMessageJSONBody,
Routes,
} from "@spacebarchat/spacebar-api-types/v9";
import { observer } from "mobx-react-lite";
import React from "react";
import styled from "styled-components";
import useLogger from "../../hooks/useLogger";
import { useAppStore } from "../../stores/AppStore";
import Guild from "../../stores/objects/Guild";
import Snowflake from "../../utils/Snowflake";
import { debounce } from "../../utils/debounce";
import { isTouchscreenDevice } from "../../utils/isTouchscreenDevice";
import MessageTextArea from "./MessageTextArea";
import FileUpload from "./attachments/AttachmentUpload";
import AttachmentUploadList from "./attachments/AttachmentUploadPreview";
import FileUpload from "./attachments/FileUpload";
const Container = styled.div`
padding: 0 16px;
@ -62,6 +70,7 @@ interface Props {
* Component for sending messages
*/
function MessageInput({ channel }: Props) {
const app = useAppStore();
const logger = useLogger("MessageInput");
const [content, setContent] = React.useState("");
const [uploadState, setUploadState] = React.useState<UploadState>({
@ -73,11 +82,11 @@ function MessageInput({ channel }: Props) {
/**
* Starts typing for client user and triggers gateway event
*/
const startTyping = React.useCallback(() => {
const startTyping = React.useCallback(async () => {
if (typeof typing === "number" && typing > +new Date()) return;
logger.debug("ShouldStartTyping");
// TODO: send typing request
await app.rest.post(Routes.channelTyping(channel.id));
setTyping(+new Date() + 10_000);
}, [typing, setTyping]);
@ -113,21 +122,83 @@ function MessageInput({ channel }: Props) {
return true;
}, [uploadState, content]);
const send = React.useCallback(() => {
if (!canSendMessage()) return;
logger.debug("ShouldSendMessage");
const sendMessage = React.useCallback(async () => {
stopTyping();
const shouldFail = app.experiments.isTreatmentEnabled("message_queue", 2);
const shouldSend = !app.experiments.isTreatmentEnabled("message_queue", 1);
if (!canSendMessage() && !shouldFail) return;
const contentCopy = content;
const uploadStateCopy = { ...uploadState };
setContent("");
setUploadState({ type: UploadStateType.NONE, files: [] });
const nonce = Snowflake.generate();
const msg = app.queue.add({
id: nonce,
content: contentCopy,
files: uploadState.files,
author: app.account!.raw,
channel: channel.id,
timestamp: new Date().toISOString(),
type: MessageType.Default,
});
if (shouldSend) {
try {
let body: RESTPostAPIChannelMessageJSONBody | FormData;
if (uploadStateCopy.files.length > 0) {
const data = new FormData();
data.append("payload_json", JSON.stringify({ content, nonce }));
uploadStateCopy.files.forEach((file, index) => {
data.append(`files[${index}]`, file);
});
body = data;
} else {
body = { content, nonce };
}
await channel.sendMessage(body, msg);
} catch (e) {
const error = e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error";
msg.fail(error);
}
} else {
msg.fail("Message queue experiment");
}
}, [content, uploadState, channel, canSendMessage]);
const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
// TODO:
if (e.key === "Enter" && !e.shiftKey) {
if (e.ctrlKey && e.key === "Enter") {
e.preventDefault();
send();
return sendMessage();
}
// TODO: handle editing last message
if (!e.shiftKey && e.key === "Enter" && !isTouchscreenDevice) {
e.preventDefault();
return sendMessage();
}
if (e.key === "Escape") {
if (uploadState.type === UploadStateType.ATTACHED && uploadState.files.length > 0) {
setUploadState({
type: UploadStateType.NONE,
files: [],
});
}
}
debouncedStopTyping(true);
};
const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setContent(e.target.value);
startTyping();
};
return (
<Container>
<InnerWrapper>
@ -179,13 +250,8 @@ function MessageInput({ channel }: Props) {
uploadState.type === UploadStateType.UPLOADING ||
uploadState.type === UploadStateType.SENDING
}
onChange={(e) => {
setContent(e.target.value);
startTyping();
}}
onKeyDown={() => {
debouncedStopTyping();
}}
onChange={onChange}
onKeyDown={onKeyDown}
/>
<ButtonWrapper>
{/* <IconButton>

View File

@ -1,3 +1,4 @@
import { observer } from "mobx-react-lite";
import React from "react";
import styled from "styled-components";
import useLogger from "../../../hooks/useLogger";
@ -102,4 +103,4 @@ function FileUpload({ append }: Props) {
);
}
export default FileUpload;
export default observer(FileUpload);

View File

@ -1,3 +1,4 @@
import { observer } from "mobx-react-lite";
import React, { Fragment } from "react";
import styled from "styled-components";
import { bytesToSize } from "../../../utils/Utils";
@ -160,4 +161,4 @@ function AttachmentUploadList({ state, remove }: Props) {
);
}
export default AttachmentUploadList;
export default observer(AttachmentUploadList);

View File

@ -41,13 +41,6 @@ export default class MessageQueue {
message.status = QueuedMessageStatus.SENDING;
}
@action
error(id: string, error: string) {
const message = this.messages.find((x) => x.id === id)!;
message.error = error;
message.status = QueuedMessageStatus.FAILED;
}
@computed
get(channel: Snowflake) {
return this.messages.filter((message) => message.channel === channel);

View File

@ -22,8 +22,8 @@ export default class QueuedMessage extends MessageBase {
channel: string;
files?: File[];
@observable progress = 0;
status: QueuedMessageStatus;
error?: string;
@observable status: QueuedMessageStatus;
@observable error?: string;
abortCallback?: () => void;
constructor(app: AppStore, data: QueuedMessageData) {
@ -37,8 +37,10 @@ export default class QueuedMessage extends MessageBase {
@action
updateProgress(e: ProgressEvent) {
this.progress = Math.round((e.loaded / e.total) * 100);
console.log(this.progress);
}
@action
setAbortCallback(cb: () => void) {
this.abortCallback = cb;
}
@ -48,4 +50,13 @@ export default class QueuedMessage extends MessageBase {
this.abortCallback();
}
}
@action
/**
* Mark this message as failed.
*/
fail(error: string) {
this.error = error;
this.status = QueuedMessageStatus.FAILED;
}
}