diff --git a/src/main/main.ts b/src/main/main.ts index 390cb18..7c7fd59 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -31,7 +31,7 @@ let win: BrowserWindow | null; const createWindow = () => { win = new BrowserWindow({ - width: 850, + width: 920, minWidth: 700, height: 900, titleBarStyle: 'hiddenInset', diff --git a/src/main/menu.ts b/src/main/menu.ts index 7025638..bf8fd27 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -1,5 +1,4 @@ import {app, Menu, shell} from 'electron'; -import {set} from 'mobx'; import store from 'src/shared/store'; @@ -44,12 +43,16 @@ const template: Electron.MenuItemConstructorOptions[] = [ {role: 'reload'}, {role: 'forceReload'}, {role: 'togglefullscreen'}, + { + accelerator: 'cmd + option + s', + label: 'Toggle Sidebar', + click: () => store.config.toggleSidebar(), + }, { visible: false, accelerator: 'cmd + l', label: 'Toggle UI Theme', - click: () => - set(store.config, {theme: store.config.theme === 'light' ? 'dark' : 'light'}), + click: () => store.config.toggleTheme(), }, ], }, diff --git a/src/renderer/components/Navigation.tsx b/src/renderer/components/Navigation.tsx index b3330d1..d083579 100644 --- a/src/renderer/components/Navigation.tsx +++ b/src/renderer/components/Navigation.tsx @@ -1,10 +1,13 @@ import * as React from 'react'; -import {Activity, Layers, Menu, Settings} from 'react-feather'; +import {Activity, Layers, Settings} from 'react-feather'; import {NavLink, useLocation} from 'react-router-dom'; import styled from '@emotion/styled'; -import {AnimatePresence, motion} from 'framer-motion'; +import {AnimateSharedLayout, motion} from 'framer-motion'; +import {observer} from 'mobx-react'; -import useDropdown from 'src/utils/useDropdown'; +import store from 'src/shared/store'; + +import HelpButton from './HelpButton'; const items = [ {name: 'Device Status', path: '/status', icon: Activity}, @@ -12,104 +15,64 @@ const items = [ {name: 'Settings', path: '/settings', icon: Settings}, ] as const; -const Navigation = () => { - const dropdownRef = React.useRef(null); - const actionRef = React.useRef(null); - const [isOpen, toggleDropdown] = useDropdown(dropdownRef, actionRef); - - const location = useLocation(); +const Navigation = observer(() => ( + + store.config.toggleSidebar()} /> + + {items.map(item => ( + + {item.path === useLocation().pathname && } + + {!store.config.sidebarCollapsed && item.name} + + ))} + + + + + +)); - return ( - - toggleDropdown()} ref={actionRef}> - {items.find(i => location?.pathname.startsWith(i.path))?.name} - - - - {isOpen && ( - - {items.map(item => ( - toggleDropdown()}> - - {item.name} - - ))} - - )} - - - ); -}; - -const Container = styled('div')` +const MenuContainer = styled(motion.nav)` position: relative; - font-size: 0.8rem; - font-weight: 500; + height: 100%; + border-right: 1px solid ${p => p.theme.border}; display: flex; - align-items: center; -`; - -const MenuButton = styled('button')` - border: none; - background: none; - padding: 0.5rem; - font-size: 0.7rem; - display: grid; - grid-auto-flow: column; - grid-auto-rows: max-content; - grid-gap: 0.5rem; - align-items: center; - text-transform: uppercase; - font-weight: 600; - opacity: 0.9; - - &:hover { - opacity: 1; - } + flex-direction: column; + grid-gap: 0.125rem; + padding: 0.5rem 0; `; -const MenuContainer = styled(motion.div)` +const SidebarToggle = styled('div')` position: absolute; - top: 38px; - right: -10px; - background: ${p => p.theme.background}; - display: grid; - grid-auto-flow: row; - grid-auto-rows: max-content; - grid-gap: 0.125rem; - padding: 0.25rem 0; - border: 1px solid ${p => p.theme.border}; - border-radius: 3px; + top: 0; + bottom: 0; + right: -1px; + width: 2px; + z-index: 2; + transition: background 150ms ease-in-out; + cursor: pointer; - &:before, - &:after { + &:before { content: ''; - display: block; + width: 5px; position: absolute; - top: -16px; - right: 18px; - border: 8px solid transparent; - border-bottom-color: ${p => p.theme.background}; + top: 0; + bottom: 0; + left: -1px; } - &:before { - margin-top: -1px; - border-bottom-color: ${p => p.theme.border}; + &:hover { + transition-delay: 300ms; + background: #f95757; } `; -MenuContainer.defaultProps = { - initial: {opacity: 0, y: 5, originX: '80%', originY: 0}, - animate: {opacity: 1, y: 0}, - exit: {opacity: 0, scale: 0.95}, - transition: {duration: 0.2}, -}; - const MenuItem = styled(NavLink)` + position: relative; padding: 0.375rem 0.75rem; - display: grid; - grid-template-columns: max-content 1fr; - grid-gap: 0.5rem; + display: flex; + gap: 0.5rem; align-items: center; text-transform: uppercase; font-weight: 600; @@ -123,4 +86,21 @@ const MenuItem = styled(NavLink)` } `; +const ActiveIndicator = styled(motion.div)` + position: absolute; + background: ${p => p.theme.subText}; + height: 10px; + margin: 0.125rem 0; + width: 2px; + border-radius: 2px; + left: 6px; +`; + +const Bottom = styled('div')` + display: flex; + align-items: flex-end; + flex-grow: 1; + padding: 0 0.5rem; +`; + export default Navigation; diff --git a/src/renderer/components/NetworkStatus.tsx b/src/renderer/components/NetworkStatus.tsx index 7af2831..781bbfd 100644 --- a/src/renderer/components/NetworkStatus.tsx +++ b/src/renderer/components/NetworkStatus.tsx @@ -24,7 +24,7 @@ const StatusIndicator = styled('div')<{state: NetworkState}>` padding: 0.25rem 0.5rem; font-size: 0.625rem; text-transform: uppercase; - border-radius: 2px; + border-radius: 4px; `; export default NetworkStatus; diff --git a/src/renderer/components/Titlebar.tsx b/src/renderer/components/Titlebar.tsx index da23eb6..f587cd3 100644 --- a/src/renderer/components/Titlebar.tsx +++ b/src/renderer/components/Titlebar.tsx @@ -1,27 +1,48 @@ +import React from 'react'; +import {Save} from 'react-feather'; import styled from '@emotion/styled'; -import Navigation from './Navigation'; +import useRelease from 'src/utils/useLatestRelease'; + +import ActionButton from './ActionButton'; import NetworkStatus from './NetworkStatus'; -const Toolbar = () => ( - - - - -); +const Toolbar = () => { + const latestRelease = useRelease(); + + const hasNewVersion = + latestRelease && + process.env.RELEASE_CHANNEL === 'stable' && + process.env.RELEASE !== latestRelease.name; + + return ( + + {hasNewVersion && latestRelease && ( + location.assign(latestRelease.html_url)}> + {latestRelease.name} available + + )} + {process.env.RELEASE} + + + ); +}; + +const Version = styled('div')` + align-items: center; + font-size: 0.7rem; + color: ${p => p.theme.subText}; + margin-top: 4px; +`; const Container = styled('header')` - position: sticky; z-index: 1; - top: 0; height: 36px; padding: 0 0.5rem; padding-left: 75px; - display: grid; - justify-content: end; - grid-auto-flow: column; - grid-auto-columns: max-content; - grid-gap: 0.5rem; + display: flex; + justify-content: flex-end; + gap: 0.5rem; align-items: center; background: ${p => p.theme.backgroundSecondary}; transition: background 300ms; @@ -29,4 +50,14 @@ const Container = styled('header')` -webkit-app-region: drag; `; +const NewVersionButton = styled(ActionButton)` + position: absolute; + bottom: 1rem; + right: 1rem; + background: #ef5f73; + color: #fff; + padding: 0.375rem 0.5rem; + font-size: 0.75rem; +`; + export default Toolbar; diff --git a/src/renderer/views/Application.tsx b/src/renderer/views/Application.tsx index 54b162f..2a07f30 100644 --- a/src/renderer/views/Application.tsx +++ b/src/renderer/views/Application.tsx @@ -1,31 +1,53 @@ import {MemoryRouter, Redirect, Route, Switch} from 'react-router-dom'; import styled from '@emotion/styled'; -import Footer from 'app/components/Footer'; import Titlebar from 'app/components/Titlebar'; import Devices from 'app/views/devices'; import OverlayConfig from 'app/views/overlayConfig'; import Settings from 'app/views/settings'; +import Navigation from 'src/renderer/components/Navigation'; const Application = () => ( - - - - - - - + + + + + + + + + + -