Skip to content

Commit 89fdaf1

Browse files
committed
[dashboard,server] switch ide version with one toggle
1 parent ffe6f48 commit 89fdaf1

File tree

7 files changed

+99
-117
lines changed

7 files changed

+99
-117
lines changed

chart/templates/server-ide-configmap.yaml

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,33 +46,22 @@ options:
4646
orderKey: "00"
4747
title: "VS Code"
4848
type: "browser"
49+
label: "Browser"
4950
logo: "https://ide.{{ $.Values.hostname }}/image/ide-logo/vscode.svg"
51+
# latestLogo: "https://ide.{{ $.Values.hostname }}/image/ide-logo/vscodeInsiders.svg"
5052
image: {{ (include "stable-image-full" (dict "root" $ "gp" $gp "comp" $gp.components.workspace.codeImage)) }}
51-
code-latest:
52-
orderKey: "01"
53-
title: "VS Code"
54-
type: "browser"
55-
logo: "https://ide.{{ $.Values.hostname }}/image/ide-logo/vscodeInsiders.svg"
56-
tooltip: "Early access version, still subject to testing."
57-
label: "Insiders"
58-
image: {{ (include "insider-image-full" (dict "root" $ "gp" $gp "comp" $gp.components.workspace.codeImage)) }}
59-
resolveImageDigest: true
53+
latestImage: {{ (include "insider-image-full" (dict "root" $ "gp" $gp "comp" $gp.components.workspace.codeImage)) }}
6054

6155
# Desktop IDEs
6256
code-desktop:
6357
orderKey: "02"
6458
title: "VS Code"
6559
type: "desktop"
60+
label: "Desktop"
6661
logo: "https://ide.{{ $.Values.hostname }}/image/ide-logo/vscode.svg"
62+
# latestLogo: "https://ide.{{ $.Values.hostname }}/image/ide-logo/vscodeInsiders.svg"
6763
image: {{ (include "gitpod.comp.imageFull" (dict "root" $ "gp" $gp "comp" $gp.components.workspace.desktopIdeImages.codeDesktop)) }}
68-
code-desktop-insiders:
69-
orderKey: "03"
70-
title: "VS Code"
71-
type: "desktop"
72-
logo: "https://ide.{{ $.Values.hostname }}/image/ide-logo/vscodeInsiders.svg"
73-
tooltip: "Visual Studio Code Insiders for early adopters."
74-
label: "Insiders"
75-
image: {{ (include "gitpod.comp.imageFull" (dict "root" $ "gp" $gp "comp" $gp.components.workspace.desktopIdeImages.codeDesktopInsiders)) }}
64+
latestImage: {{ (include "gitpod.comp.imageFull" (dict "root" $ "gp" $gp "comp" $gp.components.workspace.desktopIdeImages.codeDesktopInsiders)) }}
7665
intellij:
7766
orderKey: "04"
7867
title: "IntelliJ IDEA"
@@ -113,8 +102,9 @@ clients:
113102
"If you don't see an open dialog in your browser, make sure you have <a target='_blank' class='gp-link' href='https://code.visualstudio.com/download'>VS Code</a> installed on your machine, and then click <b>${OPEN_LINK_LABEL}</b> below.",
114103
]
115104
vscode-insiders:
116-
defaultDesktopIDE: "code-desktop-insiders"
117-
desktopIDEs: ["code-desktop-insiders"]
105+
# TODO(hw): make useLatestVersion marked?
106+
defaultDesktopIDE: "code-desktop"
107+
desktopIDEs: ["code-desktop"]
118108
installationSteps: [
119109
"If you don't see an open dialog in your browser, make sure you have <a target='_blank' class='gp-link' href='https://code.visualstudio.com/insiders'>VS Code Insiders</a> installed on your machine, and then click <b>${OPEN_LINK_LABEL}</b> below.",
120110
]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
export interface SelectableCardSolidProps {
8+
title: string;
9+
selected: boolean;
10+
className?: string;
11+
onClick: () => void;
12+
children?: React.ReactNode;
13+
}
14+
15+
function SelectableCardSolid(props: SelectableCardSolidProps) {
16+
return (
17+
<div
18+
className={`rounded-xl px-3 py-3 flex flex-col cursor-pointer group transition ease-in-out ${
19+
props.selected
20+
? "bg-gray-800 dark:bg-gray-100"
21+
: "bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700"
22+
} ${props.className || ""}`}
23+
onClick={props.onClick}
24+
>
25+
<div className="flex items-center">
26+
<p
27+
className={`w-full pl-1 text-base font-semibold truncate ${
28+
props.selected ? "text-gray-100 dark:text-gray-600" : "text-gray-600 dark:text-gray-500"
29+
}`}
30+
title={props.title}
31+
>
32+
{props.title}
33+
</p>
34+
<input className="opacity-0" type="radio" checked={props.selected} />
35+
</div>
36+
{props.children}
37+
</div>
38+
);
39+
}
40+
41+
export default SelectableCardSolid;

components/dashboard/src/settings/Preferences.tsx

Lines changed: 25 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,28 @@ import InfoBox from "../components/InfoBox";
1010
import { PageWithSubMenu } from "../components/PageWithSubMenu";
1111
import PillLabel from "../components/PillLabel";
1212
import SelectableCard from "../components/SelectableCard";
13+
import SelectableCardSolid from "../components/SelectableCardSolid";
1314
import Tooltip from "../components/Tooltip";
1415
import { getGitpodService } from "../service/service";
1516
import { ThemeContext } from "../theme-context";
1617
import { UserContext } from "../user-context";
1718
import settingsMenu from "./settings-menu";
18-
import IDENone from "../icons/IDENone.svg";
19-
import IDENoneDark from "../icons/IDENoneDark.svg";
2019
import CheckBox from "../components/CheckBox";
2120
import { trackEvent } from "../Analytics";
2221

2322
type Theme = "light" | "dark" | "system";
2423

25-
const DesktopNoneId = "none";
26-
const DesktopNone: IDEOption = {
27-
image: "",
28-
logo: IDENone,
29-
orderKey: "-1",
30-
title: "None",
31-
type: "desktop",
32-
};
24+
const DefaultBrowserIDE = "code";
3325

3426
export default function Preferences() {
3527
const { user } = useContext(UserContext);
36-
const { setIsDark, isDark } = useContext(ThemeContext);
28+
const { setIsDark } = useContext(ThemeContext);
3729

38-
const updateUserIDEInfo = async (defaultDesktopIde: string, defaultIde: string, useLatestVersion: boolean) => {
39-
const useDesktopIde = defaultDesktopIde !== DesktopNoneId;
40-
const desktopIde = useDesktopIde ? defaultDesktopIde : undefined;
30+
const updateUserIDEInfo = async (selectedIde: string, useLatestVersion: boolean) => {
31+
// Always start vscode browser ide
32+
const defaultIde = DefaultBrowserIDE;
33+
const useDesktopIde = selectedIde !== DefaultBrowserIDE;
34+
const desktopIde = useDesktopIde ? selectedIde : undefined;
4135
const additionalData = user?.additionalData ?? {};
4236
const settings = additionalData.ideSettings ?? {};
4337
settings.useDesktopIde = useDesktopIde;
@@ -61,38 +55,32 @@ export default function Preferences() {
6155
};
6256

6357
const [defaultIde, setDefaultIde] = useState<string>(user?.additionalData?.ideSettings?.defaultIde || "");
64-
const actuallySetDefaultIde = async (value: string) => {
65-
await updateUserIDEInfo(defaultDesktopIde, value, useLatestVersion);
66-
setDefaultIde(value);
67-
};
6858

6959
const [defaultDesktopIde, setDefaultDesktopIde] = useState<string>(
70-
(user?.additionalData?.ideSettings?.useDesktopIde && user?.additionalData?.ideSettings?.defaultDesktopIde) ||
71-
DesktopNoneId,
60+
user?.additionalData?.ideSettings?.defaultDesktopIde || "",
7261
);
7362
const actuallySetDefaultDesktopIde = async (value: string) => {
74-
await updateUserIDEInfo(value, defaultIde, useLatestVersion);
63+
await updateUserIDEInfo(value, useLatestVersion);
7564
setDefaultDesktopIde(value);
7665
};
7766

7867
const [useLatestVersion, setUseLatestVersion] = useState<boolean>(
7968
user?.additionalData?.ideSettings?.useLatestVersion ?? false,
8069
);
8170
const actuallySetUseLatestVersion = async (value: boolean) => {
82-
await updateUserIDEInfo(defaultDesktopIde, defaultIde, value);
71+
await updateUserIDEInfo(defaultDesktopIde, value);
8372
setUseLatestVersion(value);
8473
};
8574

8675
const [ideOptions, setIdeOptions] = useState<IDEOptions | undefined>(undefined);
8776
useEffect(() => {
8877
(async () => {
8978
const ideopts = await getGitpodService().server.getIDEOptions();
90-
ideopts.options[DesktopNoneId] = DesktopNone;
9179
setIdeOptions(ideopts);
92-
if (!defaultIde) {
80+
if (!defaultIde || ideopts.options[defaultIde] == null) {
9381
setDefaultIde(ideopts.defaultIde);
9482
}
95-
if (!defaultDesktopIde) {
83+
if (!defaultDesktopIde || ideopts.options[defaultDesktopIde] == null) {
9684
setDefaultDesktopIde(ideopts.defaultDesktopIde);
9785
}
9886
})();
@@ -112,8 +100,7 @@ export default function Preferences() {
112100
setTheme(theme);
113101
};
114102

115-
const browserIdeOptions = ideOptions && orderedIdeOptions(ideOptions, "browser");
116-
const desktopIdeOptions = ideOptions && orderedIdeOptions(ideOptions, "desktop");
103+
const allIdeOptions = ideOptions && orderedIdeOptions(ideOptions);
117104

118105
const [dotfileRepo, setDotfileRepo] = useState<string>(user?.additionalData?.dotfileRepo || "");
119106
const actuallySetDotfileRepo = async (value: string) => {
@@ -132,48 +119,17 @@ export default function Preferences() {
132119
<PageWithSubMenu subMenu={settingsMenu} title="Preferences" subtitle="Configure user preferences.">
133120
{ideOptions && (
134121
<>
135-
{browserIdeOptions && (
136-
<>
137-
<h3>Browser Editor</h3>
138-
<p className="text-base text-gray-500 dark:text-gray-400">
139-
Choose the default editor for opening workspaces in the browser.
140-
</p>
141-
<div className="my-4 gap-4 flex flex-wrap">
142-
{browserIdeOptions.map(([id, option]) => {
143-
const selected = defaultIde === id;
144-
const onSelect = () => actuallySetDefaultIde(id);
145-
return renderIdeOption(option, selected, onSelect);
146-
})}
147-
</div>
148-
{ideOptions.options[defaultIde]?.notes && (
149-
<InfoBox className="my-5 max-w-2xl">
150-
<ul>
151-
{ideOptions.options[defaultIde].notes?.map((x, idx) => (
152-
<li className={idx > 0 ? "mt-2" : ""}>{x}</li>
153-
))}
154-
</ul>
155-
</InfoBox>
156-
)}
157-
</>
158-
)}
159-
{desktopIdeOptions && (
122+
{/* <div>{JSON.stringify(user?.additionalData?.ideSettings, null, 4)}</div> */}
123+
{allIdeOptions && (
160124
<>
161-
<h3 className="mt-12 flex">
162-
Desktop Editor
163-
<PillLabel type="warn" className="font-semibold py-0.5 px-2 self-center">
164-
Beta
165-
</PillLabel>
166-
</h3>
125+
<h3>Editor</h3>
167126
<p className="text-base text-gray-500 dark:text-gray-400">
168-
Optionally, choose the default desktop editor for opening workspaces.
127+
Choose the editor for opening workspaces.
169128
</p>
170129
<div className="my-4 gap-4 flex flex-wrap max-w-2xl">
171-
{desktopIdeOptions.map(([id, option]) => {
130+
{allIdeOptions.map(([id, option]) => {
172131
const selected = defaultDesktopIde === id;
173132
const onSelect = () => actuallySetDefaultDesktopIde(id);
174-
if (id === DesktopNoneId) {
175-
option.logo = isDark ? IDENoneDark : IDENone;
176-
}
177133
return renderIdeOption(option, selected, onSelect);
178134
})}
179135
</div>
@@ -210,7 +166,7 @@ export default function Preferences() {
210166
)}
211167
<CheckBox
212168
title="Latest Release"
213-
desc="Include the latest Early Access Program (EAP) version for each JetBrains IDE."
169+
desc="Use the latest version for each editor. Insiders for VS Code, EAP for JetBrains IDEs."
214170
checked={useLatestVersion}
215171
onChange={(e) => actuallySetUseLatestVersion(e.target.checked)}
216172
/>
@@ -301,10 +257,10 @@ export default function Preferences() {
301257
);
302258
}
303259

304-
function orderedIdeOptions(ideOptions: IDEOptions, type: "browser" | "desktop") {
260+
function orderedIdeOptions(ideOptions: IDEOptions) {
305261
// TODO: Maybe convert orderKey to number before sort?
306262
return Object.entries(ideOptions.options)
307-
.filter(([_, x]) => x.type === type && !x.hidden)
263+
.filter(([_, x]) => !x.hidden)
308264
.sort((a, b) => {
309265
const keyA = a[1].orderKey || a[0];
310266
const keyB = b[1].orderKey || b[0];
@@ -314,22 +270,22 @@ function orderedIdeOptions(ideOptions: IDEOptions, type: "browser" | "desktop")
314270

315271
function renderIdeOption(option: IDEOption, selected: boolean, onSelect: () => void): JSX.Element {
316272
const card = (
317-
<SelectableCard className="w-36 h-40" title={option.title} selected={selected} onClick={onSelect}>
273+
<SelectableCardSolid className="w-36 h-40" title={option.title} selected={selected} onClick={onSelect}>
318274
<div className="flex justify-center mt-3">
319275
<img className="w-16 filter-grayscale self-center" src={option.logo} alt="logo" />
320276
</div>
321277
{option.label ? (
322278
<div
323279
className={`font-semibold text-sm ${
324-
selected ? "text-green-500" : "text-gray-500 dark:text-gray-400"
280+
selected ? "text-gray-100 dark:text-gray-600" : "text-gray-600 dark:text-gray-500"
325281
} uppercase mt-2 px-3 py-1 self-center`}
326282
>
327283
{option.label}
328284
</div>
329285
) : (
330286
<></>
331287
)}
332-
</SelectableCard>
288+
</SelectableCardSolid>
333289
);
334290

335291
if (option.tooltip) {

components/gitpod-protocol/src/workspace-instance.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,10 @@ export interface WorkspaceInstanceConfiguration {
221221

222222
// supervisorImage is the ref of the supervisor image this instance uses.
223223
supervisorImage?: string;
224+
225+
ideConfig?: {
226+
useLatest?: boolean;
227+
};
224228
}
225229

226230
/**

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,6 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
314314
delete res.status.ownerToken;
315315
// is an operational internal detail
316316
delete res.status.nodeName;
317-
// configuration contains feature flags and theia version.
318-
// we might want to share that in the future, but for the time being there's no need
319-
delete res.configuration;
320317
// internal operation detail
321318
// @ts-ignore
322319
delete res.workspaceImage;

components/server/src/workspace/workspace-starter.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -522,26 +522,33 @@ export class WorkspaceStarter {
522522
excludeFeatureFlags: NamedWorkspaceFeatureFlag[],
523523
ideConfig: IDEConfig,
524524
): Promise<WorkspaceInstance> {
525+
const ideChoice = user.additionalData?.ideSettings?.defaultIde;
526+
const useLatest = !!user.additionalData?.ideSettings?.useLatestVersion;
527+
525528
// TODO(cw): once we allow changing the IDE in the workspace config (i.e. .gitpod.yml), we must
526529
// give that value precedence over the default choice.
527530
const configuration: WorkspaceInstanceConfiguration = {
528531
ideImage: ideConfig.ideOptions.options[ideConfig.ideOptions.defaultIde].image,
529532
supervisorImage: ideConfig.supervisorImage,
533+
ideConfig: {
534+
// TODO(hw): set false when latestImage not found
535+
useLatest,
536+
},
530537
};
531538

532-
const ideChoice = user.additionalData?.ideSettings?.defaultIde;
533539
if (!!ideChoice) {
534540
const mappedImage = ideConfig.ideOptions.options[ideChoice];
535-
if (!!mappedImage && mappedImage.image) {
536-
configuration.ideImage = mappedImage.image;
541+
if (mappedImage.image != null) {
542+
configuration.ideImage = useLatest
543+
? mappedImage?.latestImage ?? mappedImage?.image
544+
: mappedImage?.image;
537545
} else if (this.authService.hasPermission(user, "ide-settings")) {
538546
// if the IDE choice isn't one of the preconfiured choices, we assume its the image name.
539547
// For now, this feature requires special permissions.
540548
configuration.ideImage = ideChoice;
541549
}
542550
}
543551

544-
const useLatest = !!user.additionalData?.ideSettings?.useLatestVersion;
545552
const referrerIde = this.resolveReferrerIDE(workspace, user, ideConfig);
546553
if (referrerIde) {
547554
configuration.desktopIdeImage = useLatest

0 commit comments

Comments
 (0)