1
0
mirror of https://github.com/spacebarchat/client.git synced 2024-11-22 10:22:30 +01:00

attachment upload progress component, fix queued messages, etc

This commit is contained in:
Puyodead1 2023-09-02 00:24:08 -04:00
parent cdc0405fba
commit 4b7c8c714e
No known key found for this signature in database
GPG Key ID: BA5F91AAEF68E5CE
9 changed files with 151 additions and 62 deletions

View File

@ -2,7 +2,7 @@ import styled from "styled-components";
interface Props {
// variant?: "primary" | "secondary" | "danger" | "success" | "warning";
outlined?: boolean;
variant?: "filled" | "outlined" | "blank";
color?: string;
}
@ -22,7 +22,7 @@ export default styled.button<Props>`
background-color: transparent;
color: ${(props) => {
if (props.outlined) return "transparent";
if (props.variant === "outlined") return "transparent";
// switch (props.variant) {
// case "primary":
// return "var(--primary)";
@ -41,7 +41,7 @@ export default styled.button<Props>`
}};
border: ${(props) => {
if (!props.outlined) return "none";
if (props.variant !== "outlined") return "none";
// switch (props.variant) {
// case "primary":
// return "1px solid var(--primary)";
@ -60,7 +60,7 @@ export default styled.button<Props>`
}};
&:hover {
background-color: var(--background-primary-alt);
background-color: ${(props) => (props.variant === "blank" ? "transparent" : "var(--background-secondary)")}
cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
}
`;

View File

@ -0,0 +1,59 @@
import { observer } from "mobx-react-lite";
import styled from "styled-components";
import { QueuedMessage } from "../../stores/MessageQueue";
import Icon from "../Icon";
import IconButton from "../IconButton";
const Container = styled.div`
max-width: 25%;
width: 100%;
border: 1px solid transparent;
padding: 10px;
border-radius: 4px;
background-color: var(--background-secondary);
border-color: var(--background-secondary-alt);
flex-direction: row;
align-items: center;
display: flex;
`;
const Wrapper = styled.div`
flex: 1;
white-space: nowrap;
overflow: hidden;
`;
const Progress = styled.progress`
height: 6px;
width: 100%;
`;
const CustomIcon = styled(Icon)`
color: var(--text-secondary);
&:hover {
color: var(--text);
}
`;
interface Props {
message: QueuedMessage;
}
function AttachmentUploadProgress({ message }: Props) {
console.log(message.progress);
return (
<Container>
<Wrapper>
<div>{message.files!.length === 1 ? message.files![0].name : `${message.files!.length} files`}</div>
<Progress value={message.progress} max={100} />
</Wrapper>
<IconButton variant="blank">
<CustomIcon icon="mdiClose" size="24px" />
</IconButton>
</Container>
);
}
export default observer(AttachmentUploadProgress);

View File

@ -1,5 +1,6 @@
import { useModals } from "@mattjennings/react-modal-stack";
import { APIAttachment, MessageType } from "@spacebarchat/spacebar-api-types/v9";
import { observer } from "mobx-react-lite";
import React from "react";
import Moment from "react-moment";
import styled from "styled-components";
@ -13,6 +14,7 @@ import Avatar from "../Avatar";
import { Link } from "../Link";
import AttachmentPreviewModal from "../modals/AttachmentPreviewModal";
import { IContextMenuItem } from "./../ContextMenuItem";
import AttachmentUploadProgress from "./AttachmentUploadProgress";
type MessageLike = MessageObject | QueuedMessage;
@ -181,6 +183,8 @@ function Message({ message, isHeader, isSending, isFailed }: Props) {
);
}, []);
if (message instanceof QueuedMessage) console.log(`progress at msg`, message.progress);
// construct the context menu options
// React.useEffect(() => {
// // if the message is queued, we don't need a context menu
@ -260,10 +264,16 @@ function Message({ message, isHeader, isSending, isFailed }: Props) {
{message.content ?? "No Content"}
</div>
)}
{"files" in message && message.files?.length !== 0 && (
<div>
<AttachmentUploadProgress message={message} />
</div>
)}
</MessageContentContainer>
</Container>
</MessageListItem>
);
}
export default Message;
export default observer(Message);

View File

@ -1,5 +1,7 @@
import { observer } from "mobx-react-lite";
import React from "react";
import styled from "styled-components";
import { QueuedMessageStatus } from "../../stores/MessageQueue";
import { QueuedMessage, QueuedMessageStatus } from "../../stores/MessageQueue";
import { default as MessageObject } from "../../stores/objects/Message";
import Message from "./Message";
@ -8,28 +10,27 @@ const Container = styled.div`
`;
interface Props {
messages: MessageObject[];
messages: (MessageObject | QueuedMessage)[];
}
/**
* Component that handles rendering a group of messages from the same author
*/
function MessageGroup({ messages }: Props) {
return (
<Container>
{messages.map((message, index) => {
return (
<Message
key={message.id}
message={message}
isHeader={index === 0}
isSending={"status" in message && message.status === QueuedMessageStatus.SENDING}
isFailed={"status" in message && message.status === QueuedMessageStatus.FAILED}
/>
);
})}
</Container>
);
const renderMessage = React.useCallback((message: MessageObject | QueuedMessage, index: number) => {
if (message instanceof QueuedMessage) console.log(`progress at msg group`, message.progress);
return (
<Message
key={message.id}
message={message}
isHeader={index === 0}
isSending={"status" in message && message.status === QueuedMessageStatus.SENDING}
isFailed={"status" in message && message.status === QueuedMessageStatus.FAILED}
/>
);
}, []);
return <Container>{messages.map((message, index) => renderMessage(message, index))}</Container>;
}
export default MessageGroup;
export default observer(MessageGroup);

View File

@ -111,11 +111,6 @@ function MessageInput(props: Props) {
);
}, []);
const uploadProgressCallback = React.useCallback((e: ProgressEvent) => {
const progress = Math.round((e.loaded * 100) / e.total);
console.log(`uploadProgressCallback`, progress);
}, []);
const onKeyDown = React.useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === "Enter" && !e.shiftKey) {
@ -133,12 +128,12 @@ function MessageInput(props: Props) {
if (!canSend && !shouldFail) return;
const nonce = Snowflake.generate();
app.queue.add({
const msg = app.queue.add({
id: nonce,
author: app.account! as unknown as User,
content,
channel: props.channel.id,
// attachments,
files: attachments,
});
if (shouldSend) {
@ -153,7 +148,7 @@ function MessageInput(props: Props) {
} else {
body = { content, nonce };
}
props.channel.sendMessage(body, uploadProgressCallback).catch((error) => {
props.channel.sendMessage(body, msg.progressCallback).catch((error) => {
app.queue.error(nonce, error as string);
});
}

View File

@ -5,6 +5,7 @@ import { PulseLoader } from "react-spinners";
import styled from "styled-components";
import useLogger from "../../hooks/useLogger";
import { useAppStore } from "../../stores/AppStore";
import { QueuedMessage } from "../../stores/MessageQueue";
import Channel from "../../stores/objects/Channel";
import Guild from "../../stores/objects/Guild";
import Message from "../../stores/objects/Message";
@ -34,7 +35,6 @@ interface Props {
function MessageList({ guild, channel }: Props) {
const app = useAppStore();
const logger = useLogger("MessageList.tsx");
// const messages = [...(channel.messages.messages ?? []), ...(channel ? app.queue.get(channel.id) ?? [] : [])];
const [hasMore, setHasMore] = React.useState(true);
const [canView, setCanView] = React.useState(false);
@ -55,7 +55,7 @@ function MessageList({ guild, channel }: Props) {
}
}, [guild, channel, canView]);
const renderMessageGroup = React.useCallback((group: Message[], index: number) => {
const renderMessageGroup = React.useCallback((group: (Message | QueuedMessage)[], index: number) => {
return <MessageGroup key={index} messages={group} />;
}, []);
@ -66,6 +66,7 @@ function MessageList({ guild, channel }: Props) {
// get last group
const lastGroup = channel.messages.grouped[channel.messages.grouped.length - 1];
if ("status" in lastGroup) return;
// get first message in the group to use as before
const before = lastGroup[0].id;
logger.debug(`Fetching 50 messages before ${before} for channel ${channel.id}`);

View File

@ -1,4 +1,4 @@
import type { APIAttachment, APIMessage } from "@spacebarchat/spacebar-api-types/v9";
import type { APIMessage } from "@spacebarchat/spacebar-api-types/v9";
import { MessageType } from "@spacebarchat/spacebar-api-types/v9";
import { action, computed, makeAutoObservable, observable } from "mobx";
@ -16,19 +16,47 @@ export type QueuedMessageData = {
channel: string;
author: User;
content: string;
attachments?: File[];
files?: File[];
};
export interface QueuedMessage {
// export interface QueuedMessage extends QueuedMessageData {
// status: QueuedMessageStatus;
// error?: string;
// timestamp: Date;
// type: MessageType;
// }
export class QueuedMessage implements QueuedMessageData {
id: string;
status: QueuedMessageStatus;
error?: string;
channel: string;
author: User;
content: string;
files?: File[];
@observable progress: number;
status: QueuedMessageStatus;
error?: string;
timestamp: Date;
type: MessageType;
attachments: APIAttachment[];
constructor(data: QueuedMessageData) {
this.id = data.id;
this.channel = data.channel;
this.author = data.author;
this.content = data.content;
this.files = data.files;
this.progress = 0;
this.status = QueuedMessageStatus.SENDING;
this.timestamp = new Date();
this.type = MessageType.Default;
makeAutoObservable(this);
}
@action
progressCallback(e: ProgressEvent) {
this.progress = Math.round((e.loaded / e.total) * 100);
console.log(this.progress);
}
}
export default class MessageQueue {
@ -42,23 +70,15 @@ export default class MessageQueue {
@action
add(data: QueuedMessageData) {
this.messages.push({
...data,
timestamp: new Date(),
status: QueuedMessageStatus.SENDING,
type: MessageType.Default,
attachments:
data.attachments?.map((x) => ({
id: Snowflake.generate(),
filename: x.name,
size: x.size,
url: URL.createObjectURL(x),
proxy_url: URL.createObjectURL(x),
height: 0,
width: 0,
content_type: x.type,
})) ?? [],
});
// this.messages.push({
// ...data,
// timestamp: new Date(),
// status: QueuedMessageStatus.SENDING,
// type: MessageType.Default,
// });
const msg = new QueuedMessage(data);
this.messages.push(msg);
return msg;
}
@action

View File

@ -4,16 +4,19 @@ import { action, computed, makeObservable, observable } from "mobx";
import useLogger from "../hooks/useLogger";
import Logger from "../utils/Logger";
import AppStore from "./AppStore";
import { QueuedMessage } from "./MessageQueue";
import Message from "./objects/Message";
export default class MessageStore {
private readonly app: AppStore;
private readonly channelId;
private readonly logger: Logger;
@observable private readonly messagesArr: IObservableArray<Message>;
constructor(app: AppStore) {
constructor(app: AppStore, channelId: string) {
this.app = app;
this.channelId = channelId;
this.logger = useLogger("MessageStore.ts");
this.messagesArr = observable.array([]);
@ -37,8 +40,8 @@ export default class MessageStore {
@computed
get grouped() {
// sort messages from the same author and within 7 minutes of each other into groups sorted by time, oldest messages at the end of the list and newest at the start, each groups messages should be sorted from oldest to newest
const sortedGroups = this.messagesArr
const messages = [...this.messagesArr, ...this.app.queue.get(this.channelId)];
const sortedGroups = messages
.slice()
.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime())
.reduce((groups, message) => {
@ -56,7 +59,7 @@ export default class MessageStore {
groups.push([message]);
}
return groups;
}, [] as Message[][])
}, [] as (Message | QueuedMessage)[][])
.map((group) => group.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()))
.reverse();

View File

@ -58,8 +58,6 @@ export default class Channel {
constructor(app: AppStore, channel: APIChannel) {
this.app = app;
this.messages = new MessageStore(app);
this.id = channel.id;
this.createdAt = new Date(channel.created_at);
this.name = channel.name;
@ -88,6 +86,8 @@ export default class Channel {
this.flags = channel.flags;
this.defaultThreadRateLimitPerUser = channel.default_thread_rate_limit_per_user;
this.messages = new MessageStore(app, this.id);
if (channel.messages) {
this.messages.addAll(channel.messages);
}