import { Link } from 'preact-router/match'; import { memo } from 'preact/compat'; import { useEffect, useRef, useState } from 'preact/hooks'; import { useHotkeys } from 'react-hotkeys-hook'; import { useSnapshot } from 'valtio'; import Icon from '../components/icon'; import Loader from '../components/loader'; import Status from '../components/status'; import db from '../utils/db'; import states, { saveStatus } from '../utils/states'; import { getCurrentAccountNS } from '../utils/store-utils'; import useDebouncedCallback from '../utils/useDebouncedCallback'; import useScroll from '../utils/useScroll'; const LIMIT = 20; function Home({ hidden }) { const snapStates = useSnapshot(states); const [uiState, setUIState] = useState('default'); const [showMore, setShowMore] = useState(false); console.debug('RENDER Home'); const homeIterator = useRef( masto.v1.timelines.listHome({ limit: LIMIT, }), ); async function fetchStatuses(firstLoad) { if (firstLoad) { // Reset iterator homeIterator.current = masto.v1.timelines.listHome({ limit: LIMIT, }); states.homeNew = []; } const allStatuses = await homeIterator.current.next(); if (allStatuses.value <= 0) { return { done: true }; } const homeValues = allStatuses.value.map((status) => { saveStatus(status); return { id: status.id, reblog: status.reblog?.id, reply: !!status.inReplyToAccountId, }; }); // BOOSTS CAROUSEL if (snapStates.settings.boostsCarousel) { let specialHome = []; let boostStash = []; let serialBoosts = 0; for (let i = 0; i < homeValues.length; i++) { const status = homeValues[i]; if (status.reblog) { boostStash.push(status); serialBoosts++; } else { specialHome.push(status); if (serialBoosts < 3) { serialBoosts = 0; } } } // if boostStash is more than quarter of homeValues // or if there are 3 or more boosts in a row if (boostStash.length > homeValues.length / 4 || serialBoosts >= 3) { // if boostStash is more than 3 quarter of homeValues const boostStashID = boostStash.map((status) => status.id); if (boostStash.length > (homeValues.length * 3) / 4) { // insert boost array at the end of specialHome list specialHome = [ ...specialHome, { id: boostStashID, boosts: boostStash }, ]; } else { // insert boosts array in the middle of specialHome list const half = Math.floor(specialHome.length / 2); specialHome = [ ...specialHome.slice(0, half), { id: boostStashID, boosts: boostStash, }, ...specialHome.slice(half), ]; } } else { // Untouched, this is fine specialHome = homeValues; } console.log({ specialHome, }); if (firstLoad) { states.home = specialHome; } else { states.home.push(...specialHome); } } else { if (firstLoad) { states.home = homeValues; } else { states.home.push(...homeValues); } } states.homeLastFetchTime = Date.now(); return { done: false, }; } const loadingStatuses = useRef(false); const loadStatuses = useDebouncedCallback((firstLoad) => { if (loadingStatuses.current) return; loadingStatuses.current = true; setUIState('loading'); (async () => { try { const { done } = await fetchStatuses(firstLoad); setShowMore(!done); setUIState('default'); } catch (e) { console.warn(e); setUIState('error'); } finally { loadingStatuses.current = false; } })(); }, 1000); useEffect(() => { loadStatuses(true); }, []); const scrollableRef = useRef(); useHotkeys('j, shift+j', (_, handler) => { // focus on next status after active status // Traverses .timeline li .status-link, focus on .status-link const activeStatus = document.activeElement.closest( '.status-link, .status-boost-link', ); const activeStatusRect = activeStatus?.getBoundingClientRect(); const allStatusLinks = Array.from( scrollableRef.current.querySelectorAll( '.status-link, .status-boost-link', ), ); if ( activeStatus && activeStatusRect.top < scrollableRef.current.clientHeight && activeStatusRect.bottom > 0 ) { const activeStatusIndex = allStatusLinks.indexOf(activeStatus); let nextStatus = allStatusLinks[activeStatusIndex + 1]; if (handler.shift) { // get next status that's not .status-boost-link nextStatus = allStatusLinks.find( (statusLink, index) => index > activeStatusIndex && !statusLink.classList.contains('status-boost-link'), ); } if (nextStatus) { nextStatus.focus(); nextStatus.scrollIntoViewIfNeeded?.(); } } else { // If active status is not in viewport, get the topmost status-link in viewport const topmostStatusLink = allStatusLinks.find((statusLink) => { const statusLinkRect = statusLink.getBoundingClientRect(); return statusLinkRect.top >= 44 && statusLinkRect.left >= 0; // 44 is the magic number for header height, not real }); if (topmostStatusLink) { topmostStatusLink.focus(); topmostStatusLink.scrollIntoViewIfNeeded?.(); } } }); useHotkeys('k. shift+k', () => { // focus on previous status after active status // Traverses .timeline li .status-link, focus on .status-link const activeStatus = document.activeElement.closest( '.status-link, .status-boost-link', ); const activeStatusRect = activeStatus?.getBoundingClientRect(); const allStatusLinks = Array.from( scrollableRef.current.querySelectorAll( '.status-link, .status-boost-link', ), ); if ( activeStatus && activeStatusRect.top < scrollableRef.current.clientHeight && activeStatusRect.bottom > 0 ) { const activeStatusIndex = allStatusLinks.indexOf(activeStatus); let prevStatus = allStatusLinks[activeStatusIndex - 1]; if (handler.shift) { // get prev status that's not .status-boost-link prevStatus = allStatusLinks.find( (statusLink, index) => index < activeStatusIndex && !statusLink.classList.contains('status-boost-link'), ); } if (prevStatus) { prevStatus.focus(); prevStatus.scrollIntoViewIfNeeded?.(); } } else { // If active status is not in viewport, get the topmost status-link in viewport const topmostStatusLink = allStatusLinks.find((statusLink) => { const statusLinkRect = statusLink.getBoundingClientRect(); return statusLinkRect.top >= 44 && statusLinkRect.left >= 0; // 44 is the magic number for header height, not real }); if (topmostStatusLink) { topmostStatusLink.focus(); topmostStatusLink.scrollIntoViewIfNeeded?.(); } } }); useHotkeys(['enter', 'o'], () => { // open active status const activeStatus = document.activeElement.closest( '.status-link, .status-boost-link', ); if (activeStatus) { activeStatus.click(); } }); const { scrollDirection, reachStart, nearReachStart, nearReachEnd, reachEnd, } = useScroll({ scrollableElement: scrollableRef.current, distanceFromStart: 1, distanceFromEnd: 3, scrollThresholdStart: 44, }); useEffect(() => { if (nearReachEnd || (reachEnd && showMore)) { loadStatuses(); } }, [nearReachEnd, reachEnd]); useEffect(() => { if (reachStart) { loadStatuses(true); } }, [reachStart]); useEffect(() => { (async () => { const keys = await db.drafts.keys(); if (keys.length) { const ns = getCurrentAccountNS(); const ownKeys = keys.filter((key) => key.startsWith(ns)); if (ownKeys.length) { states.showDrafts = true; } } })(); }, []); return (
Unable to load statuses