From f66483f0cfaf5f43f07dfa8579a881b80224f608 Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Thu, 18 Mar 2021 19:35:53 +0000 Subject: [PATCH 1/3] [dashboard] allow open workspace in new tab --- .../dashboard/src/components/ContextMenu.tsx | 9 +- .../src/workspaces/WorkspaceEntry.tsx | 88 ++++++++++--------- .../dashboard/src/workspaces/Workspaces.tsx | 2 +- 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/components/dashboard/src/components/ContextMenu.tsx b/components/dashboard/src/components/ContextMenu.tsx index e0eecbed25f2b9..ee30a37ff7396c 100644 --- a/components/dashboard/src/components/ContextMenu.tsx +++ b/components/dashboard/src/components/ContextMenu.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { MouseEvent, useState } from 'react'; export interface ContextMenuProps { children: React.ReactChild[] | React.ReactChild; @@ -14,7 +14,7 @@ export interface ContextMenuEntry { */ separator?: boolean; customFontStyle?: string; - onClick?: ()=>void; + onClick?: (event: MouseEvent)=>void; href?: string; } @@ -34,9 +34,10 @@ function ContextMenu(props: ContextMenuProps) { const enhancedEntries = props.menuEntries.map(e => { return { ... e, - onClick: () => { - e.onClick && e.onClick(); + onClick: (event: MouseEvent) => { + e.onClick && e.onClick(event); toggleExpanded(); + event.preventDefault(); } } }) diff --git a/components/dashboard/src/workspaces/WorkspaceEntry.tsx b/components/dashboard/src/workspaces/WorkspaceEntry.tsx index 521f6bce3862db..c484dff443862a 100644 --- a/components/dashboard/src/workspaces/WorkspaceEntry.tsx +++ b/components/dashboard/src/workspaces/WorkspaceEntry.tsx @@ -9,7 +9,7 @@ import { MouseEvent, useState } from 'react'; import { WorkspaceModel } from './workspace-model'; -export function WorkspaceEntry({desc, model}: {desc: WorkspaceInfo, model: WorkspaceModel}) { +export function WorkspaceEntry({ desc, model }: { desc: WorkspaceInfo, model: WorkspaceModel }) { const [isModalVisible, setModalVisible] = useState(false); const [isChangesModalVisible, setChangesModalVisible] = useState(false); const state: WorkspaceInstancePhase = desc.latestInstance?.status?.phase || 'stopped'; @@ -46,14 +46,18 @@ export function WorkspaceEntry({desc, model}: {desc: WorkspaceInfo, model: Works pathname: '/start/', hash: '#' + ws.id }); - const downloadURL = new GitpodHostUrl(window.location.href).with({ - pathname: `/workspace-download/get/${ws.id}` + const downloadURL = new GitpodHostUrl(window.location.href).with({ + pathname: `/workspace-download/get/${ws.id}` }).toString(); const menuEntries: ContextMenuEntry[] = [ { title: 'Open', href: startUrl.toString() }, + { + title: 'Stop', + onClick: () => getGitpodService().server.stopWorkspace(ws.id) + }, { title: 'Download', href: downloadURL @@ -82,50 +86,50 @@ export function WorkspaceEntry({desc, model}: {desc: WorkspaceInfo, model: Works } ]; const project = getProject(ws); - const startWsOnClick = (event: MouseEvent) => { - window.location.href = startUrl.toString(); - } const showChanges = (event: MouseEvent) => { + event.preventDefault(); setChangesModalVisible(true); } - return
-
-
-   + return
+ +
+
+   +
+
+
-
-
-
{ws.id}
-
{project || 'Unknown'}
-
-
-
-
{ws.description}
-
{ws.contextURL}
+
+
+
{ws.description}
+
{ws.contextURL}
+
+
+
0 ? showChanges : undefined}> +
+
{currentBranch}
+ { + numberOfChanges > 0 ? +
{changesLabel}
+ : +
No Changes
+ } +
-
-
0 ? showChanges: startWsOnClick}> -
-
{currentBranch}
- { - numberOfChanges > 0 ? -
{changesLabel}
- : -
No Changes
- } - setChangesModalVisible(false)}> - {getChangesPopup(pendingChanges)} - +
+
{moment(WorkspaceInfo.lastActiveISODate(desc)).fromNow()}
-
-
-
{moment(WorkspaceInfo.lastActiveISODate(desc)).fromNow()}
-
-
- - Actions - -
+
+ + Actions + +
+ + setChangesModalVisible(false)}> + {getChangesPopup(pendingChanges)} + setModalVisible(false)}>

Delete {ws.id}

@@ -135,7 +139,7 @@ export function WorkspaceEntry({desc, model}: {desc: WorkspaceInfo, model: Works
diff --git a/components/dashboard/src/workspaces/Workspaces.tsx b/components/dashboard/src/workspaces/Workspaces.tsx index bb77d05efb28b4..fff7ddcf6e98f9 100644 --- a/components/dashboard/src/workspaces/Workspaces.tsx +++ b/components/dashboard/src/workspaces/Workspaces.tsx @@ -89,7 +89,7 @@ export class Workspaces extends React.ComponentName
Context
Pending Changes
-
Last Active
+
Last Start
{ From a40685451b2c46535f61389799e704f57056a017 Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Thu, 18 Mar 2021 19:42:32 +0000 Subject: [PATCH 2/3] [dashboard] fix deletion of workspaces --- .../src/workspaces/WorkspaceEntry.tsx | 13 ++++++++----- .../src/workspaces/workspace-model.ts | 18 ++++++++++++------ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/components/dashboard/src/workspaces/WorkspaceEntry.tsx b/components/dashboard/src/workspaces/WorkspaceEntry.tsx index c484dff443862a..1d46431a01a0d8 100644 --- a/components/dashboard/src/workspaces/WorkspaceEntry.tsx +++ b/components/dashboard/src/workspaces/WorkspaceEntry.tsx @@ -53,11 +53,14 @@ export function WorkspaceEntry({ desc, model }: { desc: WorkspaceInfo, model: Wo { title: 'Open', href: startUrl.toString() - }, - { + }]; + if (state === 'running') { + menuEntries.push({ title: 'Stop', onClick: () => getGitpodService().server.stopWorkspace(ws.id) - }, + }); + } + menuEntries.push( { title: 'Download', href: downloadURL @@ -84,7 +87,7 @@ export function WorkspaceEntry({ desc, model }: { desc: WorkspaceInfo, model: Wo setModalVisible(true); } } - ]; + ); const project = getProject(ws); const showChanges = (event: MouseEvent) => { event.preventDefault(); @@ -139,7 +142,7 @@ export function WorkspaceEntry({ desc, model }: { desc: WorkspaceInfo, model: Wo
diff --git a/components/dashboard/src/workspaces/workspace-model.ts b/components/dashboard/src/workspaces/workspace-model.ts index fad617b2ce5ec1..aab4627f36d47a 100644 --- a/components/dashboard/src/workspaces/workspace-model.ts +++ b/components/dashboard/src/workspaces/workspace-model.ts @@ -2,7 +2,7 @@ import { Disposable, DisposableCollection, GitpodClient, WorkspaceInfo, Workspac import { getGitpodService } from "../service/service"; export class WorkspaceModel implements Disposable, Partial { - + protected workspaces = new Map(); protected currentlyFetching = new Set(); protected disposables = new DisposableCollection(); @@ -15,7 +15,7 @@ export class WorkspaceModel implements Disposable, Partial { constructor(protected setWorkspaces: (ws: WorkspaceInfo[]) => void) { this.internalRefetch(); } - + protected internalRefetch() { this.disposables.dispose(); this.disposables = new DisposableCollection(); @@ -27,17 +27,17 @@ export class WorkspaceModel implements Disposable, Partial { }); this.disposables.push(getGitpodService().registerClient(this)); } - + protected updateMap(workspaces: WorkspaceInfo[]) { for (const ws of workspaces) { this.workspaces.set(ws.workspace.id, ws); } } - + dispose(): void { this.disposables.dispose(); } - + async onInstanceUpdate(instance: WorkspaceInstance) { if (this.workspaces) { if (this.workspaces.has(instance.workspaceId)) { @@ -58,7 +58,13 @@ export class WorkspaceModel implements Disposable, Partial { } } } - + + async deleteWorkspace(id: string): Promise { + await getGitpodService().server.deleteWorkspace(id); + this.workspaces.delete(id); + this.notifyWorkpaces(); + } + protected internalActive = true; get active() { return this.internalActive; From 47aa569e8298106d2cc782de41d436ebf665fcfb Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Fri, 19 Mar 2021 10:56:04 +0000 Subject: [PATCH 3/3] [dashboard] improve modal the modal dialog should - close on ESC - close on click outside modal but not within - handle enter --- components/dashboard/src/components/Modal.tsx | 42 ++++++++++++++++--- .../src/workspaces/WorkspaceEntry.tsx | 17 ++++---- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/components/dashboard/src/components/Modal.tsx b/components/dashboard/src/components/Modal.tsx index ef01d24f4bcb85..0023e1bde4423c 100644 --- a/components/dashboard/src/components/Modal.tsx +++ b/components/dashboard/src/components/Modal.tsx @@ -1,27 +1,57 @@ +import { Disposable, DisposableCollection } from "@gitpod/gitpod-protocol"; +import { useEffect } from "react"; + export default function Modal(props: { children: React.ReactChild[] | React.ReactChild, visible: boolean, closeable?: boolean, className?: string, - onClose: () => void + onClose: () => void, + onEnter?: () => boolean }) { + const disposable = new DisposableCollection(); + const close = () => { + disposable.dispose(); + props.onClose(); + } + useEffect(() => { + if (!props.visible) { + return; + } + 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(); + } + } + } + window.addEventListener('keydown', keyHandler, { capture: true }); + disposable.push(Disposable.create(()=> window.removeEventListener('keydown', keyHandler))); + }); if (!props.visible) { return null; } - setTimeout(() => window.addEventListener('click', props.onClose, { once: true }), 0); return ( -
+
-
+
e.stopPropagation()}> {props.closeable !== false && ( -
+
- )} {props.children}
diff --git a/components/dashboard/src/workspaces/WorkspaceEntry.tsx b/components/dashboard/src/workspaces/WorkspaceEntry.tsx index 1d46431a01a0d8..1af14ec22c816c 100644 --- a/components/dashboard/src/workspaces/WorkspaceEntry.tsx +++ b/components/dashboard/src/workspaces/WorkspaceEntry.tsx @@ -133,17 +133,20 @@ export function WorkspaceEntry({ desc, model }: { desc: WorkspaceInfo, model: Wo setChangesModalVisible(false)}> {getChangesPopup(pendingChanges)} - setModalVisible(false)}> + setModalVisible(false)} onEnter={() => {model.deleteWorkspace(ws.id); return true;}}>
-

Delete {ws.id}

-
-

Do you really want to delete this workspace?

+

Delete Workspace

+
+

Are you sure you want to delete this workspace?

+
+

{ws.id}

+

{ws.description}

+
-
-
+