Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 106 additions & 17 deletions src/components/Main.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useEffect, useState } from 'react';
import React, { ComponentType, ReactElement, useEffect, useState } from 'react';
import { NavLink, Route, useHistory } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Helmet } from 'react-helmet';

import { ipcRenderer } from 'electron';
import classNames from 'classnames';
import MarkdownToHtml from './markdown/MarkdownToHtml';
import UnixTimestamp from './timestamp/UnixTimestamp';
import HtmlPreview from './html/HtmlPreview';
Expand All @@ -19,96 +20,120 @@ import Auto from './auto/Auto';
import CronEditor from './cron/Cron';
import JsConsole from './notebook/JavaScript';

const defaultRoutes = [
interface MenuItem {
path: string;
name: string;
show: boolean;
icon: ReactElement<any, any>;
Component: ComponentType;
}

const defaultRoutes: MenuItem[] = [
{
icon: <FontAwesomeIcon icon="robot" />,
path: '/auto',
name: 'Auto Detection',
show: true,
Component: Auto,
},
{
icon: <FontAwesomeIcon icon="clock" />,
path: '/unix-converter',
name: 'Unix Time Converter',
show: true,
Component: UnixTimestamp,
},
{
icon: <FontAwesomeIcon icon="retweet" />,
path: '/cron-editor',
name: 'Cron Editor',
show: true,
Component: CronEditor,
},
{
icon: <FontAwesomeIcon icon="registered" />,
path: '/regex-tester',
name: 'Regex Tester',
show: true,
Component: RegexTester,
},
{
icon: <FontAwesomeIcon icon={['fab', 'markdown']} />,
path: '/markdown-to-html',
name: 'Markdown to HTML',
show: true,
Component: MarkdownToHtml,
},
{
icon: <FontAwesomeIcon icon={['fab', 'html5']} />,
path: '/html-preview',
name: 'HTML Preview',
show: true,
Component: HtmlPreview,
},
{
icon: <FontAwesomeIcon icon="qrcode" />,
path: '/qrcode-generator',
name: 'QRCode Generator',
show: true,
Component: QrCodeGenerator,
},
{
icon: <FontAwesomeIcon icon="camera" />,
path: '/qrcode-reader',
name: 'QRCode Reader',
show: true,
Component: QRCodeReader,
},
{
icon: <FontAwesomeIcon icon="code" />,
path: '/base64-encoder',
name: 'Base64 Encoder',
show: true,
Component: Base64,
},
{
icon: <FontAwesomeIcon icon="exchange-alt" />,
path: '/text-diff',
name: 'Text Diff',
show: true,
Component: DiffText,
},
{
icon: <FontAwesomeIcon icon={['fab', 'js-square']} />,
path: '/json-formatter',
name: 'JSON Formatter',
show: true,
Component: JsonFormatter,
},
{
icon: <FontAwesomeIcon icon="database" />,
path: '/sql-formatter',
name: 'SQL Formatter',
show: true,
Component: SqlFormatter,
},
{
icon: <FontAwesomeIcon icon="key" />,
path: '/jwt-debugger',
name: 'JWT Debugger',
show: true,
Component: JwtDebugger,
},
{
icon: <FontAwesomeIcon icon={['fab', 'js']} />,
path: '/js-console',
name: 'Js Console',
show: false,
Component: JsConsole,
},
];

const Main = () => {
const [routes, setRoutes] = useState(defaultRoutes);
const [allRoutes, setAllRoutes] = useState<MenuItem[]>([]);
const [routes, setRoutes] = useState<MenuItem[]>([]);
const [search, setSearch] = useState('');
const [editMenu, setEditMenu] = useState(false);
const history = useHistory();

const handleSearch = (e: { target: { value: string } }) => {
Expand All @@ -122,21 +147,58 @@ const Main = () => {
useEffect(() => {
if (search.trim()) {
setRoutes(
defaultRoutes.filter(({ name }) => name.match(new RegExp(search, 'gi')))
allRoutes.filter(({ name }) => name.match(new RegExp(search, 'gi')))
);
} else if (editMenu) {
setRoutes(allRoutes);
} else {
setRoutes(defaultRoutes);
setRoutes(allRoutes.filter((r) => r.show));
}
}, [search, editMenu]);

useEffect(() => {
const routeMap: Record<string, boolean> = allRoutes.reduce(
(a, b) => ({ ...a, [b.path]: b.show }),
{}
);
setRoutes(allRoutes.filter((r) => editMenu || routeMap[r.path]));

if (allRoutes.length) {
ipcRenderer.invoke('set-store', { key: 'left-menu', value: routeMap });
}
}, [search]);
}, [allRoutes]);

useEffect(() => {
const routeMap: Record<string, boolean> = defaultRoutes.reduce(
(a, b) => ({ ...a, [b.path]: b.show }),
{}
);
ipcRenderer
.invoke('get-store', { key: 'left-menu' })
.then((map) => {
if (map) {
const routeList = defaultRoutes.map((r) => ({
...r,
show:
map[r.path] === true || map[r.path] === false
? map[r.path]
: routeMap[r.path],
}));
setAllRoutes(routeList);
}
return null;
})
.catch(console.error);
}, []);

return (
<div className="absolute inset-0 flex flex-col overflow-hidden">
<main className="relative flex flex-1 min-h-0">
{/* Left sidebar */}
<nav className="flex flex-col flex-shrink-0 w-1/4 overflow-x-hidden overflow-y-auto bg-gray-300">
{/* Search */}
<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">
<FontAwesomeIcon icon="search" />
<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">
<FontAwesomeIcon icon="search" className="mr-1" />
<input
type="text"
className="w-full p-1 bg-gray-200 border-none rounded-r-md focus:ring-0"
Expand All @@ -148,8 +210,17 @@ const Main = () => {
<FontAwesomeIcon
icon="times-circle"
onClick={() => setSearch('')}
className="mr-2 cursor-pointer"
/>
)}
<FontAwesomeIcon
icon={editMenu ? 'check' : 'sliders-h'}
onClick={() => setEditMenu(!editMenu)}
className={classNames({
'text-gray-400 cursor-pointer hover:text-gray-600': true,
'text-blue-500 hover:text-blue-600': editMenu,
})}
/>
</div>

<div
Expand All @@ -158,24 +229,42 @@ const Main = () => {
aria-orientation="horizontal"
aria-labelledby="options-menu"
>
{routes.map(({ path, name, icon }) => (
<NavLink
to={path}
{routes.map(({ path, name, icon, show }) => (
<section
key={path}
className="flex items-center justify-start px-3 py-1 mb-1 space-x-1 rounded-lg"
activeClassName="bg-blue-400 text-white"
className="flex items-center justify-between space-x-2"
>
<span className="w-6">{icon}</span>
{name}
</NavLink>
<NavLink
to={path}
className="flex items-center justify-start flex-1 px-3 py-1 mb-1 space-x-1 rounded-lg"
activeClassName="bg-blue-400 text-white"
>
<span className="w-6">{icon}</span>
{name}
</NavLink>
{editMenu && (
<input
type="checkbox"
checked={show}
onChange={() =>
setAllRoutes(
allRoutes.map((r) =>
r.path === path ? { ...r, show: !show } : r
)
)
}
className="w-4 h-4 rounded cursor-pointer"
/>
)}
</section>
))}
</div>
</nav>

{/* Main content */}
<section className="relative flex flex-col w-3/4 bg-gray-200">
<div className="h-full px-6 my-6 overflow-x-hidden overflow-y-auto">
{defaultRoutes.map(({ path, name, Component }) => (
{allRoutes.map(({ path, name, Component }) => (
<Route key={path} exact path={path}>
<Component />
<Helmet>
Expand Down
6 changes: 5 additions & 1 deletion src/helpers/fontAwesome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
faCheckCircle,
faRobot,
faRetweet,
faSlidersH,
faCheck,
} from '@fortawesome/free-solid-svg-icons';

library.add(
Expand All @@ -42,5 +44,7 @@ library.add(
faCheckCircle,
faRobot,
faRetweet,
faJs
faJs,
faSlidersH,
faCheck
);
10 changes: 6 additions & 4 deletions src/main.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ import MenuBuilder from './menu';

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

const store = new Store({
hotkey: String,
});
const store = new Store();

export default class AppUpdater {
constructor() {
Expand Down Expand Up @@ -218,10 +216,14 @@ ipcMain.handle(
}
);

ipcMain.handle('get-store', (_event, { key }) => {
ipcMain.handle('get-store', async (_event, { key }) => {
return store.get(key);
});

ipcMain.handle('set-store', async (_event, { key, value }) => {
return store.set(key, value);
});

/**
* Add event listeners...
*/
Expand Down