diff --git a/components/dashboard/src/components/SelectIDEComponent.tsx b/components/dashboard/src/components/SelectIDEComponent.tsx index cf3a396158300b..ec24054b8c6d48 100644 --- a/components/dashboard/src/components/SelectIDEComponent.tsx +++ b/components/dashboard/src/components/SelectIDEComponent.tsx @@ -130,7 +130,7 @@ function capitalize(label?: string) { return label && label[0].toLocaleUpperCase() + label.slice(1); } -const IdeOptionElementSelected: FC = ({ option, useLatest, loading = false }) => { +export const IdeOptionElementSelected: FC = ({ option, useLatest, loading = false }) => { let version: string | undefined, label: string | undefined, title: string; if (!option) { title = "Select Editor"; @@ -171,7 +171,7 @@ const IdeOptionElementSelected: FC = ({ option, useLatest ); }; -function IdeOptionElementInDropDown(p: IdeOptionElementProps): JSX.Element { +export function IdeOptionElementInDropDown(p: IdeOptionElementProps): JSX.Element { const { option, useLatest } = p; if (!option) { return <>; diff --git a/components/dashboard/src/components/SelectWorkspaceClassComponent.tsx b/components/dashboard/src/components/SelectWorkspaceClassComponent.tsx index 3241197d74f2a4..846f859e946b7f 100644 --- a/components/dashboard/src/components/SelectWorkspaceClassComponent.tsx +++ b/components/dashboard/src/components/SelectWorkspaceClassComponent.tsx @@ -82,7 +82,7 @@ type WorkspaceClassDropDownElementSelectedProps = { loading?: boolean; }; -const WorkspaceClassDropDownElementSelected: FC = ({ +export const WorkspaceClassDropDownElementSelected: FC = ({ wsClass, loading = false, }) => { @@ -104,7 +104,7 @@ const WorkspaceClassDropDownElementSelected: FC diff --git a/components/dashboard/src/data/ide-options/ide-options-query.ts b/components/dashboard/src/data/ide-options/ide-options-query.ts index fa457a0f1a14a2..0d8aab61c0a014 100644 --- a/components/dashboard/src/data/ide-options/ide-options-query.ts +++ b/components/dashboard/src/data/ide-options/ide-options-query.ts @@ -6,6 +6,7 @@ import { useQuery } from "@tanstack/react-query"; import { getGitpodService } from "../../service/service"; +import { IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol"; export const useIDEOptions = () => { return useQuery( @@ -19,3 +20,35 @@ export const useIDEOptions = () => { }, ); }; + +// TODO: update IDE Selector component to use this +export const useFilteredAndSortedIDEOptions = () => { + const { data, isLoading, ...rest } = useIDEOptions(); + if (isLoading || !data) { + return { data: undefined, isLoading, ...rest }; + } + + return { data: sortedIdeOptions(data), isLoading: false, ...rest }; +}; + +function filteredIdeOptions(ideOptions: IDEOptions) { + return IDEOptions.asArray(ideOptions).filter((x) => !x.hidden); +} + +function sortedIdeOptions(ideOptions: IDEOptions) { + return filteredIdeOptions(ideOptions).sort((a, b) => { + // Prefer experimental options + if (a.experimental && !b.experimental) { + return -1; + } + if (!a.experimental && b.experimental) { + return 1; + } + + if (!a.orderKey || !b.orderKey) { + return 0; + } + + return parseInt(a.orderKey, 10) - parseInt(b.orderKey, 10); + }); +} diff --git a/components/dashboard/src/icons/CaretDown.svg b/components/dashboard/src/icons/CaretDown.svg index 56c029abc90adb..abf4cde0f2bb6c 100644 --- a/components/dashboard/src/icons/CaretDown.svg +++ b/components/dashboard/src/icons/CaretDown.svg @@ -1,3 +1,3 @@ - - + + diff --git a/components/dashboard/src/workspaces/CreateWorkspacePage.tsx b/components/dashboard/src/workspaces/CreateWorkspacePage.tsx index d615b4fb74ea57..d4765f5b51571a 100644 --- a/components/dashboard/src/workspaces/CreateWorkspacePage.tsx +++ b/components/dashboard/src/workspaces/CreateWorkspacePage.tsx @@ -14,8 +14,6 @@ import { Link } from "react-router-dom"; import { Button } from "../components/Button"; import Modal from "../components/Modal"; import RepositoryFinder from "../components/RepositoryFinder"; -import SelectIDEComponent from "../components/SelectIDEComponent"; -import SelectWorkspaceClassComponent from "../components/SelectWorkspaceClassComponent"; import { UsageLimitReachedModal } from "../components/UsageLimitReachedModal"; import { CheckboxInputField } from "../components/forms/CheckboxInputField"; import { Heading1 } from "../components/typography/headings"; @@ -40,6 +38,7 @@ import { useDirtyState } from "../hooks/use-dirty-state"; import { LinkButton } from "../components/LinkButton"; import { InputField } from "../components/forms/InputField"; import Alert from "../components/Alert"; +import { WorkspaceDetails } from "./create-workspace/WorkspaceDetails"; export function CreateWorkspacePage() { const { user, setUser } = useContext(UserContext); @@ -64,7 +63,8 @@ export function CreateWorkspacePage() { const [selectedIde, setSelectedIde, selectedIdeIsDirty] = useDirtyState(defaultIde); const defaultWorkspaceClass = props.workspaceClass; const [selectedWsClass, setSelectedWsClass, selectedWsClassIsDirty] = useDirtyState(defaultWorkspaceClass); - const [errorWsClass, setErrorWsClass] = useState(undefined); + // TODO: handle errors in WorkspaceDetail + const [errorWsClass] = useState(undefined); const [contextURL, setContextURL] = useState( StartWorkspaceOptions.parseContextUrl(location.hash), ); @@ -163,7 +163,8 @@ export function CreateWorkspacePage() { }, [setSelectedIde, setUseLatestIde], ); - const [errorIde, setErrorIde] = useState(undefined); + // TODO: handle errors in WorkspaceDetail + const [errorIde] = useState(undefined); const existingWorkspaces = useMemo(() => { if (!workspaces.data || !CommitContext.is(workspaceContext.data)) { @@ -385,7 +386,7 @@ export function CreateWorkspacePage() { /> - + {/* - + */} + +
+ {/* {selectedWSClass?.displayName ?? "unknown"} */} + + ); +}; diff --git a/components/dashboard/src/workspaces/create-workspace/WorkspaceDetails.tsx b/components/dashboard/src/workspaces/create-workspace/WorkspaceDetails.tsx new file mode 100644 index 00000000000000..b7e8b4053d3952 --- /dev/null +++ b/components/dashboard/src/workspaces/create-workspace/WorkspaceDetails.tsx @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2023 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 { FC } from "react"; +import { WorkspaceClass } from "./WorkspaceClass"; +import { WorkspaceIDE } from "./WorkspaceIDE"; + +type Props = { + selectedIDE: string; + useLatestIDE: boolean; + selectedWSClassID: string; + onWSClassChange: (workspaceClass: string) => void; + onIDEChange: (ide: string, useLatest: boolean) => void; +}; +export const WorkspaceDetails: FC = ({ + selectedIDE, + useLatestIDE, + selectedWSClassID, + onWSClassChange, + onIDEChange, +}) => { + return ( +
+ + with + +
+ ); +}; diff --git a/components/dashboard/src/workspaces/create-workspace/WorkspaceIDE.tsx b/components/dashboard/src/workspaces/create-workspace/WorkspaceIDE.tsx new file mode 100644 index 00000000000000..57a3a153ccaa26 --- /dev/null +++ b/components/dashboard/src/workspaces/create-workspace/WorkspaceIDE.tsx @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2023 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 { FC, useMemo } from "react"; +import ContextMenu, { ContextMenuEntry } from "../../components/ContextMenu"; +import { useFilteredAndSortedIDEOptions } from "../../data/ide-options/ide-options-query"; +import { Button } from "../../components/Button"; +import Arrow from "../../components/Arrow"; +import Editor from "../../icons/Editor.svg"; +import { IdeOptionElementInDropDown } from "../../components/SelectIDEComponent"; + +type WorkspaceIDEProps = { + selectedIDE: string; + useLatestIDE: boolean; + onChange: (ide: string, useLatest: boolean) => void; +}; +export const WorkspaceIDE: FC = ({ selectedIDE, useLatestIDE, onChange }) => { + const { data: ideOptions, isLoading } = useFilteredAndSortedIDEOptions(); + + const menuEntries = useMemo((): ContextMenuEntry[] => { + return (ideOptions || []).map((ide) => ({ + title: ide.title, + customContent: , + onClick: () => { + onChange(ide.id, useLatestIDE); + }, + })); + }, [ideOptions, onChange, useLatestIDE]); + + const selectedOption = useMemo(() => { + if (!ideOptions) { + return; + } + + return ideOptions.find((ide) => ide.id === selectedIDE); + }, [ideOptions, selectedIDE]); + + if (isLoading) { + return ...; + } + + return ( + + {/* {selectedOption ? ( + + ) : ( + Please select an Editor + )} */} + + + ); +};