From b8eb279229e4ae3ff439c37ebaf87eac099d19ee Mon Sep 17 00:00:00 2001 From: "Cornelius A. Ludmann" Date: Fri, 25 Jun 2021 10:10:04 +0000 Subject: [PATCH 1/2] [dashboard] Introduce component `CodeText` --- components/dashboard/src/components/CodeText.tsx | 9 +++++++++ components/dashboard/src/settings/Account.tsx | 3 ++- .../dashboard/src/settings/EnvironmentVariables.tsx | 5 +++-- 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 components/dashboard/src/components/CodeText.tsx diff --git a/components/dashboard/src/components/CodeText.tsx b/components/dashboard/src/components/CodeText.tsx new file mode 100644 index 00000000000000..df8b862c326463 --- /dev/null +++ b/components/dashboard/src/components/CodeText.tsx @@ -0,0 +1,9 @@ +/** + * Copyright (c) 2021 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. + */ + +export default function CodeText(p: { children?: React.ReactNode }) { + return {p.children}; +} diff --git a/components/dashboard/src/settings/Account.tsx b/components/dashboard/src/settings/Account.tsx index f0899246d18ed8..1a7e5e85829879 100644 --- a/components/dashboard/src/settings/Account.tsx +++ b/components/dashboard/src/settings/Account.tsx @@ -11,6 +11,7 @@ import { getGitpodService, gitpodHostUrl } from "../service/service"; import { UserContext } from "../user-context"; import settingsMenu from "./settings-menu"; import ConfirmationModal from "../components/ConfirmationModal"; +import CodeText from "../components/CodeText"; export default function Account() { const { user } = useContext(UserContext); @@ -46,7 +47,7 @@ export default function Account() {

Profile

-

The following information will be used to set up git configuration. You can override git author name and email per project by using the default environment variables GIT_AUTHOR_NAME and GIT_COMMITTER_EMAIL.

+

The following information will be used to set up git configuration. You can override git author name and email per project by using the default environment variables GIT_AUTHOR_NAME and GIT_COMMITTER_EMAIL.

diff --git a/components/dashboard/src/settings/EnvironmentVariables.tsx b/components/dashboard/src/settings/EnvironmentVariables.tsx index 22274505eea9a6..abc47768a67686 100644 --- a/components/dashboard/src/settings/EnvironmentVariables.tsx +++ b/components/dashboard/src/settings/EnvironmentVariables.tsx @@ -5,13 +5,14 @@ */ import { UserEnvVarValue } from "@gitpod/gitpod-protocol"; -import React, { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import ConfirmationModal from "../components/ConfirmationModal"; import { Item, ItemField, ItemFieldContextMenu, ItemsList } from "../components/ItemsList"; import Modal from "../components/Modal"; import { PageWithSubMenu } from "../components/PageWithSubMenu"; import { getGitpodService } from "../service/service"; import settingsMenu from "./settings-menu"; +import CodeText from "../components/CodeText"; interface EnvVarModalProps { envVar: UserEnvVarValue; @@ -70,7 +71,7 @@ function AddEnvVarModal(p: EnvVarModalProps) { onChange={(v) => { update({repositoryPattern: v.target.value}) }} />
-

You can pass a variable for a specific project or use wildcard character (*/*) to make it available in more projects.

+

You can pass a variable for a specific project or use wildcard character (*/*) to make it available in more projects.

From b3e2569b110d86ab6ff60e2a7849f6848352b09f Mon Sep 17 00:00:00 2001 From: "Cornelius A. Ludmann" Date: Fri, 25 Jun 2021 09:21:06 +0000 Subject: [PATCH 2/2] [dashboard] Refactor What's New dialog --- components/dashboard/src/App.tsx | 4 +- components/dashboard/src/WhatsNew.tsx | 73 -------------- .../dashboard/src/components/CodeText.tsx | 2 +- .../dashboard/src/components/PillLabel.tsx | 19 ++++ .../dashboard/src/settings/Preferences.tsx | 2 +- .../src/whatsnew/WhatsNew-2021-04.tsx | 56 +++++++++++ .../src/whatsnew/WhatsNew-2021-06.tsx | 33 +++++++ .../dashboard/src/whatsnew/WhatsNew.tsx | 98 +++++++++++++++++++ 8 files changed, 210 insertions(+), 77 deletions(-) delete mode 100644 components/dashboard/src/WhatsNew.tsx create mode 100644 components/dashboard/src/components/PillLabel.tsx create mode 100644 components/dashboard/src/whatsnew/WhatsNew-2021-04.tsx create mode 100644 components/dashboard/src/whatsnew/WhatsNew-2021-06.tsx create mode 100644 components/dashboard/src/whatsnew/WhatsNew.tsx diff --git a/components/dashboard/src/App.tsx b/components/dashboard/src/App.tsx index 33d31ff497122a..d9334b4deb4668 100644 --- a/components/dashboard/src/App.tsx +++ b/components/dashboard/src/App.tsx @@ -13,7 +13,7 @@ import { Login } from './Login'; import { UserContext } from './user-context'; import { TeamsContext } from './teams/teams-context'; import { getGitpodService } from './service/service'; -import { shouldSeeWhatsNew, WhatsNew } from './WhatsNew'; +import { shouldSeeWhatsNew, WhatsNew } from './whatsnew/WhatsNew'; import gitpodIcon from './icons/gitpod.svg'; import { ErrorCodes } from '@gitpod/gitpod-protocol/lib/messaging/error'; @@ -234,7 +234,7 @@ function App() { const isCreation = window.location.pathname === '/' && hash !== ''; const isWsStart = /\/start\/?/.test(window.location.pathname) && hash !== ''; if (isWhatsNewShown) { - toRender = setWhatsNewShown(false)} />; + toRender = setWhatsNewShown(false)} />; } else if (isCreation) { toRender = ; } else if (isWsStart) { diff --git a/components/dashboard/src/WhatsNew.tsx b/components/dashboard/src/WhatsNew.tsx deleted file mode 100644 index f21d1c4478e04b..00000000000000 --- a/components/dashboard/src/WhatsNew.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2021 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 { User } from "@gitpod/gitpod-protocol"; -import { useContext } from "react"; -import Modal from "./components/Modal"; -import { getGitpodService } from "./service/service"; -import { UserContext } from "./user-context"; - -const news = 'April-2021'; -export function shouldSeeWhatsNew(user: User): boolean { - const whatsNewSeen = user?.additionalData?.whatsNewSeen; - return user.creationDate <= '2021-04-08' && (!whatsNewSeen || !whatsNewSeen[news]); -} - -export function WhatsNew(props: { visible: boolean, onClose: () => void }) { - const { user, setUser } = useContext(UserContext); - const internalClose = async () => { - if (!user) { - return; - } - const additionalData = user.additionalData = user.additionalData || {}; - additionalData.whatsNewSeen = { - ...additionalData.whatsNewSeen, - [news]: new Date().toISOString() - } - // make sure code is set as the default IDE - const ideSettings = additionalData.ideSettings = additionalData.ideSettings || {}; - ideSettings.defaultIde = 'code'; - await getGitpodService().server.updateLoggedInUser({ - additionalData - }); - setUser(user); - props.onClose(); - } - return -

What's New 🎁

-
-

New Dashboard

-

We have made some layout changes on the dashboard to improve the overall user experience of Gitpod.

-
-
-

VS Code

-

We are changing the default IDE to VS Code.

-
    -
  1. -
    -

    We're preserving most user settings and extensions.

    -

    Extensions you have manually uploaded are not transferred. You'll need to search and install those extensions through the extension panel in VS Code.

    -
    -
  2. -
  3. -
    -

    We've reduced the number of pre-installed extensions.

    -

    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.

    -
    -
  4. -
  5. -
    -

    You can still switch the IDE back to Theia.

    -

    In case you run into trouble with VS Code, you can go to the settings and switch back to the Theia.

    -
    -
  6. -
-
-
- -
-
-} \ No newline at end of file diff --git a/components/dashboard/src/components/CodeText.tsx b/components/dashboard/src/components/CodeText.tsx index df8b862c326463..a02d0ae0be407c 100644 --- a/components/dashboard/src/components/CodeText.tsx +++ b/components/dashboard/src/components/CodeText.tsx @@ -5,5 +5,5 @@ */ export default function CodeText(p: { children?: React.ReactNode }) { - return {p.children}; + return {p.children}; } diff --git a/components/dashboard/src/components/PillLabel.tsx b/components/dashboard/src/components/PillLabel.tsx new file mode 100644 index 00000000000000..7c71c5a8e3437b --- /dev/null +++ b/components/dashboard/src/components/PillLabel.tsx @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2021 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. + */ + +/** + * Renders a pill. + * + * **type**\ + * info: Renders a blue pile label (default).\ + * warn: Renders an orange pile label. + */ +export default function PillLabel(props: { children?: React.ReactNode, type?: "info" | "warn" }) { + const infoStyle = "bg-blue-50 text-blue-500 dark:bg-blue-500 dark:text-blue-100"; + const warnStyle = "bg-orange-100 text-orange-700 dark:bg-orange-600 dark:text-orange-100"; + const style = `ml-2 px-3 py-1 text-sm uppercase rounded-xl ${props.type === "warn" ? warnStyle : infoStyle}`; + return {props.children}; +} diff --git a/components/dashboard/src/settings/Preferences.tsx b/components/dashboard/src/settings/Preferences.tsx index f0b0ef8510350e..c9cb28af193eea 100644 --- a/components/dashboard/src/settings/Preferences.tsx +++ b/components/dashboard/src/settings/Preferences.tsx @@ -18,7 +18,7 @@ type Theme = 'light' | 'dark' | 'system'; export default function Preferences() { const { user } = useContext(UserContext); - const [ defaultIde, setDefaultIde ] = useState(user?.additionalData?.ideSettings?.defaultIde || 'code'); + const [ defaultIde, setDefaultIde ] = useState(user?.additionalData?.ideSettings?.defaultIde || 'theia'); const actuallySetDefaultIde = async (value: string) => { const additionalData = user?.additionalData || {}; const settings = additionalData.ideSettings || {}; diff --git a/components/dashboard/src/whatsnew/WhatsNew-2021-04.tsx b/components/dashboard/src/whatsnew/WhatsNew-2021-04.tsx new file mode 100644 index 00000000000000..2749f6901be7d2 --- /dev/null +++ b/components/dashboard/src/whatsnew/WhatsNew-2021-04.tsx @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2021 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 { User } from "@gitpod/gitpod-protocol"; +import { getGitpodService } from "../service/service"; +import { WhatsNewEntry } from "./WhatsNew"; + +export const switchToVSCodeAction = async (user: User) => { + const additionalData = user.additionalData = user.additionalData || {}; + // make sure code is set as the default IDE + const ideSettings = additionalData.ideSettings = additionalData.ideSettings || {}; + ideSettings.defaultIde = 'code'; + user = await getGitpodService().server.updateLoggedInUser({ + additionalData + }); + return user; +}; + +export const WhatsNewEntry202104: WhatsNewEntry = { + newsKey: 'April-2021', + maxUserCreationDate: '2021-04-08', + children: () => <> +
+

New Dashboard

+

We have made some layout changes on the dashboard to improve the overall user experience of Gitpod.

+
+
+

VS Code

+

We are changing the default IDE to VS Code.

+
    +
  1. +
    +

    We're preserving most user settings and extensions.

    +

    Extensions you have manually uploaded are not transferred. You'll need to search and install those extensions through the extension panel in VS Code.

    +
    +
  2. +
  3. +
    +

    We've reduced the number of pre-installed extensions.

    +

    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.

    +
    +
  4. +
  5. +
    +

    You can still switch the IDE back to Theia.

    +

    In case you run into trouble with VS Code, you can go to the settings and switch back to the Theia.

    +
    +
  6. +
+
+ , + actionAfterSeen: switchToVSCodeAction, +}; diff --git a/components/dashboard/src/whatsnew/WhatsNew-2021-06.tsx b/components/dashboard/src/whatsnew/WhatsNew-2021-06.tsx new file mode 100644 index 00000000000000..f3425396540d45 --- /dev/null +++ b/components/dashboard/src/whatsnew/WhatsNew-2021-06.tsx @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021 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 { User } from "@gitpod/gitpod-protocol"; +import { WhatsNewEntry } from "./WhatsNew"; +import { switchToVSCodeAction } from "./WhatsNew-2021-04"; +import CodeText from "../components/CodeText"; +import PillLabel from "../components/PillLabel"; + +export const WhatsNewEntry202106: WhatsNewEntry = { + children: (user: User, setUser: React.Dispatch) => { + + return <> +
+

Exposing Ports Configuration Update

+

We've changed the default behavior of exposed ports to improve the security of your dev environments.

+

Exposing ports are now private by default. You can still change port visibility through the editor or even configure this with the visibility property in .gitpod.yml.

+
+ { user.additionalData?.ideSettings?.defaultIde !== "code" && <> +
+

New Editor Deprecation Warning

+

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.

+
+ } + ; + }, + newsKey: 'June-2021', + maxUserCreationDate: '2021-07-01', + actionAfterSeen: switchToVSCodeAction, +}; diff --git a/components/dashboard/src/whatsnew/WhatsNew.tsx b/components/dashboard/src/whatsnew/WhatsNew.tsx new file mode 100644 index 00000000000000..83f33477d11260 --- /dev/null +++ b/components/dashboard/src/whatsnew/WhatsNew.tsx @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2021 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 { User } from "@gitpod/gitpod-protocol"; +import Modal from "../components/Modal"; +import { WhatsNewEntry202104 } from "./WhatsNew-2021-04"; +import { WhatsNewEntry202106 } from "./WhatsNew-2021-06"; +import { UserContext } from "../user-context"; +import { useContext, useState } from "react"; +import { getGitpodService } from "../service/service"; + +const allEntries: WhatsNewEntry[] = [ + WhatsNewEntry202106, + WhatsNewEntry202104, +] + +export const shouldSeeWhatsNew = (user: User, news: { newsKey: string, maxUserCreationDate: string }[] = allEntries) => { + const whatsNewSeen = user?.additionalData?.whatsNewSeen; + return news.some(x => user.creationDate <= x.maxUserCreationDate && (!whatsNewSeen || !whatsNewSeen[x.newsKey])); +} + +export function WhatsNew(props: { onClose: () => void }) { + const { user, setUser } = useContext(UserContext); + + const _unseenEntries = allEntries.filter(x => user && shouldSeeWhatsNew(user, [x])) || []; + const [visibleEntry, setVisibleEntry] = useState(_unseenEntries.pop()); + const [unseenEntries, setUnseenEntries] = useState(_unseenEntries); + + const markAsSeen = async (user?: User, ...news: (WhatsNewEntry | undefined)[]) => { + if (!user) { + return; + } + + for (const n of news.filter(x => x && x.actionAfterSeen)) { + user = await n!.actionAfterSeen!(user); + }; + + const additionalData = user.additionalData = user.additionalData || {}; + additionalData.whatsNewSeen = additionalData.whatsNewSeen || {}; + const now = new Date().toISOString(); + for (const newsKey of (news.filter(x => x !== undefined) as WhatsNewEntry[]).map(x => x.newsKey)) { + additionalData.whatsNewSeen[newsKey] = now; + } + user = await getGitpodService().server.updateLoggedInUser({ + additionalData + }); + setUser(user); + }; + + const internalClose = async () => { + await markAsSeen(user, ...unseenEntries, visibleEntry); + props.onClose(); + }; + + const hasNext = () => unseenEntries.length > 0; + + const next = async () => { + if (unseenEntries.length === 0) { + return; + } + visibleEntry && await markAsSeen(user, visibleEntry); + const _unseenEntries = unseenEntries; + setVisibleEntry(_unseenEntries.pop()); + setUnseenEntries(_unseenEntries); + }; + + return +

What's New 🎁

+ <>{visibleEntry && user ? visibleEntry.children(user, setUser) : <>} + { + hasNext() ? + <> +
+
{unseenEntries.length} more update{unseenEntries.length > 1 ? "s" : ""}
+ + +
+ + : +
+ +
+ } +
+} + +export interface WhatsNewEntry { + newsKey: string, + maxUserCreationDate: string, + children: (user: User, setUser: React.Dispatch) => React.ReactChild[] | React.ReactChild, + actionAfterSeen?: (user: User) => Promise; +}