Skip to content

Commit b44d206

Browse files
committed
dark mode
1 parent 5c00be4 commit b44d206

File tree

3 files changed

+169
-80
lines changed

3 files changed

+169
-80
lines changed

src/App.js

Lines changed: 123 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import ListItem from '@mui/material/ListItem';
1515
import ListItemText from '@mui/material/ListItemText';
1616
import ListItemAvatar from '@mui/material/ListItemAvatar';
1717
import Divider from '@mui/material/Divider';
18+
import CssBaseline from '@mui/material/CssBaseline';
19+
import IconButton from '@mui/material/IconButton';
1820
import React from 'react';
1921
import General from './page/General.jsx';
2022
import Engines from './page/Engines.jsx';
@@ -24,6 +26,9 @@ import FindInPage from './page/FindInPage.jsx';
2426
import Link from '@mui/material/Link';
2527
import Snackbar from '@mui/material/Snackbar';
2628
import MuiAlert from '@mui/material/Alert';
29+
import { ThemeProvider, createTheme } from '@mui/material/styles';
30+
import DarkModeIcon from '@mui/icons-material/DarkMode';
31+
import WbSunnyIcon from '@mui/icons-material/WbSunny';
2732
import { createClient } from "webdav";
2833
import { version } from './Version.js';
2934

@@ -141,11 +146,36 @@ function a11yProps(index: number) {
141146
export default function App() {
142147
const [value, setValue] = React.useState(4);
143148
const [inited, setInited] = React.useState(false);
149+
const [darkMode, setDarkMode] = React.useState(() => {
150+
try {
151+
return localStorage.getItem('sj-dark-mode') === '1';
152+
} catch (e) {
153+
return false;
154+
}
155+
});
144156

145157
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
146158
setValue(newValue);
147159
document.documentElement.scrollTop = 0;
148160
};
161+
const handleDarkModeToggle = () => {
162+
setDarkMode(prev => {
163+
const nextMode = !prev;
164+
try {
165+
localStorage.setItem('sj-dark-mode', nextMode ? '1' : '0');
166+
} catch (e) {
167+
// Ignore storage failures (private mode, disabled storage).
168+
}
169+
return nextMode;
170+
});
171+
};
172+
const theme = React.useMemo(() => {
173+
return createTheme({
174+
palette: {
175+
mode: darkMode ? 'dark' : 'light'
176+
}
177+
});
178+
}, [darkMode]);
149179
const [alertBody, setAlert] = React.useState({openAlert: false, alertContent: '', alertType: 'error'});
150180
const handleAlertOpen = (content, type) => {
151181
switch (type) {
@@ -210,87 +240,104 @@ export default function App() {
210240
return true;
211241
}, true);
212242
}, [])
243+
React.useEffect(() => {
244+
document.body.dataset.theme = darkMode ? 'dark' : 'light';
245+
}, [darkMode]);
213246

214247
return (
215-
<Box
216-
inited={inited}
217-
sx={{ flexGrow: 1, bgcolor: 'background.paper', display: 'flex', height: '100vh', marginLeft: '200px' }}
218-
>
219-
<List
220-
id="tabs"
221-
component={Paper}
222-
elevation={5}
223-
sx={{
224-
width: '200px',
225-
height: '100%',
226-
left: 0,
227-
maxWidth: 200,
228-
position: 'fixed'
229-
}}
248+
<ThemeProvider theme={theme}>
249+
<CssBaseline />
250+
<Box
251+
inited={inited}
252+
sx={{ flexGrow: 1, bgcolor: 'background.paper', display: 'flex', height: '100vh', marginLeft: '200px' }}
230253
>
231-
<ListItem
232-
onClick={() => {
233-
document.querySelector('#tabs').classList.remove('hide');
254+
<List
255+
id="tabs"
256+
component={Paper}
257+
elevation={5}
258+
sx={{
259+
width: '200px',
260+
height: '100%',
261+
left: 0,
262+
maxWidth: 200,
263+
position: 'fixed'
234264
}}
235265
>
236-
<ListItemAvatar>
237-
<Link href='https://github.com/hoothin/SearchJumper' target="_blank">
238-
<Avatar alt="SearchJumper" component={Paper} elevation={5} src={logo}/>
239-
</Link>
240-
</ListItemAvatar>
241-
<ListItemText
242-
primary={window.i18n('name')}
243-
secondary={window.version ? ("Ver " + window.version) : (window.version === 0 ? "" : "Not installed")}
244-
sx={{cursor: 'pointer'}}
245-
onClick={e => {inited && window.version !== 0 && window.version !== version && window.open("https://greasyfork.org/scripts/445274-searchjumper/code/SearchJumper.user.js")}}
246-
secondaryTypographyProps={inited && window.version !== version ? {
247-
sx:{color: 'red'},
248-
title:window.i18n('outOfDate')
249-
} : (!inited ? {
250-
sx:{color: 'red'}
251-
} : {})}/>
252-
</ListItem>
253-
<Divider component="li" variant="inset" sx={{marginRight: 3}}/>
254-
<ListItem sx={{flexFlow: 'column', height: 'calc(100% - 75px)', overflowY: 'auto', overflowX: 'hidden'}}>
255-
<Tabs
256-
orientation="vertical"
257-
variant="scrollable"
258-
scrollButtons="auto"
259-
value={value}
260-
onChange={handleChange}
261-
onClick={()=>{document.querySelector('#tabs').classList.add('hide')}}
262-
aria-label="Vertical tabs example"
263-
sx={{ borderRight: 1, borderColor: 'divider', width: '100%', flexShrink: 0 }}
266+
<ListItem
267+
onClick={() => {
268+
document.querySelector('#tabs').classList.remove('hide');
269+
}}
264270
>
265-
<Tab label={window.i18n('general')} {...a11yProps(0)} />
266-
<Tab label={window.i18n('searchEngines')} {...a11yProps(1)} />
267-
<Tab label={window.i18n('findInPage')} {...a11yProps(2)} />
268-
<Tab label={window.i18n('exportConfig')} {...a11yProps(3)} />
269-
<Tab label={window.i18n('about')} {...a11yProps(4)} />
270-
</Tabs>
271-
{/^(http|ftp)/i.test(window.location.protocol) ? <embed className="sponsors" wmode="transparent" src="https://search.hoothin.com/sjsponsors.svg"/> : <div id="sponsors" className="sponsors"></div>}
272-
</ListItem>
273-
</List>
274-
<TabPanel value={value} index={0} sx={{width:1}}>
275-
{window.searchData ? <General/> : <About/>}
276-
</TabPanel>
277-
<TabPanel value={value} index={1}>
278-
{window.searchData ? <Engines/> : <About/>}
279-
</TabPanel>
280-
<TabPanel value={value} index={2}>
281-
{window.searchData ? <FindInPage/> : <About/>}
282-
</TabPanel>
283-
<TabPanel value={value} index={3}>
284-
{window.searchData ? <Export/> : <About/>}
285-
</TabPanel>
286-
<TabPanel value={value} index={4}>
287-
<About/>
288-
</TabPanel>
289-
<Snackbar open={alertBody.openAlert} autoHideDuration={5000} anchorOrigin={{vertical: 'top', horizontal: 'center'}} onClose={handleAlertClose}>
290-
<MuiAlert elevation={6} variant="filled" onClose={handleAlertClose} severity={alertBody.alertType} sx={{ width: '100%' }} >
291-
{alertBody.alertContent}
292-
</MuiAlert>
293-
</Snackbar>
294-
</Box>
271+
<ListItemAvatar>
272+
<Link href='https://github.com/hoothin/SearchJumper' target="_blank">
273+
<Avatar alt="SearchJumper" component={Paper} elevation={5} src={logo}/>
274+
</Link>
275+
</ListItemAvatar>
276+
<ListItemText
277+
primary={window.i18n('name')}
278+
secondary={window.version ? ("Ver " + window.version) : (window.version === 0 ? "" : "Not installed")}
279+
sx={{cursor: 'pointer'}}
280+
onClick={e => {inited && window.version !== 0 && window.version !== version && window.open("https://greasyfork.org/scripts/445274-searchjumper/code/SearchJumper.user.js")}}
281+
secondaryTypographyProps={inited && window.version !== version ? {
282+
sx:{color: 'red'},
283+
title:window.i18n('outOfDate')
284+
} : (!inited ? {
285+
sx:{color: 'red'}
286+
} : {})}/>
287+
</ListItem>
288+
<Divider component="li" variant="inset" sx={{marginRight: 3}}/>
289+
<ListItem sx={{flexFlow: 'column', height: 'calc(100% - 75px)', overflowY: 'auto', overflowX: 'hidden'}}>
290+
<Tabs
291+
orientation="vertical"
292+
variant="scrollable"
293+
scrollButtons="auto"
294+
value={value}
295+
onChange={handleChange}
296+
onClick={()=>{document.querySelector('#tabs').classList.add('hide')}}
297+
aria-label="Vertical tabs example"
298+
sx={{ borderRight: 1, borderColor: 'divider', width: '100%', flexShrink: 0 }}
299+
>
300+
<Tab label={window.i18n('general')} {...a11yProps(0)} />
301+
<Tab label={window.i18n('searchEngines')} {...a11yProps(1)} />
302+
<Tab label={window.i18n('findInPage')} {...a11yProps(2)} />
303+
<Tab label={window.i18n('exportConfig')} {...a11yProps(3)} />
304+
<Tab label={window.i18n('about')} {...a11yProps(4)} />
305+
</Tabs>
306+
<Box sx={{width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '4px 8px'}}>
307+
<IconButton
308+
size="small"
309+
onClick={handleDarkModeToggle}
310+
disabled={!inited}
311+
aria-label="Dark mode"
312+
title="Dark mode"
313+
>
314+
{darkMode ? <WbSunnyIcon fontSize="small" /> : <DarkModeIcon fontSize="small" />}
315+
</IconButton>
316+
</Box>
317+
{/^(http|ftp)/i.test(window.location.protocol) ? <embed className="sponsors" wmode="transparent" src="https://search.hoothin.com/sjsponsors.svg"/> : <div id="sponsors" className="sponsors"></div>}
318+
</ListItem>
319+
</List>
320+
<TabPanel value={value} index={0} sx={{width:1}}>
321+
{window.searchData ? <General/> : <About/>}
322+
</TabPanel>
323+
<TabPanel value={value} index={1}>
324+
{window.searchData ? <Engines/> : <About/>}
325+
</TabPanel>
326+
<TabPanel value={value} index={2}>
327+
{window.searchData ? <FindInPage/> : <About/>}
328+
</TabPanel>
329+
<TabPanel value={value} index={3}>
330+
{window.searchData ? <Export/> : <About/>}
331+
</TabPanel>
332+
<TabPanel value={value} index={4}>
333+
<About/>
334+
</TabPanel>
335+
<Snackbar open={alertBody.openAlert} autoHideDuration={5000} anchorOrigin={{vertical: 'top', horizontal: 'center'}} onClose={handleAlertClose}>
336+
<MuiAlert elevation={6} variant="filled" onClose={handleAlertClose} severity={alertBody.alertType} sx={{ width: '100%' }} >
337+
{alertBody.alertContent}
338+
</MuiAlert>
339+
</Snackbar>
340+
</Box>
341+
</ThemeProvider>
295342
);
296343
}

src/index.css

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,48 @@ body {
66
-webkit-font-smoothing: antialiased;
77
-moz-osx-font-smoothing: grayscale;
88
}
9+
body[data-theme="dark"] .unfold .Mui-selected {
10+
background: #26374a;
11+
}
12+
body[data-theme="dark"] input.filterEngine {
13+
background: #1f1f1f;
14+
color: #e6e6e6;
15+
}
16+
body[data-theme="dark"] input.filterEngine::placeholder {
17+
color: #9aa0a6;
18+
}
19+
body[data-theme="dark"] .MuiAccordionSummary-root,
20+
body[data-theme="dark"] .MuiAccordionSummary-root .MuiTypography-root,
21+
body[data-theme="dark"] .MuiAccordionSummary-expandIconWrapper {
22+
color: #e6e6e6;
23+
}
24+
body[data-theme="dark"] .MuiAccordionSummary-root {
25+
background-color: #151515;
26+
}
27+
body[data-theme="dark"] .MuiAccordionSummary-root.Mui-expanded {
28+
background-color: #1f1f1f;
29+
}
30+
body[data-theme="dark"] .MuiAccordionSummary-root:hover {
31+
background-color: #2a2a2a;
32+
}
33+
body[data-theme="dark"] {
34+
scrollbar-color: #555 #1a1a1a;
35+
}
36+
body[data-theme="dark"] *::-webkit-scrollbar {
37+
width: 10px;
38+
height: 10px;
39+
}
40+
body[data-theme="dark"] *::-webkit-scrollbar-track {
41+
background: #1a1a1a;
42+
}
43+
body[data-theme="dark"] *::-webkit-scrollbar-thumb {
44+
background-color: #555;
45+
border-radius: 8px;
46+
border: 2px solid #1a1a1a;
47+
}
48+
body[data-theme="dark"] *::-webkit-scrollbar-thumb:hover {
49+
background-color: #6b6b6b;
50+
}
951

1052
code {
1153
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
@@ -338,4 +380,4 @@ input.filterEngine+svg {
338380
display: inline-block;
339381
text-align: center;
340382
box-shadow: 1px 1px 3px #6e6e6e, inset -2px -2px 3px #e3e3e3, inset 2px 2px 3px #c9c9c9;
341-
}
383+
}

src/page/Export.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -929,12 +929,12 @@ export default function Export() {
929929
</Box>
930930
<Accordion defaultExpanded={true} sx={{ maxHeight: '50vh', overflow: 'auto' }}>
931931
<AccordionSummary
932-
sx={{background: '#f9f9f9', position: 'sticky', top: 0, minHeight: '45px!important', maxHeight: '45px!important', zIndex: 2}}
932+
sx={{backgroundColor: 'background.paper', color: 'text.primary', position: 'sticky', top: 0, minHeight: '45px!important', maxHeight: '45px!important', zIndex: 2}}
933933
expandIcon={<ExpandMoreIcon />}
934934
id="template-header"
935935
>
936936
<Typography sx={{display: 'block', width: '100%', textAlign: 'center', fontWeight: 'bold', fontSize: '1.1em'}} title={window.i18n("templateTips")}>{window.i18n("templateTitle")}</Typography>
937-
<IconButton sx={{fontSize: '30px', height: '24px', position: "absolute", color: "rgba(0, 0, 0, 0.54)"}} key='addTemplate'
937+
<IconButton sx={{fontSize: '30px', height: '24px', position: "absolute", color: "text.secondary"}} key='addTemplate'
938938
onClick={e => {
939939
e.preventDefault();
940940
e.stopPropagation();
@@ -1149,4 +1149,4 @@ export default function Export() {
11491149
</Snackbar>
11501150
</Box>
11511151
);
1152-
}
1152+
}

0 commit comments

Comments
 (0)