New: Reactions Modal

This commit is contained in:
Lim Chee Aun 2023-04-06 22:51:48 +08:00
parent 224a289a20
commit 2b26635e72
3 changed files with 203 additions and 0 deletions

View file

@ -76,6 +76,7 @@ const ICONS = {
emoji2: 'mingcute:emoji-2-line',
filter: 'mingcute:filter-2-line',
chart: 'mingcute:chart-line-line',
react: 'mingcute:react-line',
};
const modules = import.meta.glob('/node_modules/@iconify-icons/mingcute/*.js');

View file

@ -1222,3 +1222,41 @@ a.card:is(:hover, :focus) {
bottom: 8px;
right: 8px;
}
/* REACTIONS */
#reactions-container main ul {
list-style: none;
margin: 0;
padding: 8px 0;
display: flex;
flex-wrap: wrap;
flex-direction: row;
column-gap: 1.5em;
row-gap: 16px;
}
#reactions-container main ul li {
display: flex;
flex-grow: 1;
flex-basis: 16em;
align-items: center;
margin: 0;
padding: 0;
gap: 8px;
}
#reactions-container main ul li .account-block-acct {
font-size: 80%;
color: var(--text-insignificant-color);
display: block;
}
#reactions-container .reactions-block {
display: flex;
flex-direction: column;
align-self: center;
}
#reactions-container .reactions-block .favourite-icon {
color: var(--favourite-color);
}
#reactions-container .reactions-block .reblog-icon {
color: var(--reblog-color);
}

View file

@ -14,11 +14,13 @@ import mem from 'mem';
import pThrottle from 'p-throttle';
import { memo } from 'preact/compat';
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { InView } from 'react-intersection-observer';
import 'swiped-events';
import { useLongPress } from 'use-long-press';
import useResizeObserver from 'use-resize-observer';
import { useSnapshot } from 'valtio';
import AccountBlock from '../components/account-block';
import Loader from '../components/loader';
import Modal from '../components/modal';
import NameText from '../components/name-text';
@ -228,6 +230,7 @@ function Status({
if (!snapStates.settings.contentTranslation) enableTranslate = false;
const [showEdited, setShowEdited] = useState(false);
const [showReactions, setShowReactions] = useState(false);
const spoilerContentRef = useRef(null);
useResizeObserver({
@ -444,6 +447,14 @@ function Status({
</MenuItem>
)}
{(!isSizeLarge || !!editedAt) && <MenuDivider />}
{isSizeLarge && (
<MenuItem onClick={() => setShowReactions(true)}>
<Icon icon="react" />
<span>
Boosted/Favourited by<span class="more-insignificant"></span>
</span>
</MenuItem>
)}
{!isSizeLarge && sameInstance && (
<>
<MenuItem onClick={replyStatus}>
@ -1123,6 +1134,18 @@ function Status({
/>
</Modal>
)}
{showReactions && (
<Modal
class="light"
onClick={(e) => {
if (e.target === e.currentTarget) {
setShowReactions(false);
}
}}
>
<ReactionsModal statusID={id} instance={instance} />
</Modal>
)}
</article>
);
}
@ -1541,6 +1564,147 @@ function EditedAtModal({
);
}
const REACTIONS_LIMIT = 80;
function ReactionsModal({ statusID, instance }) {
const { masto } = api({ instance });
const [uiState, setUIState] = useState('default');
const [accounts, setAccounts] = useState([]);
const [showMore, setShowMore] = useState(false);
const reblogIterator = useRef();
const favouriteIterator = useRef();
async function fetchAccounts(firstLoad) {
setShowMore(false);
setUIState('loading');
(async () => {
try {
if (firstLoad) {
reblogIterator.current = masto.v1.statuses.listRebloggedBy(statusID, {
limit: REACTIONS_LIMIT,
});
favouriteIterator.current = masto.v1.statuses.listFavouritedBy(
statusID,
{
limit: REACTIONS_LIMIT,
},
);
}
const [{ value: reblogResults }, { value: favouriteResults }] =
await Promise.allSettled([
reblogIterator.current.next(),
favouriteIterator.current.next(),
]);
if (reblogResults.value?.length || favouriteResults.value?.length) {
if (reblogResults.value?.length) {
for (const account of reblogResults.value) {
const theAccount = accounts.find((a) => a.id === account.id);
if (!theAccount) {
accounts.push({
...account,
_types: ['reblog'],
});
} else {
theAccount._types.push('reblog');
}
}
}
if (favouriteResults.value?.length) {
for (const account of favouriteResults.value) {
const theAccount = accounts.find((a) => a.id === account.id);
if (!theAccount) {
accounts.push({
...account,
_types: ['favourite'],
});
} else {
theAccount._types.push('favourite');
}
}
}
setAccounts(accounts);
setShowMore(!reblogResults.done || !favouriteResults.done);
} else {
setShowMore(false);
}
setUIState('default');
} catch (e) {
console.error(e);
setUIState('error');
}
})();
}
useEffect(() => {
fetchAccounts(true);
}, []);
return (
<div id="reactions-container" class="sheet">
<header>
<h2>Boosted/Favourited by</h2>
</header>
<main>
{accounts.length > 0 ? (
<>
<ul class="reactions-list">
{accounts.map((account) => {
const { _types } = account;
return (
<li key={account.id + _types}>
<div class="reactions-block">
{_types.map((type) => (
<Icon
icon={
{
reblog: 'rocket',
favourite: 'heart',
}[type]
}
class={`${type}-icon`}
/>
))}
</div>
<AccountBlock account={account} instance={instance} />
</li>
);
})}
</ul>
{uiState === 'default' &&
(showMore ? (
<InView
onChange={(inView) => {
if (inView) {
fetchAccounts();
}
}}
>
<button
type="button"
class="plain block"
onClick={() => fetchAccounts()}
>
Show more&hellip;
</button>
</InView>
) : (
<p class="ui-state insignificant">The end.</p>
))}
</>
) : uiState === 'loading' ? (
<p class="ui-state">
<Loader abrupt />
</p>
) : uiState === 'error' ? (
<p class="ui-state">Unable to load accounts</p>
) : (
<p class="ui-state insignificant">No one yet.</p>
)}
</main>
</div>
);
}
function StatusButton({
checked,
count,