Skip to content

Commit

Permalink
Merge pull request #164 from spacebarchat/fix/message-rendering
Browse files Browse the repository at this point in the history
Fix message rendering
  • Loading branch information
Puyodead1 authored Sep 12, 2023
2 parents 8b48d3d + de98d3d commit c06c72a
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 81 deletions.
77 changes: 77 additions & 0 deletions src/components/Video.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { APIAttachment } from "@spacebarchat/spacebar-api-types/v9";
import React from "react";
import { PuffLoader } from "react-spinners";
import styled from "styled-components";
import { calculateImageRatio, calculateScaledDimensions } from "../utils/Message";

const Container = styled.div`
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
min-width: 300px;
background-color: var(--background-primary);
border-radius: 10px;
`;

interface Props {
attachment: APIAttachment;
}

function Video({ attachment }: Props) {
const ref = React.useRef<HTMLVideoElement>(null);
const [isLoading, setLoading] = React.useState(true);
const [dimensions, setDimensions] = React.useState({ width: 0, height: 0 });
const [isErrored, setErrored] = React.useState(false);

const url = attachment.proxy_url && attachment.proxy_url.length > 0 ? attachment.proxy_url : attachment.url;

const onLoadedMetadata = (e: React.SyntheticEvent<HTMLVideoElement, Event>) => {
const video = e.target as HTMLVideoElement;
const width = video.videoWidth;
const height = video.videoHeight;
const ratio = calculateImageRatio(width, height, 300, 300);
const scaledDimensions = calculateScaledDimensions(width, height, ratio, 300, 300);
setDimensions({ width: scaledDimensions.scaledWidth, height: scaledDimensions.scaledHeight });
setLoading(false);
};

const onError = () => {
setErrored(true);
};

// TODO: poster
// TODO: the server doesn't return height and width yet for videos
return (
<>
{isLoading && !isErrored && (
<Container>
<PuffLoader size={"42px"} color="var(--primary)" />
</Container>
)}
{isErrored && (
<Container>
<p>Failed to load video</p>
</Container>
)}

{!isErrored && (
<video
style={isLoading ? { display: "none" } : {}}
playsInline
controls
preload="metadata"
width={dimensions.width}
height={dimensions.height}
ref={ref}
onLoadedMetadata={onLoadedMetadata}
onError={onError}
>
<source src={url} type={attachment.content_type} />
</video>
)}
</>
);
}

export default Video;
3 changes: 2 additions & 1 deletion src/components/messaging/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const Container = styled.div<{ isHeader?: boolean }>`
flex-direction: row;
position: relative;
padding: 2px 12px;
margin-top: ${(props) => (props.isHeader ? "20px" : undefined)};
&:hover {
background-color: var(--background-primary-highlight);
Expand Down Expand Up @@ -138,6 +139,7 @@ function Message({ message, isHeader, isSending, isFailed }: Props) {
// }, [isSending, isFailed]);

// handles creating the message content based on the message type
// TODO: probably move this to a separate component
const renderMessageContent = React.useCallback(() => {
switch (message.type) {
case MessageType.Default:
Expand All @@ -158,7 +160,6 @@ function Message({ message, isHeader, isSending, isFailed }: Props) {
{"embeds" in message
? message.embeds.map((embed, index) => (
<Fragment key={index}>
{" "}
<MessageEmbed key={index} embed={embed} contextMenuItems={contextMenuItems} />;
</Fragment>
))
Expand Down
11 changes: 2 additions & 9 deletions src/components/messaging/MessageAttachment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ContextMenuContext } from "../../contexts/ContextMenuContext";
import useLogger from "../../hooks/useLogger";
import { calculateImageRatio, calculateScaledDimensions } from "../../utils/Message";
import { IContextMenuItem } from "../ContextMenuItem";
import Video from "../Video";
import AttachmentPreviewModal from "../modals/AttachmentPreviewModal";

const Attachment = styled.div<{ withPointer?: boolean }>`
Expand Down Expand Up @@ -44,15 +45,7 @@ export default function MessageAttachment({ attachment, contextMenuItems, maxWid
);
finalElement = <Image src={url} alt={attachment.filename} width={scaledWidth} height={scaledHeight} />;
} else if (attachment.content_type?.startsWith("video")) {
{
/* TODO: poster thumbnail */
}
finalElement = (
<video playsInline controls preload="metadata" height={200}>
{/* TODO: the server doesn't return height and width yet for videos */}
<source src={url} type={attachment.content_type} />
</video>
);
finalElement = <Video attachment={attachment} />;
} else {
logger.warn(`Unknown attachment type: ${attachment.content_type}`);
}
Expand Down
30 changes: 13 additions & 17 deletions src/components/messaging/MessageGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,31 @@
import { observer } from "mobx-react-lite";
import { Fragment } from "react";
import styled from "styled-components";
import { default as MessageObject } from "../../stores/objects/Message";
import QueuedMessage, { QueuedMessageStatus } from "../../stores/objects/QueuedMessage";
import { MessageGroup } from "../../stores/MessageStore";
import { QueuedMessageStatus } from "../../stores/objects/QueuedMessage";
import Message from "./Message";

const Container = styled.div`
margin-top: 20px;
`;

interface Props {
messages: (MessageObject | QueuedMessage)[];
group: MessageGroup;
}

/**
* Component that handles rendering a group of messages from the same author
*/
function MessageGroup({ messages }: Props) {
function MessageGroup({ group }: Props) {
const { messages } = group;
return (
<Container>
{messages.map((message, index) => (
<Fragment key={message.id}>
<>
{messages.map((message, index) => {
return (
<Message
key={message.id}
message={message}
isHeader={index === 0}
isHeader={index === messages.length - 1}
isSending={"status" in message && message.status === QueuedMessageStatus.SENDING}
isFailed={"status" in message && message.status === QueuedMessageStatus.FAILED}
/>
</Fragment>
))}
</Container>
);
})}
</>
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/messaging/MessageInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ function MessageInput({ channel }: Props) {
const error = e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error";
msg.fail(error);
}
} else {
} else if (shouldFail) {
msg.fail("Message queue experiment");
}
}, [content, attachments, channel, canSendMessage]);
Expand Down
29 changes: 15 additions & 14 deletions src/components/messaging/MessageList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { observer } from "mobx-react-lite";
import React, { Fragment } from "react";
import React from "react";
import InfiniteScroll from "react-infinite-scroll-component";
import PulseLoader from "react-spinners/PulseLoader";
import styled from "styled-components";
import useLogger from "../../hooks/useLogger";
import { useAppStore } from "../../stores/AppStore";
import { MessageGroup as MessageGroupType } from "../../stores/MessageStore";
import Channel from "../../stores/objects/Channel";
import Guild from "../../stores/objects/Guild";
import { Permissions } from "../../utils/Permissions";
Expand Down Expand Up @@ -35,6 +36,7 @@ function MessageList({ guild, channel }: Props) {
const logger = useLogger("MessageList.tsx");
const [hasMore, setHasMore] = React.useState(true);
const [canView, setCanView] = React.useState(false);
const messageGroups = channel.messages.groups;

// handles the permission check
React.useEffect(() => {
Expand All @@ -53,30 +55,33 @@ function MessageList({ guild, channel }: Props) {
}
}, [guild, channel, canView]);

const fetchMore = async () => {
const fetchMore = React.useCallback(() => {
if (!channel.messages.count) {
return;
}

// get last group
const lastGroup = channel.messages.grouped[channel.messages.grouped.length - 1];
const lastGroup = messageGroups[messageGroups.length - 1];
// ignore queued messages
if ("status" in lastGroup) return;
if ("status" in lastGroup.messages[0]) return;
// get first message in the group to use as before
const before = lastGroup[0].id;
const before = lastGroup.messages[0].id;
logger.debug(`Fetching 50 messages before ${before} for channel ${channel.id}`);

channel.getMessages(app, false, 50, before).then((r) => {
if (r !== 50) setHasMore(false);
else setHasMore(true);
});
};
}, [channel, setHasMore]);

const renderGroup = React.useCallback(
(group: MessageGroupType) => <MessageGroup key={group.messages[0].id} group={group} />,
[],
);

return (
<Container id="scrollable-div">
{canView ? (
<InfiniteScroll
dataLength={channel.messages.grouped.length}
dataLength={messageGroups.length}
next={fetchMore}
style={{
display: "flex",
Expand Down Expand Up @@ -107,11 +112,7 @@ function MessageList({ guild, channel }: Props) {
</EndMessageContainer>
}
>
{channel.messages.grouped.map((group, index) => (
<Fragment key={index}>
<MessageGroup messages={group} />
</Fragment>
))}
{messageGroups.map((group) => renderGroup(group))}
</InfiniteScroll>
) : (
<div
Expand Down
2 changes: 1 addition & 1 deletion src/stores/MessageQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { QueuedMessageData } from "./objects/QueuedMessage";
import QueuedMessage, { QueuedMessageStatus } from "./objects/QueuedMessage";

export default class MessageQueue {
@observable private readonly messages: IObservableArray<QueuedMessage>;
@observable readonly messages: IObservableArray<QueuedMessage>;

constructor(private readonly app: AppStore) {
this.messages = observable.array([]);
Expand Down
Loading

0 comments on commit c06c72a

Please sign in to comment.