import React, { useState, useEffect, useRef, useCallback } from 'react'; import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom'; import localForage from 'localforage'; import './Style-light.css'; import './Style-dark.css'; import './Style-black.css'; import './Style-red.css'; import './fonts/Fonts.css'; import { BackwardDot, ForwardDot } from './utils.js'; import Feed from './Feed.js'; import Article from './Article.js'; import Comments from './Comments.js'; import Search from './Search.js'; import Submit from './Submit.js'; import Results from './Results.js'; import ScrollToTop from './ScrollToTop.js'; function App() { const [theme, setTheme] = useState(localStorage.getItem('theme') || ''); const cache = useRef({}); const [isFullScreen, setIsFullScreen] = useState(!!document.fullscreenElement); const [waitingWorker, setWaitingWorker] = useState(null); const [settingsOpen, setSettingsOpen] = useState(false); const defaultBodyFontSize = 1.0; const [bodyFontSize, setBodyFontSize] = useState(Number(localStorage.getItem('bodyFontSize')) || defaultBodyFontSize); const [bodyFont, setBodyFont] = useState(localStorage.getItem('bodyFont') || 'Sans Serif'); const [articleFont, setArticleFont] = useState(localStorage.getItem('articleFont') || 'Apparatus SIL'); const [filterSmallweb, setFilterSmallweb] = useState(() => localStorage.getItem('filterSmallweb') === 'true'); const [feedSources, setFeedSources] = useState(() => { const saved = localStorage.getItem('feedSources'); return saved ? JSON.parse(saved) : { hackernews: true, reddit: true, lobsters: true, tildes: true, }; }); const updateCache = useCallback((key, value) => { cache.current[key] = value; }, []); const light = () => { setTheme(''); localStorage.setItem('theme', ''); }; const dark = () => { setTheme('dark'); localStorage.setItem('theme', 'dark'); }; const black = () => { setTheme('black'); localStorage.setItem('theme', 'black'); }; const red = () => { setTheme('red'); localStorage.setItem('theme', 'red'); }; const handleFilterChange = e => { const isChecked = e.target.checked; setFilterSmallweb(isChecked); localStorage.setItem('filterSmallweb', isChecked); }; const handleFeedSourceChange = (source) => { setFeedSources(prevSources => { const newSources = { ...prevSources, [source]: !prevSources[source] }; localStorage.setItem('feedSources', JSON.stringify(newSources)); return newSources; }); }; const changeBodyFont = (font) => { setBodyFont(font); localStorage.setItem('bodyFont', font); }; const changeArticleFont = (font) => { setArticleFont(font); localStorage.setItem('articleFont', font); }; const changeBodyFontSize = (amount) => { const newSize = bodyFontSize + amount; setBodyFontSize(parseFloat(newSize.toFixed(2))); localStorage.setItem('bodyFontSize', newSize.toFixed(2)); }; const resetBodyFontSize = () => { setBodyFontSize(defaultBodyFontSize); localStorage.removeItem('bodyFontSize'); }; const bodyFontSettingsChanged = bodyFontSize !== defaultBodyFontSize; useEffect(() => { const onSWUpdate = e => { setWaitingWorker(e.detail.waiting); }; window.addEventListener('swUpdate', onSWUpdate); return () => window.removeEventListener('swUpdate', onSWUpdate); }, []); useEffect(() => { if (Object.keys(cache.current).length === 0) { localForage.iterate((value, key) => { updateCache(key, value); }).then(() => { console.log('loaded cache from localforage'); }); } }, [updateCache]); const goFullScreen = () => { if ('wakeLock' in navigator) { navigator.wakeLock.request('screen'); } document.body.requestFullscreen({ navigationUI: 'hide' }); }; const exitFullScreen = () => { document.exitFullscreen(); }; useEffect(() => { const onFullScreenChange = () => setIsFullScreen(!!document.fullscreenElement); document.addEventListener('fullscreenchange', onFullScreenChange); return () => document.removeEventListener('fullscreenchange', onFullScreenChange); }, []); useEffect(() => { if (theme === 'dark') { document.body.style.backgroundColor = '#1a1a1a'; } else if (theme === 'black') { document.body.style.backgroundColor = '#000'; } else if (theme === 'red') { document.body.style.backgroundColor = '#000'; } else { document.body.style.backgroundColor = '#eeeeee'; } }, [theme]); useEffect(() => { document.documentElement.style.fontSize = `${bodyFontSize}rem`; }, [bodyFontSize]); const fontMap = { 'Sans Serif': 'sans-serif', 'Serif': 'serif', 'Apparatus SIL': "'Apparatus SIL', sans-serif" }; useEffect(() => { document.body.style.fontFamily = fontMap[bodyFont]; }, [bodyFont]); useEffect(() => { const styleId = 'article-font-family-style'; let style = document.getElementById(styleId); if (!style) { style = document.createElement('style'); style.id = styleId; document.head.appendChild(style); } style.innerHTML = `.story-text { font-family: ${fontMap[articleFont]} !important; }`; }, [articleFont]); const fullScreenAvailable = document.fullscreenEnabled || document.mozFullscreenEnabled || document.webkitFullscreenEnabled || document.msFullscreenEnabled; return (
QotNews
Hacker News, Reddit, Lobsters, and Tildes articles rendered in reader mode.