1- import React , { useEffect , useState } from 'react' ;
1+ import React , { ComponentType , ReactElement , useEffect , useState } from 'react' ;
22import { NavLink , Route , useHistory } from 'react-router-dom' ;
33import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' ;
44import { Helmet } from 'react-helmet' ;
55
66import { ipcRenderer } from 'electron' ;
7+ import classNames from 'classnames' ;
78import MarkdownToHtml from './markdown/MarkdownToHtml' ;
89import UnixTimestamp from './timestamp/UnixTimestamp' ;
910import HtmlPreview from './html/HtmlPreview' ;
@@ -19,96 +20,120 @@ import Auto from './auto/Auto';
1920import CronEditor from './cron/Cron' ;
2021import 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
109132const 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 >
0 commit comments