diff --git a/src/components/status.jsx b/src/components/status.jsx index f0c90894..aa48edc7 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -20,466 +20,6 @@ import visibilityIconsMap from '../utils/visibility-icons-map'; import Avatar from './avatar'; import Icon from './icon'; -/* -Media type -=== -unknown = unsupported or unrecognized file type -image = Static image -gifv = Looping, soundless animation -video = Video clip -audio = Audio track -*/ - -function Media({ media, showOriginal, onClick }) { - const { blurhash, description, meta, previewUrl, remoteUrl, url, type } = - media; - const { original, small, focus } = meta || {}; - - const width = showOriginal ? original?.width : small?.width; - const height = showOriginal ? original?.height : small?.height; - const mediaURL = showOriginal ? url : previewUrl; - - const rgbAverageColor = blurhash ? getBlurHashAverageColor(blurhash) : null; - - const videoRef = useRef(); - - let focalBackgroundPosition; - if (focus) { - // Convert focal point to CSS background position - // Formula from jquery-focuspoint - // x = -1, y = 1 => 0% 0% - // x = 0, y = 0 => 50% 50% - // x = 1, y = -1 => 100% 100% - const x = ((focus.x + 1) / 2) * 100; - const y = ((1 - focus.y) / 2) * 100; - focalBackgroundPosition = `${x.toFixed(0)}% ${y.toFixed(0)}%`; - } - - if (type === 'image' || (type === 'unknown' && previewUrl && url)) { - // Note: type: unknown might not have width/height - return ( -
- {description} -
- ); - } else if (type === 'gifv' || type === 'video') { - // 20 seconds, treat as a gif - const isGIF = type === 'gifv' && original.duration <= 20; - return ( -
{ - if (isGIF) { - try { - videoRef.current?.pause(); - } catch (e) {} - } - onClick(e); - }} - onMouseEnter={() => { - if (isGIF) { - try { - videoRef.current?.play(); - } catch (e) {} - } - }} - onMouseLeave={() => { - if (isGIF) { - try { - videoRef.current?.pause(); - } catch (e) {} - } - }} - > - {showOriginal ? ( - - ) : isGIF ? ( -
- ); - } else if (type === 'audio') { - return ( -
-
- ); - } -} - -function Card({ card }) { - const { - blurhash, - title, - description, - html, - providerName, - authorName, - width, - height, - image, - url, - type, - embedUrl, - } = card; - - /* type - link = Link OEmbed - photo = Photo OEmbed - video = Video OEmbed - rich = iframe OEmbed. Not currently accepted, so won’t show up in practice. - */ - - const hasText = title || providerName || authorName; - - if (hasText && image) { - const domain = new URL(url).hostname.replace(/^www\./, ''); - return ( - - { - try { - e.target.style.display = 'none'; - } catch (e) {} - }} - /> -
-

{domain}

-

-

{description || providerName || authorName}

-
-
- ); - } else if (type === 'photo') { - return ( - - {title - - ); - } else if (type === 'video') { - return ( -
- ); - } -} - -function Poll({ poll, readOnly }) { - const [pollSnapshot, setPollSnapshot] = useState(poll); - const [uiState, setUIState] = useState('default'); - - useEffect(() => { - setPollSnapshot(poll); - }, [poll]); - - const { - expired, - expiresAt, - id, - multiple, - options, - ownVotes, - voted, - votersCount, - votesCount, - } = pollSnapshot; - - const expiresAtDate = !!expiresAt && new Date(expiresAt); - - return ( -
- {voted || expired ? ( - options.map((option, i) => { - const { title, votesCount: optionVotesCount } = option; - const pollVotesCount = votersCount || votesCount; - const percentage = - Math.round((optionVotesCount / pollVotesCount) * 100) || 0; - // check if current poll choice is the leading one - const isLeading = - optionVotesCount > 0 && - optionVotesCount === Math.max(...options.map((o) => o.votesCount)); - return ( -
-
- {title} - {voted && ownVotes.includes(i) && ( - <> - {' '} - - - )} -
-
- {percentage}% -
-
- ); - }) - ) : ( -
{ - e.preventDefault(); - const form = e.target; - const formData = new FormData(form); - const votes = []; - formData.forEach((value, key) => { - if (key === 'poll') { - votes.push(value); - } - }); - console.log(votes); - setUIState('loading'); - const pollResponse = await masto.poll.vote(id, { - choices: votes, - }); - console.log(pollResponse); - setPollSnapshot(pollResponse); - setUIState('default'); - }} - style={{ - pointerEvents: uiState === 'loading' || readOnly ? 'none' : 'auto', - opacity: uiState === 'loading' ? 0.5 : 1, - }} - > - {options.map((option, i) => { - const { title } = option; - return ( -
- -
- ); - })} - {!readOnly && ( - - )} -
- )} - {!readOnly && ( -

- {shortenNumber(votersCount)}{' '} - {votersCount === 1 ? 'voter' : 'voters'} - {votersCount !== votesCount && ( - <> - {' '} - • - {shortenNumber(votesCount)} - {' '} - vote - {votesCount === 1 ? '' : 's'} - - )}{' '} - • {expired ? 'Ended' : 'Ending'}{' '} - {!!expiresAtDate && ( - - )} -

- )} -
- ); -} - -function EditedAtModal({ statusID, onClose = () => {} }) { - const [uiState, setUIState] = useState('default'); - const [editHistory, setEditHistory] = useState([]); - - useEffect(() => { - setUIState('loading'); - (async () => { - try { - const editHistory = await masto.statuses.fetchHistory(statusID); - console.log(editHistory); - setEditHistory(editHistory); - setUIState('default'); - } catch (e) { - console.error(e); - setUIState('error'); - } - })(); - }, []); - - const currentYear = new Date().getFullYear(); - - return ( -
- -

Edit History

- {uiState === 'error' &&

Failed to load history

} - {uiState === 'loading' && ( -

- Loading… -

- )} - {editHistory.length > 0 && ( -
    - {editHistory.map((status) => { - const { createdAt } = status; - const createdAtDate = new Date(createdAt); - return ( -
  1. -

    - -

    - -
  2. - ); - })} -
- )} -
- ); -} - function fetchAccount(id) { return masto.accounts.fetch(id); } @@ -1150,6 +690,466 @@ function Status({ ); } +/* +Media type +=== +unknown = unsupported or unrecognized file type +image = Static image +gifv = Looping, soundless animation +video = Video clip +audio = Audio track +*/ + +function Media({ media, showOriginal, onClick }) { + const { blurhash, description, meta, previewUrl, remoteUrl, url, type } = + media; + const { original, small, focus } = meta || {}; + + const width = showOriginal ? original?.width : small?.width; + const height = showOriginal ? original?.height : small?.height; + const mediaURL = showOriginal ? url : previewUrl; + + const rgbAverageColor = blurhash ? getBlurHashAverageColor(blurhash) : null; + + const videoRef = useRef(); + + let focalBackgroundPosition; + if (focus) { + // Convert focal point to CSS background position + // Formula from jquery-focuspoint + // x = -1, y = 1 => 0% 0% + // x = 0, y = 0 => 50% 50% + // x = 1, y = -1 => 100% 100% + const x = ((focus.x + 1) / 2) * 100; + const y = ((1 - focus.y) / 2) * 100; + focalBackgroundPosition = `${x.toFixed(0)}% ${y.toFixed(0)}%`; + } + + if (type === 'image' || (type === 'unknown' && previewUrl && url)) { + // Note: type: unknown might not have width/height + return ( +
+ {description} +
+ ); + } else if (type === 'gifv' || type === 'video') { + // 20 seconds, treat as a gif + const isGIF = type === 'gifv' && original.duration <= 20; + return ( +
{ + if (isGIF) { + try { + videoRef.current?.pause(); + } catch (e) {} + } + onClick(e); + }} + onMouseEnter={() => { + if (isGIF) { + try { + videoRef.current?.play(); + } catch (e) {} + } + }} + onMouseLeave={() => { + if (isGIF) { + try { + videoRef.current?.pause(); + } catch (e) {} + } + }} + > + {showOriginal ? ( + + ) : isGIF ? ( +
+ ); + } else if (type === 'audio') { + return ( +
+
+ ); + } +} + +function Card({ card }) { + const { + blurhash, + title, + description, + html, + providerName, + authorName, + width, + height, + image, + url, + type, + embedUrl, + } = card; + + /* type + link = Link OEmbed + photo = Photo OEmbed + video = Video OEmbed + rich = iframe OEmbed. Not currently accepted, so won’t show up in practice. + */ + + const hasText = title || providerName || authorName; + + if (hasText && image) { + const domain = new URL(url).hostname.replace(/^www\./, ''); + return ( + + { + try { + e.target.style.display = 'none'; + } catch (e) {} + }} + /> +
+

{domain}

+

+

{description || providerName || authorName}

+
+
+ ); + } else if (type === 'photo') { + return ( + + {title + + ); + } else if (type === 'video') { + return ( +
+ ); + } +} + +function Poll({ poll, readOnly }) { + const [pollSnapshot, setPollSnapshot] = useState(poll); + const [uiState, setUIState] = useState('default'); + + useEffect(() => { + setPollSnapshot(poll); + }, [poll]); + + const { + expired, + expiresAt, + id, + multiple, + options, + ownVotes, + voted, + votersCount, + votesCount, + } = pollSnapshot; + + const expiresAtDate = !!expiresAt && new Date(expiresAt); + + return ( +
+ {voted || expired ? ( + options.map((option, i) => { + const { title, votesCount: optionVotesCount } = option; + const pollVotesCount = votersCount || votesCount; + const percentage = + Math.round((optionVotesCount / pollVotesCount) * 100) || 0; + // check if current poll choice is the leading one + const isLeading = + optionVotesCount > 0 && + optionVotesCount === Math.max(...options.map((o) => o.votesCount)); + return ( +
+
+ {title} + {voted && ownVotes.includes(i) && ( + <> + {' '} + + + )} +
+
+ {percentage}% +
+
+ ); + }) + ) : ( +
{ + e.preventDefault(); + const form = e.target; + const formData = new FormData(form); + const votes = []; + formData.forEach((value, key) => { + if (key === 'poll') { + votes.push(value); + } + }); + console.log(votes); + setUIState('loading'); + const pollResponse = await masto.poll.vote(id, { + choices: votes, + }); + console.log(pollResponse); + setPollSnapshot(pollResponse); + setUIState('default'); + }} + style={{ + pointerEvents: uiState === 'loading' || readOnly ? 'none' : 'auto', + opacity: uiState === 'loading' ? 0.5 : 1, + }} + > + {options.map((option, i) => { + const { title } = option; + return ( +
+ +
+ ); + })} + {!readOnly && ( + + )} +
+ )} + {!readOnly && ( +

+ {shortenNumber(votersCount)}{' '} + {votersCount === 1 ? 'voter' : 'voters'} + {votersCount !== votesCount && ( + <> + {' '} + • + {shortenNumber(votesCount)} + {' '} + vote + {votesCount === 1 ? '' : 's'} + + )}{' '} + • {expired ? 'Ended' : 'Ending'}{' '} + {!!expiresAtDate && ( + + )} +

+ )} +
+ ); +} + +function EditedAtModal({ statusID, onClose = () => {} }) { + const [uiState, setUIState] = useState('default'); + const [editHistory, setEditHistory] = useState([]); + + useEffect(() => { + setUIState('loading'); + (async () => { + try { + const editHistory = await masto.statuses.fetchHistory(statusID); + console.log(editHistory); + setEditHistory(editHistory); + setUIState('default'); + } catch (e) { + console.error(e); + setUIState('error'); + } + })(); + }, []); + + const currentYear = new Date().getFullYear(); + + return ( +
+ +

Edit History

+ {uiState === 'error' &&

Failed to load history

} + {uiState === 'loading' && ( +

+ Loading… +

+ )} + {editHistory.length > 0 && ( +
    + {editHistory.map((status) => { + const { createdAt } = status; + const createdAtDate = new Date(createdAt); + return ( +
  1. +

    + +

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