1
- import React , { useEffect , useState } from 'react' ;
1
+ import React , { ComponentType , ReactElement , useEffect , useState } from 'react' ;
2
2
import { NavLink , Route , useHistory } from 'react-router-dom' ;
3
3
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' ;
4
4
import { Helmet } from 'react-helmet' ;
5
5
6
6
import { ipcRenderer } from 'electron' ;
7
+ import classNames from 'classnames' ;
7
8
import MarkdownToHtml from './markdown/MarkdownToHtml' ;
8
9
import UnixTimestamp from './timestamp/UnixTimestamp' ;
9
10
import HtmlPreview from './html/HtmlPreview' ;
@@ -19,96 +20,120 @@ import Auto from './auto/Auto';
19
20
import CronEditor from './cron/Cron' ;
20
21
import JsConsole from './notebook/JavaScript' ;
21
22
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 [ ] = [
23
32
{
24
33
icon : < FontAwesomeIcon icon = "robot" /> ,
25
34
path : '/auto' ,
26
35
name : 'Auto Detection' ,
36
+ show : true ,
27
37
Component : Auto ,
28
38
} ,
29
39
{
30
40
icon : < FontAwesomeIcon icon = "clock" /> ,
31
41
path : '/unix-converter' ,
32
42
name : 'Unix Time Converter' ,
43
+ show : true ,
33
44
Component : UnixTimestamp ,
34
45
} ,
35
46
{
36
47
icon : < FontAwesomeIcon icon = "retweet" /> ,
37
48
path : '/cron-editor' ,
38
49
name : 'Cron Editor' ,
50
+ show : true ,
39
51
Component : CronEditor ,
40
52
} ,
41
53
{
42
54
icon : < FontAwesomeIcon icon = "registered" /> ,
43
55
path : '/regex-tester' ,
44
56
name : 'Regex Tester' ,
57
+ show : true ,
45
58
Component : RegexTester ,
46
59
} ,
47
60
{
48
61
icon : < FontAwesomeIcon icon = { [ 'fab' , 'markdown' ] } /> ,
49
62
path : '/markdown-to-html' ,
50
63
name : 'Markdown to HTML' ,
64
+ show : true ,
51
65
Component : MarkdownToHtml ,
52
66
} ,
53
67
{
54
68
icon : < FontAwesomeIcon icon = { [ 'fab' , 'html5' ] } /> ,
55
69
path : '/html-preview' ,
56
70
name : 'HTML Preview' ,
71
+ show : true ,
57
72
Component : HtmlPreview ,
58
73
} ,
59
74
{
60
75
icon : < FontAwesomeIcon icon = "qrcode" /> ,
61
76
path : '/qrcode-generator' ,
62
77
name : 'QRCode Generator' ,
78
+ show : true ,
63
79
Component : QrCodeGenerator ,
64
80
} ,
65
81
{
66
82
icon : < FontAwesomeIcon icon = "camera" /> ,
67
83
path : '/qrcode-reader' ,
68
84
name : 'QRCode Reader' ,
85
+ show : true ,
69
86
Component : QRCodeReader ,
70
87
} ,
71
88
{
72
89
icon : < FontAwesomeIcon icon = "code" /> ,
73
90
path : '/base64-encoder' ,
74
91
name : 'Base64 Encoder' ,
92
+ show : true ,
75
93
Component : Base64 ,
76
94
} ,
77
95
{
78
96
icon : < FontAwesomeIcon icon = "exchange-alt" /> ,
79
97
path : '/text-diff' ,
80
98
name : 'Text Diff' ,
99
+ show : true ,
81
100
Component : DiffText ,
82
101
} ,
83
102
{
84
103
icon : < FontAwesomeIcon icon = { [ 'fab' , 'js-square' ] } /> ,
85
104
path : '/json-formatter' ,
86
105
name : 'JSON Formatter' ,
106
+ show : true ,
87
107
Component : JsonFormatter ,
88
108
} ,
89
109
{
90
110
icon : < FontAwesomeIcon icon = "database" /> ,
91
111
path : '/sql-formatter' ,
92
112
name : 'SQL Formatter' ,
113
+ show : true ,
93
114
Component : SqlFormatter ,
94
115
} ,
95
116
{
96
117
icon : < FontAwesomeIcon icon = "key" /> ,
97
118
path : '/jwt-debugger' ,
98
119
name : 'JWT Debugger' ,
120
+ show : true ,
99
121
Component : JwtDebugger ,
100
122
} ,
101
123
{
102
124
icon : < FontAwesomeIcon icon = { [ 'fab' , 'js' ] } /> ,
103
125
path : '/js-console' ,
104
126
name : 'Js Console' ,
127
+ show : false ,
105
128
Component : JsConsole ,
106
129
} ,
107
130
] ;
108
131
109
132
const Main = ( ) => {
110
- const [ routes , setRoutes ] = useState ( defaultRoutes ) ;
133
+ const [ allRoutes , setAllRoutes ] = useState < MenuItem [ ] > ( [ ] ) ;
134
+ const [ routes , setRoutes ] = useState < MenuItem [ ] > ( [ ] ) ;
111
135
const [ search , setSearch ] = useState ( '' ) ;
136
+ const [ editMenu , setEditMenu ] = useState ( false ) ;
112
137
const history = useHistory ( ) ;
113
138
114
139
const handleSearch = ( e : { target : { value : string } } ) => {
@@ -122,21 +147,58 @@ const Main = () => {
122
147
useEffect ( ( ) => {
123
148
if ( search . trim ( ) ) {
124
149
setRoutes (
125
- defaultRoutes . filter ( ( { name } ) => name . match ( new RegExp ( search , 'gi' ) ) )
150
+ allRoutes . filter ( ( { name } ) => name . match ( new RegExp ( search , 'gi' ) ) )
126
151
) ;
152
+ } else if ( editMenu ) {
153
+ setRoutes ( allRoutes ) ;
127
154
} 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 } ) ;
129
168
}
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
+ } , [ ] ) ;
131
193
132
194
return (
133
195
< div className = "absolute inset-0 flex flex-col overflow-hidden" >
134
196
< main className = "relative flex flex-1 min-h-0" >
135
197
{ /* Left sidebar */ }
136
198
< nav className = "flex flex-col flex-shrink-0 w-1/4 overflow-x-hidden overflow-y-auto bg-gray-300" >
137
199
{ /* 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" />
140
202
< input
141
203
type = "text"
142
204
className = "w-full p-1 bg-gray-200 border-none rounded-r-md focus:ring-0"
@@ -148,8 +210,17 @@ const Main = () => {
148
210
< FontAwesomeIcon
149
211
icon = "times-circle"
150
212
onClick = { ( ) => setSearch ( '' ) }
213
+ className = "mr-2 cursor-pointer"
151
214
/>
152
215
) }
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
+ />
153
224
</ div >
154
225
155
226
< div
@@ -158,24 +229,42 @@ const Main = () => {
158
229
aria-orientation = "horizontal"
159
230
aria-labelledby = "options-menu"
160
231
>
161
- { routes . map ( ( { path, name, icon } ) => (
162
- < NavLink
163
- to = { path }
232
+ { routes . map ( ( { path, name, icon, show } ) => (
233
+ < section
164
234
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"
167
236
>
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 >
171
260
) ) }
172
261
</ div >
173
262
</ nav >
174
263
175
264
{ /* Main content */ }
176
265
< section className = "relative flex flex-col w-3/4 bg-gray-200" >
177
266
< 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 } ) => (
179
268
< Route key = { path } exact path = { path } >
180
269
< Component />
181
270
< Helmet >
0 commit comments