|
1 | 1 | /**
|
2 |
| - * Copyright (c) 2022 Gitpod GmbH. All rights reserved. |
| 2 | + * Copyright (c) 2023 Gitpod GmbH. All rights reserved. |
3 | 3 | * Licensed under the GNU Affero General Public License (AGPL).
|
4 | 4 | * See License.AGPL.txt in the project root for license information.
|
5 | 5 | */
|
6 | 6 |
|
7 |
| -import { IDEOption, IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol"; |
8 | 7 | import { useContext, useEffect, useState } from "react";
|
9 |
| -import InfoBox from "../components/InfoBox"; |
10 |
| -import SelectableCardSolid from "../components/SelectableCardSolid"; |
11 |
| -import Tooltip from "../components/Tooltip"; |
12 |
| -import { getGitpodService } from "../service/service"; |
13 | 8 | import { UserContext } from "../user-context";
|
14 | 9 | import CheckBox from "../components/CheckBox";
|
15 | 10 | import { User } from "@gitpod/gitpod-protocol";
|
| 11 | +import SelectIDEComponent from "../components/SelectIDEComponent"; |
16 | 12 | import PillLabel from "../components/PillLabel";
|
| 13 | +import { useUpdateCurrentUserMutation } from "../data/current-user/update-mutation"; |
17 | 14 |
|
18 | 15 | export type IDEChangedTrackLocation = "workspace_list" | "workspace_start" | "preferences";
|
19 | 16 | interface SelectIDEProps {
|
20 | 17 | updateUserContext?: boolean;
|
21 | 18 | location: IDEChangedTrackLocation;
|
22 | 19 | }
|
23 | 20 |
|
24 |
| -export const updateUserIDEInfo = async ( |
25 |
| - user: User, |
26 |
| - selectedIde: string, |
27 |
| - useLatestVersion: boolean, |
28 |
| - location: IDEChangedTrackLocation, |
29 |
| -) => { |
30 |
| - const additionalData = user?.additionalData ?? {}; |
31 |
| - const settings = additionalData.ideSettings ?? {}; |
32 |
| - settings.settingVersion = "2.0"; |
33 |
| - settings.defaultIde = selectedIde; |
34 |
| - settings.useLatestVersion = useLatestVersion; |
35 |
| - additionalData.ideSettings = settings; |
36 |
| - getGitpodService() |
37 |
| - .server.trackEvent({ |
38 |
| - event: "ide_configuration_changed", |
39 |
| - properties: { |
40 |
| - ...settings, |
41 |
| - location, |
42 |
| - }, |
43 |
| - }) |
44 |
| - .then() |
45 |
| - .catch(console.error); |
46 |
| - return getGitpodService().server.updateLoggedInUser({ additionalData }); |
47 |
| -}; |
48 |
| - |
49 | 21 | export default function SelectIDE(props: SelectIDEProps) {
|
50 | 22 | const { user, setUser } = useContext(UserContext);
|
| 23 | + const updateUser = useUpdateCurrentUserMutation(); |
51 | 24 |
|
52 | 25 | // Only exec once when we access this component
|
53 | 26 | useEffect(() => {
|
54 | 27 | user && User.migrationIDESettings(user);
|
55 | 28 | // eslint-disable-next-line react-hooks/exhaustive-deps
|
56 | 29 | }, []);
|
57 | 30 |
|
58 |
| - const actualUpdateUserIDEInfo = async (user: User, selectedIde: string, useLatestVersion: boolean) => { |
59 |
| - const newUserData = await updateUserIDEInfo(user, selectedIde, useLatestVersion, props.location); |
| 31 | + const [defaultIde, setDefaultIde] = useState<string>(user?.additionalData?.ideSettings?.defaultIde || "code"); |
| 32 | + const [useLatestVersion, setUseLatestVersion] = useState<boolean>( |
| 33 | + user?.additionalData?.ideSettings?.useLatestVersion ?? false, |
| 34 | + ); |
| 35 | + |
| 36 | + const actualUpdateUserIDEInfo = async (selectedIde: string, useLatestVersion: boolean) => { |
| 37 | + const additionalData = user?.additionalData ?? {}; |
| 38 | + const settings = additionalData.ideSettings ?? {}; |
| 39 | + settings.settingVersion = "2.0"; |
| 40 | + settings.defaultIde = selectedIde; |
| 41 | + settings.useLatestVersion = useLatestVersion; |
| 42 | + additionalData.ideSettings = settings; |
| 43 | + |
| 44 | + const newUserData = await updateUser.mutateAsync({ additionalData }); |
60 | 45 | props.updateUserContext && setUser({ ...newUserData });
|
61 | 46 | };
|
62 | 47 |
|
63 |
| - const [defaultIde, setDefaultIde] = useState<string>(user?.additionalData?.ideSettings?.defaultIde || "code"); |
64 | 48 | const actuallySetDefaultIde = async (value: string) => {
|
65 |
| - await actualUpdateUserIDEInfo(user!, value, useLatestVersion); |
| 49 | + await actualUpdateUserIDEInfo(value, useLatestVersion); |
66 | 50 | setDefaultIde(value);
|
67 | 51 | };
|
68 | 52 |
|
69 |
| - const [useLatestVersion, setUseLatestVersion] = useState<boolean>( |
70 |
| - user?.additionalData?.ideSettings?.useLatestVersion ?? false, |
71 |
| - ); |
72 | 53 | const actuallySetUseLatestVersion = async (value: boolean) => {
|
73 |
| - await actualUpdateUserIDEInfo(user!, defaultIde, value); |
| 54 | + await actualUpdateUserIDEInfo(defaultIde, value); |
74 | 55 | setUseLatestVersion(value);
|
75 | 56 | };
|
76 | 57 |
|
77 |
| - const [ideOptions, setIdeOptions] = useState<IDEOptions | undefined>(undefined); |
78 |
| - useEffect(() => { |
79 |
| - (async () => { |
80 |
| - setIdeOptions(await getGitpodService().server.getIDEOptions()); |
81 |
| - })(); |
82 |
| - }, []); |
83 |
| - |
84 |
| - const allIdeOptions = ideOptions && orderedIdeOptions(ideOptions); |
| 58 | + //todo(ft): find a better way to group IDEs by vendor |
| 59 | + const shouldShowJetbrainsNotice = !["code", "code-desktop"].includes(defaultIde); // a really hacky way to get just JetBrains IDEs |
85 | 60 |
|
86 | 61 | return (
|
87 | 62 | <>
|
88 |
| - {ideOptions && ( |
89 |
| - <> |
90 |
| - {allIdeOptions && ( |
91 |
| - <> |
92 |
| - <div className={`my-4 gap-3 flex flex-wrap max-w-3xl`}> |
93 |
| - {allIdeOptions.map(([id, option]) => { |
94 |
| - const selected = defaultIde === id; |
95 |
| - const version = useLatestVersion ? option.latestImageVersion : option.imageVersion; |
96 |
| - const onSelect = () => actuallySetDefaultIde(id); |
97 |
| - return renderIdeOption(option, selected, version, onSelect); |
98 |
| - })} |
99 |
| - </div> |
100 |
| - {ideOptions.options[defaultIde]?.notes && ( |
101 |
| - <InfoBox className="my-5 max-w-2xl"> |
102 |
| - <ul> |
103 |
| - {ideOptions.options[defaultIde].notes?.map((x, idx) => ( |
104 |
| - <li className={idx > 0 ? "mt-2" : ""}>{x}</li> |
105 |
| - ))} |
106 |
| - </ul> |
107 |
| - </InfoBox> |
108 |
| - )} |
109 |
| - |
110 |
| - <p className="text-left w-full text-gray-400 dark:text-gray-500"> |
111 |
| - <strong>JetBrains </strong> integration is currently in{" "} |
112 |
| - <PillLabel type="warn" className="font-semibold mt-2 ml-0 py-0.5 px-1 self-center"> |
113 |
| - <a href="https://www.gitpod.io/docs/references/gitpod-releases"> |
114 |
| - <span className="text-xs">Beta</span> |
115 |
| - </a> |
116 |
| - </PillLabel> |
117 |
| - · |
118 |
| - <a |
119 |
| - href="https://github.com/gitpod-io/gitpod/issues/6576" |
120 |
| - target="_blank" |
121 |
| - rel="noopener noreferrer" |
122 |
| - className="gp-link" |
123 |
| - > |
124 |
| - Send feedback |
125 |
| - </a> |
126 |
| - </p> |
127 |
| - </> |
128 |
| - )} |
129 |
| - <CheckBox |
130 |
| - title="Latest Release (Unstable)" |
131 |
| - desc={ |
132 |
| - <span> |
133 |
| - Use the latest version for each editor.{" "} |
134 |
| - <a |
135 |
| - className="gp-link" |
136 |
| - target="_blank" |
137 |
| - href="https://code.visualstudio.com/blogs/2016/02/01/introducing_insiders_build" |
138 |
| - rel="noreferrer" |
139 |
| - > |
140 |
| - Insiders |
141 |
| - </a>{" "} |
142 |
| - for VS Code,{" "} |
143 |
| - <a |
144 |
| - className="gp-link" |
145 |
| - target="_blank" |
146 |
| - href="https://www.jetbrains.com/resources/eap/" |
147 |
| - rel="noreferrer" |
148 |
| - > |
149 |
| - EAP |
150 |
| - </a>{" "} |
151 |
| - for JetBrains IDEs. |
152 |
| - </span> |
153 |
| - } |
154 |
| - checked={useLatestVersion} |
155 |
| - onChange={(e) => actuallySetUseLatestVersion(e.target.checked)} |
156 |
| - /> |
157 |
| - </> |
158 |
| - )} |
159 |
| - </> |
160 |
| - ); |
161 |
| -} |
162 |
| - |
163 |
| -function orderedIdeOptions(ideOptions: IDEOptions) { |
164 |
| - // TODO: Maybe convert orderKey to number before sort? |
165 |
| - return Object.entries(ideOptions.options) |
166 |
| - .filter(([_, x]) => !x.hidden) |
167 |
| - .sort((a, b) => { |
168 |
| - const keyA = a[1].orderKey || a[0]; |
169 |
| - const keyB = b[1].orderKey || b[0]; |
170 |
| - return keyA.localeCompare(keyB); |
171 |
| - }); |
172 |
| -} |
173 |
| - |
174 |
| -function renderIdeOption( |
175 |
| - option: IDEOption, |
176 |
| - selected: boolean, |
177 |
| - version: IDEOption["imageVersion"], |
178 |
| - onSelect: () => void, |
179 |
| -): JSX.Element { |
180 |
| - const shouldShowOptionType = option.type !== "desktop" || option.title === "VS Code"; // Force show of "Desktop" in the list for VS Code Desktop |
181 |
| - const card = ( |
182 |
| - <SelectableCardSolid className="w-36 h-44" title={option.title} selected={selected} onClick={onSelect}> |
183 |
| - {version ? ( |
184 |
| - <span |
185 |
| - className={`font-normal font-mono text-xs ${ |
186 |
| - selected |
187 |
| - ? "text-gray-100 dark:text-gray-600" |
188 |
| - : "text-gray-400 contrast-more:text-gray-600 dark:text-gray-500" |
189 |
| - } pl-1 self-start`} |
190 |
| - title="The IDE's current version on Gitpod" |
191 |
| - > |
192 |
| - {version} |
193 |
| - </span> |
194 |
| - ) : ( |
195 |
| - <span |
196 |
| - style={{ |
197 |
| - minHeight: "1rem", |
| 63 | + <div className="w-112 my-4"> |
| 64 | + <SelectIDEComponent |
| 65 | + onSelectionChange={async (ide) => { |
| 66 | + await actuallySetDefaultIde(ide); |
198 | 67 | }}
|
199 |
| - ></span> |
200 |
| - )} |
201 |
| - <div className="flex justify-center mt-3 mb-2"> |
202 |
| - <img className="w-16 filter-grayscale self-center" src={option.logo} alt="logo" /> |
| 68 | + selectedIdeOption={defaultIde} |
| 69 | + useLatest={useLatestVersion} |
| 70 | + /> |
203 | 71 | </div>
|
204 |
| - {shouldShowOptionType ? ( |
205 |
| - <PillLabel type="warn" className="place-self-start py-0.5 my-2 flex"> |
206 |
| - <span className="text-xs capitalize">{option.type}</span> |
207 |
| - </PillLabel> |
208 |
| - ) : ( |
209 |
| - option.label && ( |
210 |
| - <PillLabel type="neutral" className="place-self-start py-0.5 my-2 flex"> |
211 |
| - <span className="text-xs normal-case font-medium">{option.label}</span> |
| 72 | + |
| 73 | + {shouldShowJetbrainsNotice && ( |
| 74 | + <p className="text-left w-full text-gray-400 dark:text-gray-500"> |
| 75 | + <strong>JetBrains </strong> integration is currently in{" "} |
| 76 | + <PillLabel type="warn" className="font-semibold mt-2 ml-0 py-0.5 px-1 self-center"> |
| 77 | + <a href="https://www.gitpod.io/docs/references/gitpod-releases"> |
| 78 | + <span className="text-xs">Beta</span> |
| 79 | + </a> |
212 | 80 | </PillLabel>
|
213 |
| - ) |
| 81 | + · |
| 82 | + <a |
| 83 | + href="https://github.com/gitpod-io/gitpod/issues/6576" |
| 84 | + target="_blank" |
| 85 | + rel="noopener noreferrer" |
| 86 | + className="gp-link" |
| 87 | + > |
| 88 | + Send feedback |
| 89 | + </a> |
| 90 | + </p> |
214 | 91 | )}
|
215 |
| - </SelectableCardSolid> |
216 |
| - ); |
217 | 92 |
|
218 |
| - if (option.tooltip) { |
219 |
| - return <Tooltip content={option.tooltip}>{card}</Tooltip>; |
220 |
| - } |
221 |
| - return card; |
| 93 | + <CheckBox |
| 94 | + title="Latest Release (Unstable)" |
| 95 | + desc={ |
| 96 | + <span> |
| 97 | + Use the latest version for each editor.{" "} |
| 98 | + <a |
| 99 | + className="gp-link" |
| 100 | + target="_blank" |
| 101 | + href="https://code.visualstudio.com/blogs/2016/02/01/introducing_insiders_build" |
| 102 | + rel="noreferrer" |
| 103 | + > |
| 104 | + Insiders |
| 105 | + </a>{" "} |
| 106 | + for VS Code,{" "} |
| 107 | + <a |
| 108 | + className="gp-link" |
| 109 | + target="_blank" |
| 110 | + href="https://www.jetbrains.com/resources/eap/" |
| 111 | + rel="noreferrer" |
| 112 | + > |
| 113 | + EAP |
| 114 | + </a>{" "} |
| 115 | + for JetBrains IDEs. |
| 116 | + </span> |
| 117 | + } |
| 118 | + checked={useLatestVersion} |
| 119 | + onChange={(e) => actuallySetUseLatestVersion(e.target.checked)} |
| 120 | + /> |
| 121 | + </> |
| 122 | + ); |
222 | 123 | }
|
0 commit comments