From 2b26635e72ddaa941da01de484d0066c256d0560 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Thu, 6 Apr 2023 22:51:48 +0800 Subject: [PATCH] New: Reactions Modal --- src/components/icon.jsx | 1 + src/components/status.css | 38 +++++++++ src/components/status.jsx | 164 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+) diff --git a/src/components/icon.jsx b/src/components/icon.jsx index 0d37deb6..196ca0f5 100644 --- a/src/components/icon.jsx +++ b/src/components/icon.jsx @@ -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'); diff --git a/src/components/status.css b/src/components/status.css index 3f583226..c1509ae9 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -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); +} diff --git a/src/components/status.jsx b/src/components/status.jsx index e3f5112c..9dc0e570 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -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({ )} {(!isSizeLarge || !!editedAt) && } + {isSizeLarge && ( + setShowReactions(true)}> + + + Boosted/Favourited by + + + )} {!isSizeLarge && sameInstance && ( <> @@ -1123,6 +1134,18 @@ function Status({ /> )} + {showReactions && ( + { + if (e.target === e.currentTarget) { + setShowReactions(false); + } + }} + > + + + )} ); } @@ -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 ( +
+
+

Boosted/Favourited by…

+
+
+ {accounts.length > 0 ? ( + <> +
    + {accounts.map((account) => { + const { _types } = account; + return ( +
  • +
    + {_types.map((type) => ( + + ))} +
    + +
  • + ); + })} +
+ {uiState === 'default' && + (showMore ? ( + { + if (inView) { + fetchAccounts(); + } + }} + > + + + ) : ( +

The end.

+ ))} + + ) : uiState === 'loading' ? ( +

+ +

+ ) : uiState === 'error' ? ( +

Unable to load accounts

+ ) : ( +

No one yet.

+ )} +
+
+ ); +} + function StatusButton({ checked, count,