feat: Implement settings modal with theme and font size adjustments
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
@@ -20,6 +20,11 @@ function App() {
|
|||||||
const cache = useRef({});
|
const cache = useRef({});
|
||||||
const [isFullScreen, setIsFullScreen] = useState(!!document.fullscreenElement);
|
const [isFullScreen, setIsFullScreen] = useState(!!document.fullscreenElement);
|
||||||
const [waitingWorker, setWaitingWorker] = useState(null);
|
const [waitingWorker, setWaitingWorker] = useState(null);
|
||||||
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
|
const defaultBodyFontSize = 16;
|
||||||
|
const defaultStoryFontSize = 1.2;
|
||||||
|
const [bodyFontSize, setBodyFontSize] = useState(Number(localStorage.getItem('bodyFontSize')) || defaultBodyFontSize);
|
||||||
|
const [storyFontSize, setStoryFontSize] = useState(Number(localStorage.getItem('storyFontSize')) || defaultStoryFontSize);
|
||||||
|
|
||||||
const updateCache = useCallback((key, value) => {
|
const updateCache = useCallback((key, value) => {
|
||||||
cache.current[key] = value;
|
cache.current[key] = value;
|
||||||
@@ -45,6 +50,27 @@ function App() {
|
|||||||
localStorage.setItem('theme', 'red');
|
localStorage.setItem('theme', 'red');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const changeBodyFontSize = (amount) => {
|
||||||
|
const newSize = bodyFontSize + amount;
|
||||||
|
setBodyFontSize(newSize);
|
||||||
|
localStorage.setItem('bodyFontSize', newSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeStoryFontSize = (amount) => {
|
||||||
|
const newSize = storyFontSize + amount;
|
||||||
|
setStoryFontSize(parseFloat(newSize.toFixed(1)));
|
||||||
|
localStorage.setItem('storyFontSize', newSize.toFixed(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetFontSettings = () => {
|
||||||
|
setBodyFontSize(defaultBodyFontSize);
|
||||||
|
localStorage.removeItem('bodyFontSize');
|
||||||
|
setStoryFontSize(defaultStoryFontSize);
|
||||||
|
localStorage.removeItem('storyFontSize');
|
||||||
|
};
|
||||||
|
|
||||||
|
const fontSettingsChanged = bodyFontSize !== defaultBodyFontSize || storyFontSize !== defaultStoryFontSize;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onSWUpdate = e => {
|
const onSWUpdate = e => {
|
||||||
setWaitingWorker(e.detail.waiting);
|
setWaitingWorker(e.detail.waiting);
|
||||||
@@ -92,6 +118,21 @@ function App() {
|
|||||||
}
|
}
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.style.fontSize = `${bodyFontSize}px`;
|
||||||
|
}, [bodyFontSize]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const styleId = 'story-font-size-style';
|
||||||
|
let style = document.getElementById(styleId);
|
||||||
|
if (!style) {
|
||||||
|
style = document.createElement('style');
|
||||||
|
style.id = styleId;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
style.innerHTML = `.story-text { font-size: ${storyFontSize}rem !important; }`;
|
||||||
|
}, [storyFontSize]);
|
||||||
|
|
||||||
const fullScreenAvailable = document.fullscreenEnabled ||
|
const fullScreenAvailable = document.fullscreenEnabled ||
|
||||||
document.mozFullscreenEnabled ||
|
document.mozFullscreenEnabled ||
|
||||||
document.webkitFullscreenEnabled ||
|
document.webkitFullscreenEnabled ||
|
||||||
@@ -99,6 +140,33 @@ function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={theme}>
|
<div className={theme}>
|
||||||
|
{settingsOpen &&
|
||||||
|
<div className="modal-overlay" onClick={() => setSettingsOpen(false)}>
|
||||||
|
<div className="modal-content" onClick={e => e.stopPropagation()}>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div className="setting-group">
|
||||||
|
<h4>Body Font Size</h4>
|
||||||
|
<button onClick={() => changeBodyFontSize(-1)}>-</button>
|
||||||
|
<span className="font-size-display">{bodyFontSize}px</span>
|
||||||
|
<button onClick={() => changeBodyFontSize(1)}>+</button>
|
||||||
|
</div>
|
||||||
|
<div className="setting-group">
|
||||||
|
<h4>Story Font Size</h4>
|
||||||
|
<button onClick={() => changeStoryFontSize(-0.1)}>-</button>
|
||||||
|
<span className="font-size-display">{storyFontSize.toFixed(1)}rem</span>
|
||||||
|
<button onClick={() => changeStoryFontSize(0.1)}>+</button>
|
||||||
|
</div>
|
||||||
|
<button onClick={resetFontSettings} disabled={!fontSettingsChanged}>Reset Font Settings</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
{waitingWorker &&
|
{waitingWorker &&
|
||||||
<div className='update-banner'>
|
<div className='update-banner'>
|
||||||
Client version mismatch, please refresh:{' '}
|
Client version mismatch, please refresh:{' '}
|
||||||
@@ -117,7 +185,7 @@ function App() {
|
|||||||
<p>
|
<p>
|
||||||
<Link to='/'>QotNews</Link>
|
<Link to='/'>QotNews</Link>
|
||||||
|
|
||||||
<span className='theme'><a href='#' onClick={() => light()}>Light</a> - <a href='#' onClick={() => dark()}>Dark</a> - <a href='#' onClick={() => black()}>Black</a> - <a href='#' onClick={() => red()}>Red</a></span>
|
<button className="settings-button" onClick={() => setSettingsOpen(true)}>Settings</button>
|
||||||
<br />
|
<br />
|
||||||
<span className='slogan'>Hacker News, Reddit, Lobsters, and Tildes articles rendered in reader mode.</span>
|
<span className='slogan'>Hacker News, Reddit, Lobsters, and Tildes articles rendered in reader mode.</span>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -405,3 +405,77 @@ button.comment {
|
|||||||
visibility: visible;
|
visibility: visible;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-button {
|
||||||
|
float: right;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #828282;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: fixed;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
background: #eee;
|
||||||
|
color: #000;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 101;
|
||||||
|
min-width: 250px;
|
||||||
|
border: 1px solid #828282;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content h3, .modal-content h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content .setting-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content button {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border: 1px solid #828282;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content button:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content button.active {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content .font-size-display {
|
||||||
|
display: inline-block;
|
||||||
|
width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 0.25rem;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user