Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
svenefftinge committed Dec 16, 2022
1 parent 6813b77 commit e56ef18
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 2 deletions.
119 changes: 119 additions & 0 deletions components/dashboard/src/components/DropDown2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* Copyright (c) 2022 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 { useMemo, useState } from "react";
import Arrow from "./Arrow";

export interface DropDown2Element {
id: string;
elementInDropDown?: JSX.Element;
element: JSX.Element;
searchableString?: string;
isSelectable?: boolean;
}

export interface DropDown2Props {
elements: DropDown2Element[];
selectedElement?: string;
searchable?: boolean;
onSelectionChange: (id: string) => void;
}

export default function DropDown2(props: DropDown2Props) {
const [selectedElement, setSelectedElement] = useState<string>(props.selectedElement || props.elements[0].id);
const [showDropDown, setShowDropDown] = useState<boolean>(false);
const onSelected = useMemo(
() => (elementId: string) => {
props.onSelectionChange(elementId);
setSelectedElement(elementId);
setShowDropDown(false);
},
[props],
);
const doShowDropDown = useMemo(() => () => setShowDropDown(true), []);
if (props.elements.length === 0) {
return <></>;
}
return (
<div>
{showDropDown ? (
<div
onKeyDown={(e) => {
if (e.key === "Escape") {
setShowDropDown(false);
e.preventDefault();
}
}}
>
<DropDownMenu {...props} onSelected={onSelected} />
</div>
) : (
<div
className="rounded-xl hover:bg-gray-400 dark:hover:bg-gray-700 cursor-pointer flex items-center px-2"
onClick={doShowDropDown}
>
{props.elements.find((e) => e.id === selectedElement)?.element}
<div className="flex-grow" />
<div>
<Arrow direction={"down"} />
</div>
</div>
)}
</div>
);
}

interface DropDownMenuProps extends DropDown2Props {
onSelected: (ide: string) => void;
}

function DropDownMenu(props: DropDownMenuProps): JSX.Element {
const { elements, selectedElement, searchable, onSelected } = props;
const [search, setSearch] = useState<string>("");
const filteredOptions = useMemo(
() =>
elements.filter(
(o) => !o.isSelectable || (o.searchableString || "").toLowerCase().indexOf(search.toLowerCase()) !== -1,
),
[search, elements],
);
return (
<div className="relative flex flex-col">
{searchable && (
<input
type="text"
autoFocus
className="w-full focus"
placeholder="Search IDE"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
)}
<div className="absolute w-full top-11 bg-gray-900 max-h-72 overflow-auto rounded-xl mt-3">
{filteredOptions.map((element) => {
const selected = element.id === selectedElement;

let selectionClasses = `hover:bg-gray-400 dark:hover:bg-gray-700 cursor-pointer`;
if (selected) {
selectionClasses = `bg-gray-300 dark:bg-gray-800`;
}
if (!element.isSelectable) {
selectionClasses = ``;
}
return (
<div
key={element.id}
className={"rounded-md " + selectionClasses}
onClick={() => element.isSelectable && onSelected(element.id)}
>
{element.elementInDropDown || element.element}
</div>
);
})}
</div>
</div>
);
}
153 changes: 153 additions & 0 deletions components/dashboard/src/components/SelectIDEComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* Copyright (c) 2022 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 { IDEOption, IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol";
import { useContext, useEffect, useState } from "react";
import { getGitpodService } from "../service/service";
import { UserContext } from "../user-context";
import CheckBox from "./CheckBox";
import DropDown2, { DropDown2Element } from "./DropDown2";

interface SelectIDEComponentProps {
ideOptions?: IDEOptions;
selectedIdeOption?: string;
useLatest?: boolean;
setUseLatest?: (useLatestVersion: boolean) => void;
onSelectionChange: (ide: string) => void;
}

export default function SelectIDEComponent(props: SelectIDEComponentProps) {
const { user } = useContext(UserContext);
function options2Elements(
ideOptions?: IDEOptions,
useLatest?: boolean,
setUseLatest?: (useLatest: boolean) => void,
): DropDown2Element[] {
if (!ideOptions) {
return [];
}
return [
...IDEOptions.asArray(ideOptions).map((ide) => ({
id: ide.id,
element: <IdeOptionElementSelected option={ide} useLatest={!!useLatest} />,
elementInDropDown: <IdeOptionElementInDropDown option={ide} useLatest={!!useLatest} />,
searchableString: `${ide.label}${ide.title}${ide.notes}${ide.id}`,
isSelectable: true,
})),
{
id: "non-selectable-checkbox",
element: (
<div className="ml-3">
<CheckBox
title="Use latest"
desc="Use latest version of IDE"
checked={!!useLatest}
onChange={(e) => setUseLatest && setUseLatest(e.target.checked)}
/>
</div>
),
isSelectable: false,
},
];
}

const [elements, setElements] = useState<DropDown2Element[]>(
options2Elements(props.ideOptions, props.useLatest, props.setUseLatest),
);
const [selectedIdeOption, setSelectedIdeOption] = useState<string | undefined>(
props.selectedIdeOption || user?.additionalData?.ideSettings?.defaultIde,
);
useEffect(() => {
(async () => {
let options = props.ideOptions;
if (!options) {
options = await getGitpodService().server.getIDEOptions();
}
setElements(options2Elements(options, props.useLatest, props.setUseLatest));
if (!selectedIdeOption || !options.options[selectedIdeOption]) {
setSelectedIdeOption(options.defaultIde);
}
})();
}, [props.ideOptions, selectedIdeOption, props.useLatest, props.setUseLatest]);
return (
<DropDown2
elements={elements}
onSelectionChange={props.onSelectionChange}
searchable={true}
selectedElement={selectedIdeOption}
/>
);
}

interface IdeOptionElementProps {
option: IDEOption | undefined;
useLatest: boolean;
}

function IdeOptionElementSelected(p: IdeOptionElementProps): JSX.Element {
const { option, useLatest } = p;
if (!option) {
return <></>;
}
const version = useLatest ? option.latestImageVersion : option.imageVersion;
const label = option.type === "desktop" ? "" : option.type;

return (
<div className="flex" title={option.title}>
<div className="mx-2 my-3">
<img className="w-8 filter-grayscale self-center" src={option.logo} alt="logo" />
</div>
<div className="flex-col ml-1 mt-1 flex-grow">
<div className="flex">Editor</div>
<div className="flex text-sm text-gray-100 dark:text-gray-600">
<div>{option.title}</div>
<div className="ml-1">{version}</div>
<div className="ml-1">
{label ? (
<span className={`font-semibold text-sm text-gray-600 dark:text-gray-500"}`}>
{label[0].toLocaleUpperCase() + label.slice(1)}
</span>
) : (
<></>
)}
</div>
</div>
</div>
</div>
);
}

function IdeOptionElementInDropDown(p: IdeOptionElementProps): JSX.Element {
const { option, useLatest } = p;
if (!option) {
return <></>;
}
const version = useLatest ? option.latestImageVersion : option.imageVersion;
const label = option.type === "desktop" ? "" : option.type;

return (
<div className="flex" title={option.title}>
<div className="mx-2 my-3">
<img className="w-8 filter-grayscale self-center" src={option.logo} alt="logo" />
</div>
<div className="flex-col ml-1 mt-1 flex-grow">
<div className="flex">
<div>{option.title}</div>
<div className="ml-1 text-gray-100 dark:text-gray-600">{version}</div>
</div>
<div className="">
{label ? (
<span className={`font-semibold text-sm text-gray-600 dark:text-gray-500"}`}>
{label[0].toLocaleUpperCase() + label.slice(1)}
</span>
) : (
<></>
)}
</div>
</div>
</div>
);
}
19 changes: 17 additions & 2 deletions components/dashboard/src/workspaces/StartWorkspaceModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@
* See License.AGPL.txt in the project root for license information.
*/

import { useContext, useEffect } from "react";
import { useContext, useEffect, useState } from "react";
import { useLocation } from "react-router";
import Modal from "../components/Modal";
import RepositoryFinder from "../components/RepositoryFinder";
import SelectIDEComponent from "../components/SelectIDEComponent";
import { UserContext } from "../user-context";
import { StartWorkspaceModalContext } from "./start-workspace-modal-context";

export function StartWorkspaceModal() {
const { user } = useContext(UserContext);
const { isStartWorkspaceModalVisible, setIsStartWorkspaceModalVisible } = useContext(StartWorkspaceModalContext);
const location = useLocation();
const [useLatestIde, setUseLatestIde] = useState(!!user?.additionalData?.ideSettings?.useLatestVersion);

// Close the modal on navigation events.
useEffect(() => {
Expand All @@ -26,10 +30,21 @@ export function StartWorkspaceModal() {
onEnter={() => false}
visible={!!isStartWorkspaceModalVisible}
>
<h3 className="pb-2">Open in Gitpod</h3>
<h3 className="pb-2">New Workspace</h3>
<div className="border-t border-gray-200 dark:border-gray-800 mt-2 -mx-6 px-6 pt-4">
<RepositoryFinder />
</div>

<div className="border-t border-gray-200 dark:border-gray-800 mt-2 -mx-6 px-6 pt-4">
<div className="text-sm text-gray-100 dark:text-gray-600">Configure Workspace</div>
<SelectIDEComponent
onSelectionChange={(e) => {
console.log(e);
}}
useLatest={useLatestIde}
setUseLatest={setUseLatestIde}
/>
</div>
</Modal>
);
}
6 changes: 6 additions & 0 deletions components/gitpod-protocol/src/ide-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export interface IDEOptions {
clients?: { [id: string]: IDEClient };
}

export namespace IDEOptions {
export function asArray(options: IDEOptions): (IDEOption & { id: string })[] {
return Object.keys(options.options).map((id) => ({ ...options.options[id], id }));
}
}

export interface IDEClient {
/**
* The default desktop IDE when the user has not specified one.
Expand Down

0 comments on commit e56ef18

Please sign in to comment.