refactor: Extract settings modal to dedicated component
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
@@ -41,63 +41,6 @@ function App() {
|
|||||||
cache.current[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(() => {
|
useEffect(() => {
|
||||||
const onSWUpdate = e => {
|
const onSWUpdate = e => {
|
||||||
setWaitingWorker(e.detail.waiting);
|
setWaitingWorker(e.detail.waiting);
|
||||||
@@ -116,17 +59,6 @@ function App() {
|
|||||||
}
|
}
|
||||||
}, [updateCache]);
|
}, [updateCache]);
|
||||||
|
|
||||||
const goFullScreen = () => {
|
|
||||||
if ('wakeLock' in navigator) {
|
|
||||||
navigator.wakeLock.request('screen');
|
|
||||||
}
|
|
||||||
document.body.requestFullscreen({ navigationUI: 'hide' });
|
|
||||||
};
|
|
||||||
|
|
||||||
const exitFullScreen = () => {
|
|
||||||
document.exitFullscreen();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onFullScreenChange = () => setIsFullScreen(!!document.fullscreenElement);
|
const onFullScreenChange = () => setIsFullScreen(!!document.fullscreenElement);
|
||||||
document.addEventListener('fullscreenchange', onFullScreenChange);
|
document.addEventListener('fullscreenchange', onFullScreenChange);
|
||||||
@@ -171,104 +103,26 @@ function App() {
|
|||||||
}, [articleFont]);
|
}, [articleFont]);
|
||||||
|
|
||||||
|
|
||||||
const fullScreenAvailable = document.fullscreenEnabled ||
|
|
||||||
document.mozFullscreenEnabled ||
|
|
||||||
document.webkitFullscreenEnabled ||
|
|
||||||
document.msFullscreenEnabled;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={theme}>
|
<div className={theme}>
|
||||||
{settingsOpen &&
|
<Settings
|
||||||
<>
|
settingsOpen={settingsOpen}
|
||||||
<div className="modal-overlay" onClick={() => setSettingsOpen(false)}></div>
|
setSettingsOpen={setSettingsOpen}
|
||||||
|
theme={theme}
|
||||||
<div className="modal-content" onClick={e => e.stopPropagation()}>
|
setTheme={setTheme}
|
||||||
<button className="close-modal-button" onClick={() => setSettingsOpen(false)}>×</button>
|
isFullScreen={isFullScreen}
|
||||||
<h3>Settings</h3>
|
filterSmallweb={filterSmallweb}
|
||||||
|
setFilterSmallweb={setFilterSmallweb}
|
||||||
<div className="setting-group">
|
feedSources={feedSources}
|
||||||
<h4>Theme</h4>
|
setFeedSources={setFeedSources}
|
||||||
<button className={theme === '' ? 'active' : ''} onClick={() => { light(); setSettingsOpen(false); }}>Light</button>
|
bodyFontSize={bodyFontSize}
|
||||||
<button className={theme === 'dark' ? 'active' : ''} onClick={() => { dark(); setSettingsOpen(false); }}>Dark</button>
|
setBodyFontSize={setBodyFontSize}
|
||||||
<button className={theme === 'black' ? 'active' : ''} onClick={() => { black(); setSettingsOpen(false); }}>Black</button>
|
defaultBodyFontSize={defaultBodyFontSize}
|
||||||
<button className={theme === 'red' ? 'active' : ''} onClick={() => { red(); setSettingsOpen(false); }}>Red</button>
|
bodyFont={bodyFont}
|
||||||
{fullScreenAvailable &&
|
setBodyFont={setBodyFont}
|
||||||
<div style={{ marginTop: '0.5rem' }}>
|
articleFont={articleFont}
|
||||||
{!isFullScreen ?
|
setArticleFont={setArticleFont}
|
||||||
<button onClick={() => { goFullScreen(); setSettingsOpen(false); }}>Enter Fullscreen</button>
|
/>
|
||||||
:
|
|
||||||
<button onClick={() => { exitFullScreen(); setSettingsOpen(false); }}>Exit Fullscreen</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="setting-group">
|
|
||||||
<h4>Feed</h4>
|
|
||||||
<div className="font-option gap">
|
|
||||||
<input className="checkbox" type="checkbox" id="filter-smallweb" checked={filterSmallweb} onChange={handleFilterChange} />
|
|
||||||
<label htmlFor="filter-smallweb">Small websites only</label>
|
|
||||||
</div>
|
|
||||||
<div className="font-option">
|
|
||||||
<input className="checkbox" type="checkbox" id="filter-hackernews" name="feed-source" checked={feedSources.hackernews} onChange={() => handleFeedSourceChange('hackernews')} />
|
|
||||||
<label htmlFor="filter-hackernews">Hacker News</label>
|
|
||||||
</div>
|
|
||||||
<div className="font-option">
|
|
||||||
<input className="checkbox" type="checkbox" id="filter-reddit" name="feed-source" checked={feedSources.reddit} onChange={() => handleFeedSourceChange('reddit')} />
|
|
||||||
<label htmlFor="filter-reddit">Reddit</label>
|
|
||||||
</div>
|
|
||||||
<div className="font-option">
|
|
||||||
<input className="checkbox" type="checkbox" id="filter-lobsters" name="feed-source" checked={feedSources.lobsters} onChange={() => handleFeedSourceChange('lobsters')} />
|
|
||||||
<label htmlFor="filter-lobsters">Lobsters</label>
|
|
||||||
</div>
|
|
||||||
<div className="font-option">
|
|
||||||
<input className="checkbox" type="checkbox" id="filter-tildes" name="feed-source" checked={feedSources.tildes} onChange={() => handleFeedSourceChange('tildes')} />
|
|
||||||
<label htmlFor="filter-tildes">Tildes</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="setting-group">
|
|
||||||
<h4>Font Size</h4>
|
|
||||||
<button onClick={() => changeBodyFontSize(-0.05)}>-</button>
|
|
||||||
<span className="font-size-display">{bodyFontSize.toFixed(2)}</span>
|
|
||||||
<button onClick={() => changeBodyFontSize(0.05)}>+</button>
|
|
||||||
<button onClick={resetBodyFontSize} disabled={!bodyFontSettingsChanged}>Reset</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="setting-group">
|
|
||||||
<h4>Body Font</h4>
|
|
||||||
<div className="font-option">
|
|
||||||
<input className="checkbox" type="radio" id="body-sans-serif" name="body-font" value="Sans Serif" checked={bodyFont === 'Sans Serif'} onChange={() => changeBodyFont('Sans Serif')} />
|
|
||||||
<label htmlFor="body-sans-serif">Sans Serif *</label>
|
|
||||||
</div>
|
|
||||||
<div className="font-option">
|
|
||||||
<input className="checkbox" type="radio" id="body-serif" name="body-font" value="Serif" checked={bodyFont === 'Serif'} onChange={() => changeBodyFont('Serif')} />
|
|
||||||
<label htmlFor="body-serif">Serif</label>
|
|
||||||
</div>
|
|
||||||
<div className="font-option">
|
|
||||||
<input className="checkbox" type="radio" id="body-apparatus" name="body-font" value="Apparatus SIL" checked={bodyFont === 'Apparatus SIL'} onChange={() => changeBodyFont('Apparatus SIL')} />
|
|
||||||
<label htmlFor="body-apparatus">Apparatus SIL</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="setting-group">
|
|
||||||
<h4>Article Font</h4>
|
|
||||||
<div className="font-option">
|
|
||||||
<input className="checkbox" type="radio" id="article-sans-serif" name="article-font" value="Sans Serif" checked={articleFont === 'Sans Serif'} onChange={() => changeArticleFont('Sans Serif')} />
|
|
||||||
<label htmlFor="article-sans-serif">Sans Serif</label>
|
|
||||||
</div>
|
|
||||||
<div className="font-option">
|
|
||||||
<input className="checkbox" type="radio" id="article-serif" name="article-font" value="Serif" checked={articleFont === 'Serif'} onChange={() => changeArticleFont('Serif')} />
|
|
||||||
<label htmlFor="article-serif">Serif</label>
|
|
||||||
</div>
|
|
||||||
<div className="font-option">
|
|
||||||
<input className="checkbox" type="radio" id="article-apparatus" name="article-font" value="Apparatus SIL" checked={articleFont === 'Apparatus SIL'} onChange={() => changeArticleFont('Apparatus SIL')} />
|
|
||||||
<label htmlFor="article-apparatus">Apparatus SIL *</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
|
|
||||||
{waitingWorker &&
|
{waitingWorker &&
|
||||||
<div className='update-banner'>
|
<div className='update-banner'>
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function Settings({
|
||||||
|
settingsOpen,
|
||||||
|
setSettingsOpen,
|
||||||
|
theme,
|
||||||
|
setTheme,
|
||||||
|
isFullScreen,
|
||||||
|
filterSmallweb,
|
||||||
|
setFilterSmallweb,
|
||||||
|
feedSources,
|
||||||
|
setFeedSources,
|
||||||
|
bodyFontSize,
|
||||||
|
setBodyFontSize,
|
||||||
|
defaultBodyFontSize,
|
||||||
|
bodyFont,
|
||||||
|
setBodyFont,
|
||||||
|
articleFont,
|
||||||
|
setArticleFont,
|
||||||
|
}) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
const goFullScreen = () => {
|
||||||
|
if ('wakeLock' in navigator) {
|
||||||
|
navigator.wakeLock.request('screen');
|
||||||
|
}
|
||||||
|
document.body.requestFullscreen({ navigationUI: 'hide' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const exitFullScreen = () => {
|
||||||
|
document.exitFullscreen();
|
||||||
|
};
|
||||||
|
|
||||||
|
const fullScreenAvailable = document.fullscreenEnabled ||
|
||||||
|
document.mozFullscreenEnabled ||
|
||||||
|
document.webkitFullscreenEnabled ||
|
||||||
|
document.msFullscreenEnabled;
|
||||||
|
|
||||||
|
if (!settingsOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="modal-overlay" onClick={() => setSettingsOpen(false)}></div>
|
||||||
|
|
||||||
|
<div className="modal-content" onClick={e => e.stopPropagation()}>
|
||||||
|
<button className="close-modal-button" onClick={() => setSettingsOpen(false)}>×</button>
|
||||||
|
<h3>Settings</h3>
|
||||||
|
|
||||||
|
<div className="setting-group">
|
||||||
|
<h4>Theme</h4>
|
||||||
|
<button className={theme === '' ? 'active' : ''} onClick={() => { light(); setSettingsOpen(false); }}>Light</button>
|
||||||
|
<button className={theme === 'dark' ? 'active' : ''} onClick={() => { dark(); setSettingsOpen(false); }}>Dark</button>
|
||||||
|
<button className={theme === 'black' ? 'active' : ''} onClick={() => { black(); setSettingsOpen(false); }}>Black</button>
|
||||||
|
<button className={theme === 'red' ? 'active' : ''} onClick={() => { red(); setSettingsOpen(false); }}>Red</button>
|
||||||
|
{fullScreenAvailable &&
|
||||||
|
<div style={{ marginTop: '0.5rem' }}>
|
||||||
|
{!isFullScreen ?
|
||||||
|
<button onClick={() => { goFullScreen(); setSettingsOpen(false); }}>Enter Fullscreen</button>
|
||||||
|
:
|
||||||
|
<button onClick={() => { exitFullScreen(); setSettingsOpen(false); }}>Exit Fullscreen</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="setting-group">
|
||||||
|
<h4>Feed</h4>
|
||||||
|
<div className="font-option gap">
|
||||||
|
<input className="checkbox" type="checkbox" id="filter-smallweb" checked={filterSmallweb} onChange={handleFilterChange} />
|
||||||
|
<label htmlFor="filter-smallweb">Small websites only</label>
|
||||||
|
</div>
|
||||||
|
<div className="font-option">
|
||||||
|
<input className="checkbox" type="checkbox" id="filter-hackernews" name="feed-source" checked={feedSources.hackernews} onChange={() => handleFeedSourceChange('hackernews')} />
|
||||||
|
<label htmlFor="filter-hackernews">Hacker News</label>
|
||||||
|
</div>
|
||||||
|
<div className="font-option">
|
||||||
|
<input className="checkbox" type="checkbox" id="filter-reddit" name="feed-source" checked={feedSources.reddit} onChange={() => handleFeedSourceChange('reddit')} />
|
||||||
|
<label htmlFor="filter-reddit">Reddit</label>
|
||||||
|
</div>
|
||||||
|
<div className="font-option">
|
||||||
|
<input className="checkbox" type="checkbox" id="filter-lobsters" name="feed-source" checked={feedSources.lobsters} onChange={() => handleFeedSourceChange('lobsters')} />
|
||||||
|
<label htmlFor="filter-lobsters">Lobsters</label>
|
||||||
|
</div>
|
||||||
|
<div className="font-option">
|
||||||
|
<input className="checkbox" type="checkbox" id="filter-tildes" name="feed-source" checked={feedSources.tildes} onChange={() => handleFeedSourceChange('tildes')} />
|
||||||
|
<label htmlFor="filter-tildes">Tildes</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="setting-group">
|
||||||
|
<h4>Font Size</h4>
|
||||||
|
<button onClick={() => changeBodyFontSize(-0.05)}>-</button>
|
||||||
|
<span className="font-size-display">{bodyFontSize.toFixed(2)}</span>
|
||||||
|
<button onClick={() => changeBodyFontSize(0.05)}>+</button>
|
||||||
|
<button onClick={resetBodyFontSize} disabled={!bodyFontSettingsChanged}>Reset</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="setting-group">
|
||||||
|
<h4>Body Font</h4>
|
||||||
|
<div className="font-option">
|
||||||
|
<input className="checkbox" type="radio" id="body-sans-serif" name="body-font" value="Sans Serif" checked={bodyFont === 'Sans Serif'} onChange={() => changeBodyFont('Sans Serif')} />
|
||||||
|
<label htmlFor="body-sans-serif">Sans Serif *</label>
|
||||||
|
</div>
|
||||||
|
<div className="font-option">
|
||||||
|
<input className="checkbox" type="radio" id="body-serif" name="body-font" value="Serif" checked={bodyFont === 'Serif'} onChange={() => changeBodyFont('Serif')} />
|
||||||
|
<label htmlFor="body-serif">Serif</label>
|
||||||
|
</div>
|
||||||
|
<div className="font-option">
|
||||||
|
<input className="checkbox" type="radio" id="body-apparatus" name="body-font" value="Apparatus SIL" checked={bodyFont === 'Apparatus SIL'} onChange={() => changeBodyFont('Apparatus SIL')} />
|
||||||
|
<label htmlFor="body-apparatus">Apparatus SIL</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="setting-group">
|
||||||
|
<h4>Article Font</h4>
|
||||||
|
<div className="font-option">
|
||||||
|
<input className="checkbox" type="radio" id="article-sans-serif" name="article-font" value="Sans Serif" checked={articleFont === 'Sans Serif'} onChange={() => changeArticleFont('Sans Serif')} />
|
||||||
|
<label htmlFor="article-sans-serif">Sans Serif</label>
|
||||||
|
</div>
|
||||||
|
<div className="font-option">
|
||||||
|
<input className="checkbox" type="radio" id="article-serif" name="article-font" value="Serif" checked={articleFont === 'Serif'} onChange={() => changeArticleFont('Serif')} />
|
||||||
|
<label htmlFor="article-serif">Serif</label>
|
||||||
|
</div>
|
||||||
|
<div className="font-option">
|
||||||
|
<input className="checkbox" type="radio" id="article-apparatus" name="article-font" value="Apparatus SIL" checked={articleFont === 'Apparatus SIL'} onChange={() => changeArticleFont('Apparatus SIL')} />
|
||||||
|
<label htmlFor="article-apparatus">Apparatus SIL *</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Settings;
|
||||||
|
|||||||
Reference in New Issue
Block a user