Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dashboard] Env Variables & Misc Fixes #3581

Merged
merged 6 commits into from
Mar 26, 2021
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
4 changes: 2 additions & 2 deletions components/dashboard/README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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`.
Global state is passed through `React.Context`.
5 changes: 3 additions & 2 deletions components/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function App() {
<React.Fragment>
<Route path={["/", "/workspaces"]} exact render={
() => <Workspaces />} />
<Route path="/account" exact component={Account} />
<Route path={["/account", "/settings"]} exact component={Account} />
<Route path={["/integrations", "/access-control"]} exact component={Integrations} />
<Route path="/notifications" exact component={Notifications} />
<Route path="/plans" exact component={Plans} />
Expand All @@ -102,7 +102,8 @@ const renderMenu = () => (
},
{
title: 'Settings',
link: '/account'
link: '/settings',
matches: /^(?!.*workspace).*$/
},
]}
right={[
Expand Down
74 changes: 44 additions & 30 deletions components/dashboard/src/components/ContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* See License-AGPL.txt in the project root for license information.
*/

import { MouseEvent, useState } from 'react';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';

export interface ContextMenuProps {
children: React.ReactChild[] | React.ReactChild;
Expand All @@ -20,8 +21,9 @@ export interface ContextMenuEntry {
*/
separator?: boolean;
customFontStyle?: string;
onClick?: (event: MouseEvent)=>void;
onClick?: (event: React.MouseEvent) => void;
href?: string;
link?: string;
}

function ContextMenu(props: ContextMenuProps) {
Expand All @@ -30,23 +32,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"

Expand All @@ -56,26 +61,35 @@ function ContextMenu(props: ContextMenuProps) {
<div className="relative cursor-pointer">
<div onClick={(e) => {
toggleExpanded();
e.preventDefault();
e.stopPropagation();
}}>
{props.children}
</div>
{expanded?
{expanded ?
<div className={`mt-2 z-50 ${props.width || 'w-48'} bg-white absolute right-0 flex flex-col border border-gray-200 rounded-lg truncated`}>
{enhancedEntries.map((e, index) => {
const clickable = e.href || e.onClick;
const entry = <div key={e.title} className={`px-4 flex py-3 ${clickable?'hover:bg-gray-200':''} text-sm leading-1 ${e.customFontStyle || font} ${e.separator? ' border-b border-gray-200':''}`} >
<div>{e.title}</div><div className="flex-1"></div>{e.active ? <div className="pl-1 font-semibold">&#x2713;</div>: null}
{props.menuEntries.map((e, index) => {
const clickable = e.href || e.onClick || e.link;
const entry = <div className={`px-4 flex py-3 ${clickable ? 'hover:bg-gray-200' : ''} text-sm leading-1 ${e.customFontStyle || font} ${e.separator ? ' border-b border-gray-200' : ''}`} >
<div>{e.title}</div><div className="flex-1"></div>{e.active ? <div className="pl-1 font-semibold">&#x2713;</div> : null}
</div>
if (!clickable) {
return entry;
const key = `entry-${menuId}-${index}-${e.title}`;
if (e.link) {
return <Link key={key} to={e.link} onClick={e.onClick}>
{entry}
</Link>;
} else if (e.href) {
return <a key={key} href={e.href} onClick={e.onClick}>
{entry}
</a>;
} else {
return <div key={key} onClick={e.onClick}>
{entry}
</div>
}
return <a key={`entry-${menuId}-${index}-${e.title}`} href={e.href} onClick={e.onClick}>
{entry}
</a>

})}
</div>
:
:
null
}
</div>
Expand Down
34 changes: 11 additions & 23 deletions components/dashboard/src/components/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
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()) {
const location = useLocation();
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";
} else {
classes += " text-gray-600 hover:bg-gray-100 ";
Expand All @@ -38,35 +41,20 @@ function Menu(props: { left: Entry[], right: Entry[] }) {

return (
<header className="lg:px-28 px-10 bg-white flex flex-wrap items-center py-4">
<style dangerouslySetInnerHTML={{
__html: `
#menu-toggle:checked+#menu {
display: block;
}
`}} />
<div className="flex justify-between items-center pr-3">
<Link to="/">
<img src={images.gitpodIcon} className="h-6" />
</Link>
</div>
<div className="lg:hidden flex-grow" />
<label htmlFor="menu-toggle" className="pointer-cursor lg:hidden block">
<svg className="fill-current text-gray-700"
xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<title>menu</title>
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path>
</svg>
</label>
<input className="hidden" type="checkbox" id="menu-toggle" />
<div className="hidden lg:flex lg:flex-1 lg:items-center lg:w-auto w-full" id="menu">
<nav className="lg:flex-1">
<ul className="lg:flex lg:flex-1 items-center justify-between text-base text-gray-700 space-x-2">
<div className="flex flex-1 items-center w-auto w-full" id="menu">
<nav className="flex-1">
<ul className="flex flex-1 items-center justify-between text-base text-gray-700 space-x-2">
{props.left.map(MenuItem)}
<li className="flex-1"></li>
{props.right.map(MenuItem)}
</ul>
</nav>
<div className="lg:ml-3 flex items-center justify-start lg:mb-0 mb-4 pointer-cursor m-l-auto rounded-full border-2 border-white hover:border-gray-200 p-0.5 font-medium">
<div className="ml-3 flex items-center justify-start mb-0 pointer-cursor m-l-auto rounded-full border-2 border-white hover:border-gray-200 p-0.5 font-medium">
<ContextMenu menuEntries={[
{
title: (user && User.getPrimaryEmail(user)) || '',
Expand All @@ -75,7 +63,7 @@ function Menu(props: { left: Entry[], right: Entry[] }) {
},
{
title: 'Settings',
href: '/settings',
link: '/settings',
separator: true
},
{
Expand Down
50 changes: 23 additions & 27 deletions components/dashboard/src/components/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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 (
<div className="fixed top-0 left-0 bg-black bg-opacity-70 z-50 w-screen h-screen" onClick={close}>
<div className="fixed top-0 left-0 bg-black bg-opacity-70 z-50 w-screen h-screen" onClick={props.onClose}>
<div className="w-screen h-screen align-middle" style={{display: 'table-cell'}}>
<div className={"relative bg-white border rounded-xl p-6 max-w-lg mx-auto text-gray-600" + props.className} onClick={e => e.stopPropagation()}>
{props.closeable !== false && (
<div className="absolute right-7 top-6 cursor-pointer hover:bg-gray-200 rounded-md p-2" onClick={close}>
<div className="absolute right-7 top-6 cursor-pointer hover:bg-gray-200 rounded-md p-2" onClick={props.onClose}>
<svg version="1.1" width="14px" height="14px"
viewBox="0 0 100 100">
<line x1="0" y1="0" x2="100" y2="100" stroke="currentColor" strokeWidth="10px" />
Expand Down
2 changes: 1 addition & 1 deletion components/dashboard/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
}

input[type=text] {
@apply text-xs block w-56 text-sm text-gray-600 rounded-md focus:border-gray-300 focus:bg-white focus:ring-0;
@apply text-xs block w-56 text-sm text-gray-600 rounded-md border-2 border-gray-400 focus:border-gray-500 focus:bg-white focus:ring-0;
}

input[type=text]::placeholder {
Expand Down
Loading