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:
parent
aa7a8327e4
commit
0be4d972d0
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user