Skip to content

Commit 2907cfa

Browse files
authored
Custom left menu (#26)
1 parent 01cd525 commit 2907cfa

File tree

3 files changed

+117
-22
lines changed

3 files changed

+117
-22
lines changed

src/components/Main.tsx

Lines changed: 106 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React, { ComponentType, ReactElement, useEffect, useState } from 'react';
22
import { NavLink, Route, useHistory } from 'react-router-dom';
33
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
44
import { Helmet } from 'react-helmet';
55

66
import { ipcRenderer } from 'electron';
7+
import classNames from 'classnames';
78
import MarkdownToHtml from './markdown/MarkdownToHtml';
89
import UnixTimestamp from './timestamp/UnixTimestamp';
910
import HtmlPreview from './html/HtmlPreview';
@@ -19,96 +20,120 @@ import Auto from './auto/Auto';
1920
import CronEditor from './cron/Cron';
2021
import JsConsole from './notebook/JavaScript';
2122

22-
const defaultRoutes = [
23+
interface MenuItem {
24+
path: string;
25+
name: string;
26+
show: boolean;
27+
icon: ReactElement<any, any>;
28+
Component: ComponentType;
29+
}
30+
31+
const defaultRoutes: MenuItem[] = [
2332
{
2433
icon: <FontAwesomeIcon icon="robot" />,
2534
path: '/auto',
2635
name: 'Auto Detection',
36+
show: true,
2737
Component: Auto,
2838
},
2939
{
3040
icon: <FontAwesomeIcon icon="clock" />,
3141
path: '/unix-converter',
3242
name: 'Unix Time Converter',
43+
show: true,
3344
Component: UnixTimestamp,
3445
},
3546
{
3647
icon: <FontAwesomeIcon icon="retweet" />,
3748
path: '/cron-editor',
3849
name: 'Cron Editor',
50+
show: true,
3951
Component: CronEditor,
4052
},
4153
{
4254
icon: <FontAwesomeIcon icon="registered" />,
4355
path: '/regex-tester',
4456
name: 'Regex Tester',
57+
show: true,
4558
Component: RegexTester,
4659
},
4760
{
4861
icon: <FontAwesomeIcon icon={['fab', 'markdown']} />,
4962
path: '/markdown-to-html',
5063
name: 'Markdown to HTML',
64+
show: true,
5165
Component: MarkdownToHtml,
5266
},
5367
{
5468
icon: <FontAwesomeIcon icon={['fab', 'html5']} />,
5569
path: '/html-preview',
5670
name: 'HTML Preview',
71+
show: true,
5772
Component: HtmlPreview,
5873
},
5974
{
6075
icon: <FontAwesomeIcon icon="qrcode" />,
6176
path: '/qrcode-generator',
6277
name: 'QRCode Generator',
78+
show: true,
6379
Component: QrCodeGenerator,
6480
},
6581
{
6682
icon: <FontAwesomeIcon icon="camera" />,
6783
path: '/qrcode-reader',
6884
name: 'QRCode Reader',
85+
show: true,
6986
Component: QRCodeReader,
7087
},
7188
{
7289
icon: <FontAwesomeIcon icon="code" />,
7390
path: '/base64-encoder',
7491
name: 'Base64 Encoder',
92+
show: true,
7593
Component: Base64,
7694
},
7795
{
7896
icon: <FontAwesomeIcon icon="exchange-alt" />,
7997
path: '/text-diff',
8098
name: 'Text Diff',
99+
show: true,
81100
Component: DiffText,
82101
},
83102
{
84103
icon: <FontAwesomeIcon icon={['fab', 'js-square']} />,
85104
path: '/json-formatter',
86105
name: 'JSON Formatter',
106+
show: true,
87107
Component: JsonFormatter,
88108
},
89109
{
90110
icon: <FontAwesomeIcon icon="database" />,
91111
path: '/sql-formatter',
92112
name: 'SQL Formatter',
113+
show: true,
93114
Component: SqlFormatter,
94115
},
95116
{
96117
icon: <FontAwesomeIcon icon="key" />,
97118
path: '/jwt-debugger',
98119
name: 'JWT Debugger',
120+
show: true,
99121
Component: JwtDebugger,
100122
},
101123
{
102124
icon: <FontAwesomeIcon icon={['fab', 'js']} />,
103125
path: '/js-console',
104126
name: 'Js Console',
127+
show: false,
105128
Component: JsConsole,
106129
},
107130
];
108131

109132
const Main = () => {
110-
const [routes, setRoutes] = useState(defaultRoutes);
133+
const [allRoutes, setAllRoutes] = useState<MenuItem[]>([]);
134+
const [routes, setRoutes] = useState<MenuItem[]>([]);
111135
const [search, setSearch] = useState('');
136+
const [editMenu, setEditMenu] = useState(false);
112137
const history = useHistory();
113138

114139
const handleSearch = (e: { target: { value: string } }) => {
@@ -122,21 +147,58 @@ const Main = () => {
122147
useEffect(() => {
123148
if (search.trim()) {
124149
setRoutes(
125-
defaultRoutes.filter(({ name }) => name.match(new RegExp(search, 'gi')))
150+
allRoutes.filter(({ name }) => name.match(new RegExp(search, 'gi')))
126151
);
152+
} else if (editMenu) {
153+
setRoutes(allRoutes);
127154
} else {
128-
setRoutes(defaultRoutes);
155+
setRoutes(allRoutes.filter((r) => r.show));
156+
}
157+
}, [search, editMenu]);
158+
159+
useEffect(() => {
160+
const routeMap: Record<string, boolean> = allRoutes.reduce(
161+
(a, b) => ({ ...a, [b.path]: b.show }),
162+
{}
163+
);
164+
setRoutes(allRoutes.filter((r) => editMenu || routeMap[r.path]));
165+
166+
if (allRoutes.length) {
167+
ipcRenderer.invoke('set-store', { key: 'left-menu', value: routeMap });
129168
}
130-
}, [search]);
169+
}, [allRoutes]);
170+
171+
useEffect(() => {
172+
const routeMap: Record<string, boolean> = defaultRoutes.reduce(
173+
(a, b) => ({ ...a, [b.path]: b.show }),
174+
{}
175+
);
176+
ipcRenderer
177+
.invoke('get-store', { key: 'left-menu' })
178+
.then((map) => {
179+
if (map) {
180+
const routeList = defaultRoutes.map((r) => ({
181+
...r,
182+
show:
183+
map[r.path] === true || map[r.path] === false
184+
? map[r.path]
185+
: routeMap[r.path],
186+
}));
187+
setAllRoutes(routeList);
188+
}
189+
return null;
190+
})
191+
.catch(console.error);
192+
}, []);
131193

132194
return (
133195
<div className="absolute inset-0 flex flex-col overflow-hidden">
134196
<main className="relative flex flex-1 min-h-0">
135197
{/* Left sidebar */}
136198
<nav className="flex flex-col flex-shrink-0 w-1/4 overflow-x-hidden overflow-y-auto bg-gray-300">
137199
{/* Search */}
138-
<div className="flex items-center px-2 mx-3 mt-6 space-x-1 text-gray-400 bg-gray-200 rounded-md focus-within:text-gray-600 focus-within:ring-2 focus-within:ring-blue-500">
139-
<FontAwesomeIcon icon="search" />
200+
<div className="flex items-center justify-between px-2 mx-3 mt-6 text-gray-400 bg-gray-200 rounded-md focus-within:text-gray-600 focus-within:ring-2 focus-within:ring-blue-500">
201+
<FontAwesomeIcon icon="search" className="mr-1" />
140202
<input
141203
type="text"
142204
className="w-full p-1 bg-gray-200 border-none rounded-r-md focus:ring-0"
@@ -148,8 +210,17 @@ const Main = () => {
148210
<FontAwesomeIcon
149211
icon="times-circle"
150212
onClick={() => setSearch('')}
213+
className="mr-2 cursor-pointer"
151214
/>
152215
)}
216+
<FontAwesomeIcon
217+
icon={editMenu ? 'check' : 'sliders-h'}
218+
onClick={() => setEditMenu(!editMenu)}
219+
className={classNames({
220+
'text-gray-400 cursor-pointer hover:text-gray-600': true,
221+
'text-blue-500 hover:text-blue-600': editMenu,
222+
})}
223+
/>
153224
</div>
154225

155226
<div
@@ -158,24 +229,42 @@ const Main = () => {
158229
aria-orientation="horizontal"
159230
aria-labelledby="options-menu"
160231
>
161-
{routes.map(({ path, name, icon }) => (
162-
<NavLink
163-
to={path}
232+
{routes.map(({ path, name, icon, show }) => (
233+
<section
164234
key={path}
165-
className="flex items-center justify-start px-3 py-1 mb-1 space-x-1 rounded-lg"
166-
activeClassName="bg-blue-400 text-white"
235+
className="flex items-center justify-between space-x-2"
167236
>
168-
<span className="w-6">{icon}</span>
169-
{name}
170-
</NavLink>
237+
<NavLink
238+
to={path}
239+
className="flex items-center justify-start flex-1 px-3 py-1 mb-1 space-x-1 rounded-lg"
240+
activeClassName="bg-blue-400 text-white"
241+
>
242+
<span className="w-6">{icon}</span>
243+
{name}
244+
</NavLink>
245+
{editMenu && (
246+
<input
247+
type="checkbox"
248+
checked={show}
249+
onChange={() =>
250+
setAllRoutes(
251+
allRoutes.map((r) =>
252+
r.path === path ? { ...r, show: !show } : r
253+
)
254+
)
255+
}
256+
className="w-4 h-4 rounded cursor-pointer"
257+
/>
258+
)}
259+
</section>
171260
))}
172261
</div>
173262
</nav>
174263

175264
{/* Main content */}
176265
<section className="relative flex flex-col w-3/4 bg-gray-200">
177266
<div className="h-full px-6 my-6 overflow-x-hidden overflow-y-auto">
178-
{defaultRoutes.map(({ path, name, Component }) => (
267+
{allRoutes.map(({ path, name, Component }) => (
179268
<Route key={path} exact path={path}>
180269
<Component />
181270
<Helmet>

src/helpers/fontAwesome.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
faCheckCircle,
2222
faRobot,
2323
faRetweet,
24+
faSlidersH,
25+
faCheck,
2426
} from '@fortawesome/free-solid-svg-icons';
2527

2628
library.add(
@@ -42,5 +44,7 @@ library.add(
4244
faCheckCircle,
4345
faRobot,
4446
faRetweet,
45-
faJs
47+
faJs,
48+
faSlidersH,
49+
faCheck
4650
);

src/main.dev.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ import MenuBuilder from './menu';
3131

3232
const Store = require('electron-store');
3333

34-
const store = new Store({
35-
hotkey: String,
36-
});
34+
const store = new Store();
3735

3836
export default class AppUpdater {
3937
constructor() {
@@ -218,10 +216,14 @@ ipcMain.handle(
218216
}
219217
);
220218

221-
ipcMain.handle('get-store', (_event, { key }) => {
219+
ipcMain.handle('get-store', async (_event, { key }) => {
222220
return store.get(key);
223221
});
224222

223+
ipcMain.handle('set-store', async (_event, { key, value }) => {
224+
return store.set(key, value);
225+
});
226+
225227
/**
226228
* Add event listeners...
227229
*/

0 commit comments

Comments
 (0)