-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6813b77
commit e56ef18
Showing
4 changed files
with
295 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
153
components/dashboard/src/components/SelectIDEComponent.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters