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 => (
+
+ ))}
+
+
+
+
+
+));
- return (
-
- toggleDropdown()} ref={actionRef}>
- {items.find(i => location?.pathname.startsWith(i.path))?.name}
-
-
-
- {isOpen && (
-
- {items.map(item => (
-
- ))}
-
- )}
-
-
- );
-};
-
-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 = () => (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
);
-const Frame = styled('main')`
+const Frame = styled('div')`
+ flex-grow: 1;
+ height: 0;
+ display: grid;
+ grid-template-columns: max-content 1fr;
+ grid-template-rows: max-content 1fr max-content;
+ grid-template-areas:
+ 'header header'
+ 'nav main';
+
+ > nav {
+ grid-area: nav;
+ }
+
+ > header {
+ grid-area: header;
+ }
+`;
+
+const Content = styled('main')`
+ position: relative;
display: flex;
flex-direction: column;
- flex-grow: 1;
+ overflow: scroll;
`;
export default Application;
diff --git a/src/shared/store/index.ts b/src/shared/store/index.ts
index d7dbd95..7aa1215 100644
--- a/src/shared/store/index.ts
+++ b/src/shared/store/index.ts
@@ -1,7 +1,7 @@
import {User} from '@sentry/types';
import * as ip from 'ip-address';
import {identity} from 'lodash';
-import {computed, observable, toJS} from 'mobx';
+import {action, computed, observable, toJS} from 'mobx';
import {
CDJStatus,
Device,
@@ -167,6 +167,22 @@ export class AppConfig {
@serializable
@observable
theme: 'light' | 'dark' | 'system' = 'light';
+ /**
+ * State of the sidebar
+ */
+ @serializable
+ @observable
+ sidebarCollapsed = false;
+
+ @action
+ toggleSidebar() {
+ this.sidebarCollapsed = !this.sidebarCollapsed;
+ }
+
+ @action
+ toggleTheme() {
+ this.theme = this.theme === 'light' ? 'dark' : 'light';
+ }
}
export class AppStore {