From 8098e7f01dc66bc194840018957ffa3b4e8cdf9b Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Thu, 25 Mar 2021 04:24:11 +0100 Subject: [PATCH 1/6] Update README.md --- components/dashboard/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/dashboard/README.md b/components/dashboard/README.md index f33fbab9b2516b..aad6079c3bda1b 100644 --- a/components/dashboard/README.md +++ b/components/dashboard/README.md @@ -1,4 +1,4 @@ -# Dashboard Redesign POC +# Dashboard The dashboard is written in TypeScript and React. For styling it uses TailwindCSS which is a bit nicer than inlining CSS as it supports pseudo classes and a is a little more abstract/reusable. @@ -28,4 +28,4 @@ const FeaturePreview = React.lazy(() => import('./settings/FeaturePreview')); const GitIntegration = React.lazy(() => import('./settings/GitIntegration')); ``` -Global state is passed through `React.Context`. \ No newline at end of file +Global state is passed through `React.Context`. From 58c72ac51b67fab58a6d2b266e6b7d973bfbc187 Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Thu, 25 Mar 2021 04:52:34 +0000 Subject: [PATCH 2/6] [dashboard] fixed and simplified modals and context menus --- .../dashboard/src/components/ContextMenu.tsx | 43 ++++++++-------- components/dashboard/src/components/Modal.tsx | 50 +++++++++---------- .../dashboard/src/workspaces/Workspaces.tsx | 17 ++++--- 3 files changed, 57 insertions(+), 53 deletions(-) diff --git a/components/dashboard/src/components/ContextMenu.tsx b/components/dashboard/src/components/ContextMenu.tsx index dd5b3426ee25f0..3e87e42b545ef3 100644 --- a/components/dashboard/src/components/ContextMenu.tsx +++ b/components/dashboard/src/components/ContextMenu.tsx @@ -4,7 +4,7 @@ * See License-AGPL.txt in the project root for license information. */ -import { MouseEvent, useState } from 'react'; +import { useEffect, useState } from 'react'; export interface ContextMenuProps { children: React.ReactChild[] | React.ReactChild; @@ -20,7 +20,7 @@ export interface ContextMenuEntry { */ separator?: boolean; customFontStyle?: string; - onClick?: (event: MouseEvent)=>void; + onClick?: (event: React.MouseEvent)=>void; href?: string; } @@ -30,23 +30,26 @@ function ContextMenu(props: ContextMenuProps) { setExpanded(!expanded); } - if (expanded) { - // HACK! I want to skip the bubbling phase of the current click - setTimeout(() => { - window.addEventListener('click', () => setExpanded(false), { once: true }); - }, 0); - } - - const enhancedEntries = props.menuEntries.map(e => { - return { - ... e, - onClick: (event: MouseEvent) => { - e.onClick && e.onClick(event); - toggleExpanded(); - event.preventDefault(); - } + const handler = (evt: KeyboardEvent) => { + if (evt.key === 'Escape') { + setExpanded(false); } - }) + } + + const clickHandler = (evt: MouseEvent) => { + setExpanded(false); + } + + useEffect(() => { + window.addEventListener('keydown', handler); + window.addEventListener('click', clickHandler); + // Remove event listeners on cleanup + return () => { + window.removeEventListener('keydown', handler); + window.removeEventListener('click', clickHandler); + }; + }, []); // Empty array ensures that effect is only run on mount and unmount + const font = "text-gray-600 hover:text-gray-800" @@ -56,13 +59,13 @@ function ContextMenu(props: ContextMenuProps) {
{ toggleExpanded(); - e.preventDefault(); + e.stopPropagation(); }}> {props.children}
{expanded?
- {enhancedEntries.map((e, index) => { + {props.menuEntries.map((e, index) => { const clickable = e.href || e.onClick; const entry =
{e.title}
{e.active ?
: null} diff --git a/components/dashboard/src/components/Modal.tsx b/components/dashboard/src/components/Modal.tsx index d4584250160321..7caedeb32ee3ad 100644 --- a/components/dashboard/src/components/Modal.tsx +++ b/components/dashboard/src/components/Modal.tsx @@ -4,7 +4,6 @@ * See License-AGPL.txt in the project root for license information. */ -import { Disposable, DisposableCollection } from "@gitpod/gitpod-protocol"; import { useEffect } from "react"; export default function Modal(props: { @@ -15,43 +14,40 @@ export default function Modal(props: { onClose: () => void, onEnter?: () => boolean }) { - const disposable = new DisposableCollection(); - const close = () => { - disposable.dispose(); - props.onClose(); - } - useEffect(() => { - if (!props.visible) { - return; + + const handler = (evt: KeyboardEvent) => { + if (evt.key === 'Escape') { + props.onClose(); } - const keyHandler = (k: globalThis.KeyboardEvent) => { - if (k.eventPhase === 1 /* CAPTURING */) { - if (k.key === 'Escape') { - close(); - } - if (k.key === 'Enter') { - if (props.onEnter) { - if (props.onEnter() === false) { - return; - } - } - close(); - k.stopPropagation(); + if (evt.key === 'Enter') { + if (props.onEnter) { + if (props.onEnter()) { + props.onClose(); } + } else { + props.onClose(); } } - window.addEventListener('keydown', keyHandler, { capture: true }); - disposable.push(Disposable.create(()=> window.removeEventListener('keydown', keyHandler))); - }); + } + // Add event listeners + useEffect(() => { + window.addEventListener('keydown', handler); + // Remove event listeners on cleanup + return () => { + window.removeEventListener('keydown', handler); + }; + }, []); // Empty array ensures that effect is only run on mount and unmount + if (!props.visible) { return null; } + return ( -
+
e.stopPropagation()}> {props.closeable !== false && ( -
+
diff --git a/components/dashboard/src/workspaces/Workspaces.tsx b/components/dashboard/src/workspaces/Workspaces.tsx index 6ffc947fb44e8e..8a0fd632a5c0ea 100644 --- a/components/dashboard/src/workspaces/Workspaces.tsx +++ b/components/dashboard/src/workspaces/Workspaces.tsx @@ -49,11 +49,16 @@ export class Workspaces extends React.Component this.setState({ + isTemplateModelOpen: true + }); + + protected hideStartWSModal = () => this.setState({ + isTemplateModelOpen: false + }); + render() { const wsModel = this.workspaceModel; - const toggleStartWSModal = () => this.setState({ - isTemplateModelOpen: !this.state?.isTemplateModelOpen - }); const onActive = () => wsModel!.active = true; const onAll = () => wsModel!.active = false; return <> @@ -91,7 +96,7 @@ export class Workspaces extends React.Component
{wsModel && this.state?.workspaces.length > 0 ? - + : null }
@@ -141,13 +146,13 @@ export class Workspaces extends React.Component

No Active Workspaces

Prefix any git repository URL with gitpod.io/# or start a new workspace for a recently used project. Learn more
- +
)} ({ title: r.name, From 4eb20968461313ae3a6c1669c704a711882be0e7 Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Thu, 25 Mar 2021 05:14:20 +0000 Subject: [PATCH 3/6] [dashboard] fix active menu visualization --- components/dashboard/src/App.tsx | 5 +++-- components/dashboard/src/components/Menu.tsx | 7 +++++-- components/dashboard/src/settings/SettingsPage.tsx | 6 ++++-- components/dashboard/src/settings/settings-menu.ts | 12 ++++++------ 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/components/dashboard/src/App.tsx b/components/dashboard/src/App.tsx index 1b402259b929bd..88b68ed9ea765f 100644 --- a/components/dashboard/src/App.tsx +++ b/components/dashboard/src/App.tsx @@ -75,7 +75,7 @@ function App() { } /> - + @@ -102,7 +102,8 @@ const renderMenu = () => ( }, { title: 'Settings', - link: '/account' + link: '/settings', + matches: /^(?!.*workspace).*$/ }, ]} right={[ diff --git a/components/dashboard/src/components/Menu.tsx b/components/dashboard/src/components/Menu.tsx index 99ab069195a1f6..fb255721f4719f 100644 --- a/components/dashboard/src/components/Menu.tsx +++ b/components/dashboard/src/components/Menu.tsx @@ -11,13 +11,16 @@ import { gitpodHostUrl } from "../service/service"; import { UserContext } from "../user-context"; import ContextMenu from "./ContextMenu"; import * as images from '../images'; +import { useLocation } from "react-router"; interface Entry { - title: string, link: string + title: string, link: string, matches?: RegExp } function MenuItem(entry: Entry) { + const location = useLocation(); let classes = "flex block text-sm font-medium lg:px-3 px-0 py-1.5 rounded-md"; - if (window.location.pathname.toLowerCase() === entry.link.toLowerCase()) { + if (location.pathname.toLowerCase() === entry.link.toLowerCase() || + entry.matches && entry.matches.test(location.pathname.toLowerCase())) { classes += " bg-gray-200"; } else { classes += " text-gray-600 hover:bg-gray-100 "; diff --git a/components/dashboard/src/settings/SettingsPage.tsx b/components/dashboard/src/settings/SettingsPage.tsx index f81f64257cde05..685dc920fbac56 100644 --- a/components/dashboard/src/settings/SettingsPage.tsx +++ b/components/dashboard/src/settings/SettingsPage.tsx @@ -4,6 +4,7 @@ * See License-AGPL.txt in the project root for license information. */ +import { useLocation } from "react-router"; import { Link } from "react-router-dom"; import Header from '../components/Header'; import settingsMenu from "./settings-menu"; @@ -15,6 +16,7 @@ export interface Props { } export function SettingsPage(p: Props) { + const location = useLocation(); return
@@ -22,12 +24,12 @@ export function SettingsPage(p: Props) {
    {settingsMenu.map(e => { let classes = "flex block py-2 px-4 rounded-md"; - if (e.link.toLowerCase() === window.location.pathname) { + if (e.link.some(l => l.toLocaleLowerCase() === location.pathname)) { classes += " bg-gray-800 text-gray-50"; } else { classes += " hover:bg-gray-100"; } - return + return
  • {e.title}
  • diff --git a/components/dashboard/src/settings/settings-menu.ts b/components/dashboard/src/settings/settings-menu.ts index 6582d1cde51642..6bd77561c1e839 100644 --- a/components/dashboard/src/settings/settings-menu.ts +++ b/components/dashboard/src/settings/settings-menu.ts @@ -7,26 +7,26 @@ export default [ { title: 'Account', - link: '/account' + link: ['/account','/settings'] }, { title: 'Notifications', - link: '/notifications' + link: ['/notifications'] }, { title: 'Plans', - link: '/plans' + link: ['/plans'] }, { title: 'Variables', - link: '/variables' + link: ['/variables'] }, { title: 'Integrations', - link: '/integrations' + link: ["/integrations", "/access-control"] }, { title: 'Preferences', - link: '/preferences' + link: ['/preferences'] }, ]; \ No newline at end of file From 45e903e9def6317b84fe9d7a1c36ee6d4035bd1e Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Thu, 25 Mar 2021 05:27:14 +0000 Subject: [PATCH 4/6] [dashboard] fix avatar menu --- .../dashboard/src/components/ContextMenu.tsx | 35 ++++++++++++------- components/dashboard/src/components/Menu.tsx | 2 +- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/components/dashboard/src/components/ContextMenu.tsx b/components/dashboard/src/components/ContextMenu.tsx index 3e87e42b545ef3..edc0a6c4d4c4d3 100644 --- a/components/dashboard/src/components/ContextMenu.tsx +++ b/components/dashboard/src/components/ContextMenu.tsx @@ -5,6 +5,7 @@ */ import { useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; export interface ContextMenuProps { children: React.ReactChild[] | React.ReactChild; @@ -20,8 +21,9 @@ export interface ContextMenuEntry { */ separator?: boolean; customFontStyle?: string; - onClick?: (event: React.MouseEvent)=>void; + onClick?: (event: React.MouseEvent) => void; href?: string; + link?: string; } function ContextMenu(props: ContextMenuProps) { @@ -39,7 +41,7 @@ function ContextMenu(props: ContextMenuProps) { const clickHandler = (evt: MouseEvent) => { setExpanded(false); } - + useEffect(() => { window.addEventListener('keydown', handler); window.addEventListener('click', clickHandler); @@ -63,22 +65,31 @@ function ContextMenu(props: ContextMenuProps) { }}> {props.children}
- {expanded? + {expanded ?
{props.menuEntries.map((e, index) => { - const clickable = e.href || e.onClick; - const entry =
-
{e.title}
{e.active ?
: null} + const clickable = e.href || e.onClick || e.link; + const entry =
+
{e.title}
{e.active ?
: null}
- if (!clickable) { - return entry; + const key = `entry-${menuId}-${index}-${e.title}`; + if (e.link) { + return + {entry} + ; + } else if (e.href) { + return + {entry} + ; + } else { + return
+ {entry} +
} - return - {entry} - + })}
- : + : null }
diff --git a/components/dashboard/src/components/Menu.tsx b/components/dashboard/src/components/Menu.tsx index fb255721f4719f..7f8171d40dfe36 100644 --- a/components/dashboard/src/components/Menu.tsx +++ b/components/dashboard/src/components/Menu.tsx @@ -78,7 +78,7 @@ function Menu(props: { left: Entry[], right: Entry[] }) { }, { title: 'Settings', - href: '/settings', + link: '/settings', separator: true }, { From 9c78f87d1544f76a51e95b579a0cda0b6bd555ab Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Thu, 25 Mar 2021 05:42:33 +0000 Subject: [PATCH 5/6] [dashboard] remove responsive menu --- components/dashboard/src/components/Menu.tsx | 25 ++++---------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/components/dashboard/src/components/Menu.tsx b/components/dashboard/src/components/Menu.tsx index 7f8171d40dfe36..263774c2365f31 100644 --- a/components/dashboard/src/components/Menu.tsx +++ b/components/dashboard/src/components/Menu.tsx @@ -18,7 +18,7 @@ interface Entry { function MenuItem(entry: Entry) { const location = useLocation(); - let classes = "flex block text-sm font-medium lg:px-3 px-0 py-1.5 rounded-md"; + let classes = "flex block text-sm font-medium px-3 px-0 py-1.5 rounded-md"; if (location.pathname.toLowerCase() === entry.link.toLowerCase() || entry.matches && entry.matches.test(location.pathname.toLowerCase())) { classes += " bg-gray-200"; @@ -41,35 +41,20 @@ function Menu(props: { left: Entry[], right: Entry[] }) { return (
-