feat: add corresponding UI for interactions reactions

This commit is contained in:
Paul Makles 2022-07-30 12:23:56 +02:00
parent 084c90613f
commit dedc1e0666

View file

@ -2,6 +2,8 @@ import { observer } from "mobx-react-lite";
import { Message } from "revolt.js"; import { Message } from "revolt.js";
import styled, { css } from "styled-components"; import styled, { css } from "styled-components";
import { useCallback } from "preact/hooks";
import { useClient } from "../../../../controllers/client/ClientController"; import { useClient } from "../../../../controllers/client/ClientController";
import { RenderEmoji } from "../../../markdown/plugins/emoji"; import { RenderEmoji } from "../../../markdown/plugins/emoji";
@ -9,18 +11,35 @@ interface Props {
message: Message; message: Message;
} }
/**
* Reaction list element
*/
const List = styled.div` const List = styled.div`
gap: 0.4em; gap: 0.4em;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
margin-top: 0.2em; margin-top: 0.2em;
align-items: center;
`; `;
/**
* List divider
*/
const Divider = styled.div`
width: 1px;
height: 14px;
background: var(--tertiary-foreground);
`;
/**
* Reaction styling
*/
const Reaction = styled.div<{ active: boolean }>` const Reaction = styled.div<{ active: boolean }>`
padding: 0.4em; padding: 0.4em;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
vertical-align: middle; vertical-align: middle;
border: 1px solid transparent;
color: var(--secondary-foreground); color: var(--secondary-foreground);
border-radius: var(--border-radius); border-radius: var(--border-radius);
background: var(--secondary-background); background: var(--secondary-background);
@ -42,30 +61,73 @@ const Reaction = styled.div<{ active: boolean }>`
${(props) => ${(props) =>
props.active && props.active &&
css` css`
border: 1px solid var(--accent); border-color: var(--accent);
`} `}
`; `;
/**
* Render reactions on a message
*/
export const Reactions = observer(({ message }: Props) => { export const Reactions = observer(({ message }: Props) => {
const client = useClient(); const client = useClient();
if (message.reactions.size === 0) return null;
return ( /**
<List> * Render individual reaction entries
{Array.from(message.reactions, ([key, user_ids]) => { */
const active = user_ids.has(client.user!._id); const Entry = useCallback(
observer(({ id, user_ids }: { id: string; user_ids?: Set<string> }) => {
const active = user_ids?.has(client.user!._id) || false;
return ( return (
<Reaction <Reaction
key={key}
active={active} active={active}
onClick={() => onClick={() =>
active ? message.unreact(key) : message.react(key) active ? message.unreact(id) : message.react(id)
}> }>
<RenderEmoji match={key} /> {user_ids.size} <RenderEmoji match={id} /> {user_ids?.size || 0}
</Reaction> </Reaction>
); );
})} }),
[],
);
/**
* Determine two lists of 'required' and 'optional' reactions
*/
const { required, optional } = (() => {
const required = new Set<string>();
const optional = new Set<string>();
if (message.interactions?.reactions) {
for (const reaction of message.interactions.reactions) {
required.add(reaction);
}
}
for (const key of message.reactions.keys()) {
if (!required.has(key)) {
optional.add(key);
}
}
return {
required,
optional,
};
})();
// Don't render list if nothing is going to show anyways
if (required.size === 0 && optional.size === 0) return null;
return (
<List>
{Array.from(required, (id) => (
<Entry key={id} id={id} user_ids={message.reactions.get(id)} />
))}
{required.size !== 0 && optional.size !== 0 && <Divider />}
{Array.from(optional, (id) => (
<Entry key={id} id={id} user_ids={message.reactions.get(id)} />
))}
</List> </List>
); );
}); });