Skip to content

[dashboard] Add ItemsList component #4454

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

Merged
merged 2 commits into from
Jun 10, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.

## June 2021

- Adding `ItemsList` component as a more maintainable and consistent way to render a list of workspaces, git integrations, environment variables, etc. ([#4454](https://github.com/gitpod-io/gitpod/pull/4454))
- Improve backup stability when pods get evicted ([#4405](https://github.com/gitpod-io/gitpod/pull/4405))
- Fix text color in workspaces list for dark theme ([#4410](https://github.com/gitpod-io/gitpod/pull/4410))
- Better reflect incremental prebuilds in prebuilt workspace logs ([#4293](https://github.com/gitpod-io/gitpod/pull/4293))
Expand Down
7 changes: 5 additions & 2 deletions components/dashboard/src/components/ContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';

export interface ContextMenuProps {
children: React.ReactChild[] | React.ReactChild;
children?: React.ReactChild[] | React.ReactChild;
menuEntries: ContextMenuEntry[];
width?: string;
}
Expand Down Expand Up @@ -57,13 +57,16 @@ function ContextMenu(props: ContextMenuProps) {

const menuId = String(Math.random());

// Default 'children' is the three dots hamburger button.
const children = props.children || <svg className="w-8 h-8 p-1 text-gray-600 dark:text-gray-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Actions</title><g fill="currentColor" transform="rotate(90 12 12)"><circle cx="1" cy="1" r="2" transform="translate(5 11)" /><circle cx="1" cy="1" r="2" transform="translate(11 11)" /><circle cx="1" cy="1" r="2" transform="translate(17 11)" /></g></svg>;

return (
<div className="relative cursor-pointer">
<div onClick={(e) => {
toggleExpanded();
e.stopPropagation();
}}>
{props.children}
{children}
</div>
{expanded ?
<div className={`mt-2 z-50 ${props.width || 'w-48'} bg-white dark:bg-gray-900 absolute right-0 flex flex-col border border-gray-200 dark:border-gray-800 rounded-lg truncated`}>
Expand Down
55 changes: 55 additions & 0 deletions components/dashboard/src/components/ItemsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License-AGPL.txt in the project root for license information.
*/

import ContextMenu, { ContextMenuEntry } from "./ContextMenu"

export function ItemsList(props: {
children?: React.ReactNode;
className?: string;
}) {
return <div className={`flex flex-col space-y-2 ${props.className || ""}`}>
{props.children}
</div>;
}

export function Item(props: {
children?: React.ReactNode;
className?: string;
header?: boolean;
}) {
const headerClassName = "text-sm text-gray-400 border-t border-b border-gray-200 dark:border-gray-800";
const notHeaderClassName = "rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gitpod-kumquat-light";
return <div className={`flex flex-grow flex-row w-full px-3 py-3 justify-between transition ease-in-out group ${props.header ? headerClassName : notHeaderClassName} ${props.className || ""}`}>
{props.children}
</div>;
}

export function ItemField(props: {
children?: React.ReactNode;
className?: string;
}) {
return <div className={`flex-grow my-auto mx-1 ${props.className || ""}`}>
{props.children}
</div>;
}

export function ItemFieldIcon(props: {
children?: React.ReactNode;
className?: string;
}) {
return <div className={`flex self-center w-8 ${props.className || ""}`}>
{props.children}
</div>;
}

export function ItemFieldContextMenu(props: {
menuEntries?: ContextMenuEntry[];
className?: string;
}) {
return <div className={`flex self-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md cursor-pointer w-8 ${props.className || ""}`}>
{!props.menuEntries ? null : <ContextMenu menuEntries={props.menuEntries} />}
</div>;
}
70 changes: 31 additions & 39 deletions components/dashboard/src/settings/EnvironmentVariables.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
*/

import { UserEnvVarValue } from "@gitpod/gitpod-protocol";
import { useEffect, useRef, useState } from "react";
import ContextMenu from "../components/ContextMenu";
import React, { useEffect, useRef, useState } from "react";
import ConfirmationModal from "../components/ConfirmationModal";
import { Item, ItemField, ItemFieldContextMenu, ItemsList } from "../components/ItemsList";
import Modal from "../components/Modal";
import { getGitpodService } from "../service/service";
import { PageWithSubMenu } from "../components/PageWithSubMenu";
import { getGitpodService } from "../service/service";
import settingsMenu from "./settings-menu";
import ConfirmationModal from "../components/ConfirmationModal";

interface EnvVarModalProps {
envVar: UserEnvVarValue;
Expand Down Expand Up @@ -180,6 +180,8 @@ export default function EnvVars() {
return '';
};



return <PageWithSubMenu subMenu={settingsMenu} title='Variables' subtitle='Configure environment variables for all workspaces.'>
{isAddEnvVarModalVisible && <AddEnvVarModal
save={save}
Expand Down Expand Up @@ -209,41 +211,31 @@ export default function EnvVars() {
<button onClick={add}>New Variable</button>
</div>
</div>
: <div className="space-y-2">
<div className="flex flex-col space-y-2">
<div className="px-3 py-3 flex justify-between space-x-2 text-sm text-gray-400 border-t border-b border-gray-200 dark:border-gray-800">
<div className="w-5/12">Name</div>
<div className="w-5/12">Scope</div>
<div className="w-2/12"></div>
</div>
</div>
<div className="flex flex-col">
{envVars.map(variable => {
return <div className="rounded-xl whitespace-nowrap flex space-x-2 py-3 px-3 w-full justify-between hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gitpod-kumquat-light transition ease-in-out group">
<div className="w-5/12 m-auto overflow-ellipsis truncate">{variable.name}</div>
<div className="w-5/12 m-auto overflow-ellipsis truncate text-sm text-gray-400">{variable.repositoryPattern}</div>
<div className="w-2/12 flex justify-end">
<div className="flex w-8 self-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md cursor-pointer opacity-0 group-hover:opacity-100">
<ContextMenu menuEntries={[
{
title: 'Edit',
onClick: () => edit(variable),
separator: true
},
{
title: 'Delete',
customFontStyle: 'text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300',
onClick: () => confirmDeleteVariable(variable)
},
]}>
<svg className="w-8 h-8 p-1 text-gray-600 dark:text-gray-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Actions</title><g fill="currentColor" transform="rotate(90 12 12)"><circle cx="1" cy="1" r="2" transform="translate(5 11)"/><circle cx="1" cy="1" r="2" transform="translate(11 11)"/><circle cx="1" cy="1" r="2" transform="translate(17 11)"/></g></svg>
</ContextMenu>
</div>
</div>
</div>
})}
</div>
</div>
: <ItemsList>
<Item header={true}>
<ItemField className="w-5/12">Name</ItemField>
<ItemField className="w-5/12">Scope</ItemField>
<ItemFieldContextMenu />
</Item>
{envVars.map(variable => {
return <Item className="whitespace-nowrap">
<ItemField className="w-5/12 overflow-ellipsis truncate">{variable.name}</ItemField>
<ItemField className="w-5/12 overflow-ellipsis truncate text-sm text-gray-400">{variable.repositoryPattern}</ItemField>
<ItemFieldContextMenu menuEntries={[
{
title: 'Edit',
onClick: () => edit(variable),
separator: true
},
{
title: 'Delete',
customFontStyle: 'text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300',
onClick: () => confirmDeleteVariable(variable)
},
]} />
</Item>
})}
</ItemsList>
}
</PageWithSubMenu>;
}
86 changes: 37 additions & 49 deletions components/dashboard/src/settings/Integrations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@
import { AuthProviderEntry, AuthProviderInfo } from "@gitpod/gitpod-protocol";
import { SelectAccountPayload } from "@gitpod/gitpod-protocol/lib/auth";
import React, { useContext, useEffect, useState } from "react";
import ContextMenu, { ContextMenuEntry } from "../components/ContextMenu";
import { getGitpodService, gitpodHostUrl } from "../service/service";
import { UserContext } from "../user-context";
import copy from '../images/copy.svg';
import exclamation from '../images/exclamation.svg';
import AlertBox from "../components/AlertBox";
import Modal from "../components/Modal";
import { openAuthorizeWindow } from "../provider-utils";
import CheckBox from '../components/CheckBox';
import ConfirmationModal from "../components/ConfirmationModal";
import { ContextMenuEntry } from "../components/ContextMenu";
import { Item, ItemField, ItemFieldContextMenu, ItemFieldIcon, ItemsList } from "../components/ItemsList";
import Modal from "../components/Modal";
import { PageWithSubMenu } from "../components/PageWithSubMenu";
import settingsMenu from "./settings-menu";
import copy from '../images/copy.svg';
import exclamation from '../images/exclamation.svg';
import { openAuthorizeWindow } from "../provider-utils";
import { getGitpodService, gitpodHostUrl } from "../service/service";
import { UserContext } from "../user-context";
import { SelectAccountModal } from "./SelectAccountModal";
import ConfirmationModal from "../components/ConfirmationModal";
import settingsMenu from "./settings-menu";

export default function Integrations() {

Expand Down Expand Up @@ -280,36 +281,30 @@ function GitProviders() {

<h3>Git Providers</h3>
<h2>Manage permissions for git providers.</h2>
<div className="flex flex-col pt-6 space-y-2">
<ItemsList className="pt-6">
{authProviders && authProviders.map(ap => (
<div key={"ap-" + ap.authProviderId} className="flex-grow flex flex-row hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl h-16 w-full transition ease-in-out group">
<div className="px-4 self-center w-1/12">
<div className={"rounded-full w-3 h-3 text-sm align-middle " + (isConnected(ap.authProviderId) ? "bg-green-500" : "bg-gray-400")}>
<Item key={"ap-" + ap.authProviderId} className="h-16">
<ItemFieldIcon>
<div className={"rounded-full w-3 h-3 text-sm align-middle m-auto " + (isConnected(ap.authProviderId) ? "bg-green-500" : "bg-gray-400")}>
&nbsp;
</div>
</div>
<div className="p-0 my-auto flex flex-col xl:w-3/12 w-4/12">
</ItemFieldIcon>
<ItemField className="w-4/12 xl:w-3/12 flex flex-col">
<span className="my-auto font-medium truncate overflow-ellipsis">{ap.authProviderType}</span>
<span className="text-sm my-auto text-gray-400 truncate overflow-ellipsis">{ap.host}</span>
</div>
<div className="p-0 my-auto flex flex-col xl:w-2/12 w-6/12">
</ItemField>
<ItemField className="w-6/12 xl:w-3/12 flex flex-col">
<span className="my-auto truncate text-gray-500 overflow-ellipsis">{getUsername(ap.authProviderId) || "–"}</span>
<span className="text-sm my-auto text-gray-400">Username</span>
</div>
<div className="p-0 my-auto hidden xl:flex xl:flex-col xl:w-5/12">
</ItemField>
<ItemField className="hidden xl:w-5/12 xl:flex xl:flex-col">
<span className="my-auto truncate text-gray-500 overflow-ellipsis">{getPermissions(ap.authProviderId)?.join(", ") || "–"}</span>
<span className="text-sm my-auto text-gray-400">Permissions</span>
</div>
<div className="my-auto flex w-1/12 mr-4 opacity-0 group-hover:opacity-100 justify-end">
<div className="self-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md cursor-pointer w-8">
<ContextMenu menuEntries={gitProviderMenu(ap)}>
<svg className="w-8 h-8 p-1 text-gray-600 dark:text-gray-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Actions</title><g fill="currentColor" transform="rotate(90 12 12)"><circle cx="1" cy="1" r="2" transform="translate(5 11)" /><circle cx="1" cy="1" r="2" transform="translate(11 11)" /><circle cx="1" cy="1" r="2" transform="translate(17 11)" /></g></svg>
</ContextMenu>
</div>
</div>
</div>
</ItemField>
<ItemFieldContextMenu menuEntries={gitProviderMenu(ap)} />
</Item>
))}
</div>
</ItemsList>
</div>);
}

Expand Down Expand Up @@ -399,31 +394,24 @@ function GitIntegrations() {
</div>
</div>
)}
<div className="flex flex-col pt-6 space-y-2">
<ItemsList className="pt-6">
{providers && providers.map(ap => (
<div key={"ap-" + ap.id} className="flex-grow flex flex-row hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl h-16 w-full transition ease-in-out group">

<div className="px-4 self-center w-1/12">
<div className={"rounded-full w-3 h-3 text-sm align-middle " + (ap.status === "verified" ? "bg-green-500" : "bg-gray-400")}>
<Item key={"ap-" + ap.id} className="h-16">
<ItemFieldIcon>
<div className={"rounded-full w-3 h-3 text-sm align-middle m-auto " + (ap.status === "verified" ? "bg-green-500" : "bg-gray-400")}>
&nbsp;
</div>
</div>
<div className="p-0 my-auto flex flex-col w-3/12">
<span className="my-auto font-medium truncate overflow-ellipsis">{ap.type}</span>
</div>
<div className="p-0 my-auto flex flex-col w-7/12">
</ItemFieldIcon>
<ItemField className="w-3/12 flex flex-col">
<span className="font-medium truncate overflow-ellipsis">{ap.type}</span>
</ItemField>
<ItemField className="w-7/12 flex flex-col">
<span className="my-auto truncate text-gray-500 overflow-ellipsis">{ap.host}</span>
</div>
<div className="my-auto flex w-1/12 mr-4 opacity-0 group-hover:opacity-100 justify-end">
<div className="self-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md cursor-pointer w-8">
<ContextMenu menuEntries={gitProviderMenu(ap)}>
<svg className="w-8 h-8 p-1 text-gray-600 dark:text-gray-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Actions</title><g fill="currentColor" transform="rotate(90 12 12)"><circle cx="1" cy="1" r="2" transform="translate(5 11)" /><circle cx="1" cy="1" r="2" transform="translate(11 11)" /><circle cx="1" cy="1" r="2" transform="translate(17 11)" /></g></svg>
</ContextMenu>
</div>
</div>
</div>
</ItemField>
<ItemFieldContextMenu menuEntries={gitProviderMenu(ap)} />
</Item>
))}
</div>
</ItemsList>
</div>);
}

Expand Down
4 changes: 1 addition & 3 deletions components/dashboard/src/settings/Teams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -512,9 +512,7 @@ function AllTeams() {
</div>
<div className="my-auto flex w-1/12 mr-4 opacity-0 group-hover:opacity-100 justify-end">
<div className="self-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md cursor-pointer w-8">
<ContextMenu menuEntries={subscriptionMenu(sub)}>
<svg className="w-8 h-8 p-1 text-gray-600 dark:text-gray-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Actions</title><g fill="currentColor" transform="rotate(90 12 12)"><circle cx="1" cy="1" r="2" transform="translate(5 11)"/><circle cx="1" cy="1" r="2" transform="translate(11 11)"/><circle cx="1" cy="1" r="2" transform="translate(17 11)"/></g></svg>
</ContextMenu>
<ContextMenu menuEntries={subscriptionMenu(sub)} />
</div>
</div>
</div>
Expand Down
Loading