Skip to content

Commit b9ccf78

Browse files
[dashboard] Refactor What's New dialog
1 parent a4bcb5a commit b9ccf78

File tree

8 files changed

+210
-77
lines changed

8 files changed

+210
-77
lines changed

components/dashboard/src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Login } from './Login';
1313
import { UserContext } from './user-context';
1414
import { TeamsContext } from './teams/teams-context';
1515
import { getGitpodService } from './service/service';
16-
import { shouldSeeWhatsNew, WhatsNew } from './WhatsNew';
16+
import { shouldSeeWhatsNew, WhatsNew } from './whatsnew/WhatsNew';
1717
import gitpodIcon from './icons/gitpod.svg';
1818
import { ErrorCodes } from '@gitpod/gitpod-protocol/lib/messaging/error';
1919

@@ -232,7 +232,7 @@ function App() {
232232
const isCreation = window.location.pathname === '/' && hash !== '';
233233
const isWsStart = /\/start\/?/.test(window.location.pathname) && hash !== '';
234234
if (isWhatsNewShown) {
235-
toRender = <WhatsNew visible={true} onClose={() => setWhatsNewShown(false)} />;
235+
toRender = <WhatsNew onClose={() => setWhatsNewShown(false)} />;
236236
} else if (isCreation) {
237237
toRender = <CreateWorkspace contextUrl={hash} />;
238238
} else if (isWsStart) {

components/dashboard/src/WhatsNew.tsx

Lines changed: 0 additions & 73 deletions
This file was deleted.

components/dashboard/src/components/CodeText.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
*/
66

77
export default function CodeText(p: { children?: React.ReactNode }) {
8-
return <span className="bg-gray-100 dark:bg-gray-800 px-1.5 py-1 rounded-md text-sm font-mono font-medium">{p.children}</span>;
8+
return <span className="bg-gray-100 dark:bg-gray-700 px-1.5 py-0.5 rounded-md text-sm font-mono font-medium">{p.children}</span>;
99
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
/**
8+
* Renders a pill.
9+
*
10+
* **type**\
11+
* info: Renders a blue pile label (default).\
12+
* warn: Renders an orange pile label.
13+
*/
14+
export default function PillLabel(props: { children?: React.ReactNode, type?: "info" | "warn" }) {
15+
const infoStyle = "bg-blue-50 text-blue-500 dark:bg-blue-500 dark:text-blue-100";
16+
const warnStyle = "bg-orange-100 text-orange-700 dark:bg-orange-600 dark:text-orange-100";
17+
const style = `ml-2 px-3 py-1 text-sm uppercase rounded-xl ${props.type === "warn" ? warnStyle : infoStyle}`;
18+
return <span className={style}>{props.children}</span>;
19+
}

components/dashboard/src/settings/Preferences.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type Theme = 'light' | 'dark' | 'system';
1818

1919
export default function Preferences() {
2020
const { user } = useContext(UserContext);
21-
const [ defaultIde, setDefaultIde ] = useState<string>(user?.additionalData?.ideSettings?.defaultIde || 'code');
21+
const [ defaultIde, setDefaultIde ] = useState<string>(user?.additionalData?.ideSettings?.defaultIde || 'theia');
2222
const actuallySetDefaultIde = async (value: string) => {
2323
const additionalData = user?.additionalData || {};
2424
const settings = additionalData.ideSettings || {};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
import { User } from "@gitpod/gitpod-protocol";
8+
import { getGitpodService } from "../service/service";
9+
import { WhatsNewEntry } from "./WhatsNew";
10+
11+
export const switchToVSCodeAction = async (user: User) => {
12+
const additionalData = user.additionalData = user.additionalData || {};
13+
// make sure code is set as the default IDE
14+
const ideSettings = additionalData.ideSettings = additionalData.ideSettings || {};
15+
ideSettings.defaultIde = 'code';
16+
user = await getGitpodService().server.updateLoggedInUser({
17+
additionalData
18+
});
19+
return user;
20+
};
21+
22+
export const WhatsNewEntry202104: WhatsNewEntry = {
23+
newsKey: 'April-2021',
24+
maxUserCreationDate: '2021-04-08',
25+
children: () => <>
26+
<div className="border-t border-gray-200 dark:border-gray-800 -mx-6 px-6 py-4">
27+
<p className="pb-2 text-gray-900 dark:text-gray-100 text-base font-medium">New Dashboard</p>
28+
<p className="pb-2 text-gray-500 dark:text-gray-400 text-sm">We have made some layout changes on the dashboard to improve the overall user experience of Gitpod.</p>
29+
</div>
30+
<div className="border-t border-b border-gray-200 dark:border-gray-800 -mx-6 px-6 py-4">
31+
<p className="pb-2 text-gray-900 dark:text-gray-100 text-base font-medium">VS Code</p>
32+
<p className="pb-4 text-gray-500 dark:text-gray-400 text-sm">We are changing the default IDE to VS Code.</p>
33+
<ol className="pb-2 text-gray-500 dark:text-gray-400 text-sm list-outside list-decimal space-y-2">
34+
<li className="ml-5">
35+
<div>
36+
<p className="text-gray-500 dark:text-gray-400 text-sm">We're preserving most <span className="font-bold">user settings and extensions</span>.</p>
37+
<p className="text-gray-400 dark:text-gray-500 text-sm">Extensions you have manually uploaded are not transferred. You'll need to search and install those extensions through the extension panel in VS Code.</p>
38+
</div>
39+
</li>
40+
<li className="ml-5">
41+
<div>
42+
<p className="text-gray-500 dark:text-gray-400 text-sm">We've reduced the number of <span className="font-bold">pre-installed extensions</span>.</p>
43+
<p className="text-gray-400 dark:text-gray-500 text-sm">The Theia-based editor included pre-installed extensions for the most popular programming languages which was convenient for starters but added additional bloat. You can now install any extensions you need and leave out those you don't.</p>
44+
</div>
45+
</li>
46+
<li className="ml-5">
47+
<div>
48+
<p className="text-gray-500 dark:text-gray-400 text-sm">You can still <span className="font-bold">switch the IDE</span> back to Theia.</p>
49+
<p className="text-gray-400 dark:text-gray-500 text-sm">In case you run into trouble with VS Code, you can go to the settings and switch back to the Theia.</p>
50+
</div>
51+
</li>
52+
</ol>
53+
</div>
54+
</>,
55+
actionAfterSeen: switchToVSCodeAction,
56+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
import { User } from "@gitpod/gitpod-protocol";
8+
import { WhatsNewEntry } from "./WhatsNew";
9+
import { switchToVSCodeAction } from "./WhatsNew-2021-04";
10+
import CodeText from "../components/CodeText";
11+
import PillLabel from "../components/PillLabel";
12+
13+
export const WhatsNewEntry202106: WhatsNewEntry = {
14+
children: (user: User, setUser: React.Dispatch<User>) => {
15+
16+
return <>
17+
<div className="border-t border-b border-gray-200 dark:border-gray-800 -mx-6 px-6 pt-6 pb-4">
18+
<p className="pb-2 text-gray-900 dark:text-gray-100 text-base font-medium">Exposing Ports <PillLabel>Configuration Update</PillLabel></p>
19+
<p className="pb-2 text-gray-500 dark:text-gray-400 text-sm">We've changed the default behavior of exposed ports to improve the security of your dev environments.</p>
20+
<p className="pb-2 text-gray-500 dark:text-gray-400 text-sm">Exposing ports are now private by default. You can still change port visibility through the editor or even configure this with the <CodeText>visibility</CodeText> property in <CodeText>.gitpod.yml</CodeText>.</p>
21+
</div>
22+
{ user.additionalData?.ideSettings?.defaultIde !== "code" && <>
23+
<div className="border-b border-gray-200 dark:border-gray-800 -mx-6 px-6 pt-6 pb-4">
24+
<p className="pb-2 text-gray-900 dark:text-gray-100 text-base font-medium">New Editor <PillLabel type="warn">Deprecation Warning</PillLabel></p>
25+
<p className="pb-2 text-gray-500 dark:text-gray-400 text-sm">We're deprecating the Theia editor. You can still switch back to Theia for the next few weeks but the preference will be removed by the end of August 2021.</p>
26+
</div>
27+
</>}
28+
</>;
29+
},
30+
newsKey: 'June-2021',
31+
maxUserCreationDate: '2021-06-30', // TODO
32+
actionAfterSeen: switchToVSCodeAction,
33+
};
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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+
import { User } from "@gitpod/gitpod-protocol";
8+
import Modal from "../components/Modal";
9+
import { WhatsNewEntry202104 } from "./WhatsNew-2021-04";
10+
import { WhatsNewEntry202106 } from "./WhatsNew-2021-06";
11+
import { UserContext } from "../user-context";
12+
import { useContext, useState } from "react";
13+
import { getGitpodService } from "../service/service";
14+
15+
const allEntries: WhatsNewEntry[] = [
16+
WhatsNewEntry202106,
17+
WhatsNewEntry202104,
18+
]
19+
20+
export const shouldSeeWhatsNew = (user: User, news: { newsKey: string, maxUserCreationDate: string }[] = allEntries) => {
21+
const whatsNewSeen = user?.additionalData?.whatsNewSeen;
22+
return news.some(x => user.creationDate <= x.maxUserCreationDate && (!whatsNewSeen || !whatsNewSeen[x.newsKey]));
23+
}
24+
25+
export function WhatsNew(props: { onClose: () => void }) {
26+
const { user, setUser } = useContext(UserContext);
27+
28+
const _unseenEntries = allEntries.filter(x => user && shouldSeeWhatsNew(user, [x])) || [];
29+
const [visibleEntry, setVisibleEntry] = useState(_unseenEntries.pop());
30+
const [unseenEntries, setUnseenEntries] = useState(_unseenEntries);
31+
32+
const markAsSeen = async (user?: User, ...news: (WhatsNewEntry | undefined)[]) => {
33+
if (!user) {
34+
return;
35+
}
36+
37+
for (const n of news.filter(x => x && x.actionAfterSeen)) {
38+
user = await n!.actionAfterSeen!(user);
39+
};
40+
41+
const additionalData = user.additionalData = user.additionalData || {};
42+
additionalData.whatsNewSeen = additionalData.whatsNewSeen || {};
43+
const now = new Date().toISOString();
44+
for (const newsKey of (news.filter(x => x !== undefined) as WhatsNewEntry[]).map(x => x.newsKey)) {
45+
additionalData.whatsNewSeen[newsKey] = now;
46+
}
47+
user = await getGitpodService().server.updateLoggedInUser({
48+
additionalData
49+
});
50+
setUser(user);
51+
};
52+
53+
const internalClose = async () => {
54+
await markAsSeen(user, ...unseenEntries, visibleEntry);
55+
props.onClose();
56+
};
57+
58+
const hasNext = () => unseenEntries.length > 0;
59+
60+
const next = async () => {
61+
if (unseenEntries.length === 0) {
62+
return;
63+
}
64+
visibleEntry && await markAsSeen(user, visibleEntry);
65+
const _unseenEntries = unseenEntries;
66+
setVisibleEntry(_unseenEntries.pop());
67+
setUnseenEntries(_unseenEntries);
68+
};
69+
70+
return <Modal
71+
visible={!!visibleEntry}
72+
onClose={internalClose}
73+
>
74+
<h3 className="pb-4">What's New 🎁</h3>
75+
<>{visibleEntry && user ? visibleEntry.children(user, setUser) : <></>}</>
76+
{
77+
hasNext() ?
78+
<>
79+
<div className="flex items-center justify-end mt-6 space-x-2">
80+
<div className="text-sm mr-auto italic">{unseenEntries.length} page{unseenEntries.length > 1 ? "s" : ""} left</div>
81+
<button className="ml-2 secondary" onClick={internalClose}>Dismiss All</button>
82+
<button className="ml-2" onClick={next}>Next</button>
83+
</div>
84+
</>
85+
:
86+
<div className="flex justify-end mt-6 space-x-2">
87+
<button onClick={internalClose}>Continue</button>
88+
</div>
89+
}
90+
</Modal>
91+
}
92+
93+
export interface WhatsNewEntry {
94+
newsKey: string,
95+
maxUserCreationDate: string,
96+
children: (user: User, setUser: React.Dispatch<User>) => React.ReactChild[] | React.ReactChild,
97+
actionAfterSeen?: (user: User) => Promise<User>;
98+
}

0 commit comments

Comments
 (0)