mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
571 lines
26 KiB
TypeScript
571 lines
26 KiB
TypeScript
/**
|
|
* 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 React, { Suspense, useContext, useEffect, useState } from "react";
|
|
import Menu from "./Menu";
|
|
import { Redirect, Route, Switch } from "react-router";
|
|
|
|
import { Login } from "./Login";
|
|
import { UserContext } from "./user-context";
|
|
import { getSelectedTeamSlug, TeamsContext } from "./teams/teams-context";
|
|
import { ThemeContext } from "./theme-context";
|
|
import { getGitpodService } from "./service/service";
|
|
import { shouldSeeWhatsNew, WhatsNew } from "./whatsnew/WhatsNew";
|
|
import gitpodIcon from "./icons/gitpod.svg";
|
|
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
|
|
import { useHistory } from "react-router-dom";
|
|
import { trackButtonOrAnchor, trackPathChange, trackLocation } from "./Analytics";
|
|
import { ContextURL, User } from "@gitpod/gitpod-protocol";
|
|
import * as GitpodCookie from "@gitpod/gitpod-protocol/lib/util/gitpod-cookie";
|
|
import { Experiment } from "./experiments";
|
|
import { workspacesPathMain } from "./workspaces/workspaces.routes";
|
|
import {
|
|
settingsPathAccount,
|
|
settingsPathBilling,
|
|
settingsPathIntegrations,
|
|
settingsPathMain,
|
|
settingsPathNotifications,
|
|
settingsPathPlans,
|
|
settingsPathPreferences,
|
|
settingsPathTeams,
|
|
settingsPathTeamsJoin,
|
|
settingsPathTeamsNew,
|
|
settingsPathVariables,
|
|
settingsPathSSHKeys,
|
|
usagePathMain,
|
|
settingsPathPersonalAccessTokens,
|
|
} from "./settings/settings.routes";
|
|
import {
|
|
projectsPathInstallGitHubApp,
|
|
projectsPathMain,
|
|
projectsPathMainWithParams,
|
|
projectsPathNew,
|
|
} from "./projects/projects.routes";
|
|
import { refreshSearchData } from "./components/RepositoryFinder";
|
|
import { StartWorkspaceModal } from "./workspaces/StartWorkspaceModal";
|
|
import { parseProps } from "./start/StartWorkspace";
|
|
import SelectIDEModal from "./settings/SelectIDEModal";
|
|
import { StartPage, StartPhase } from "./start/StartPage";
|
|
import { isGitpodIo, isLocalPreview } from "./utils";
|
|
import Alert from "./components/Alert";
|
|
import { BlockedRepositories } from "./admin/BlockedRepositories";
|
|
import { AppNotifications } from "./AppNotifications";
|
|
import { publicApiTeamsToProtocol, teamsService } from "./service/public-api";
|
|
import { FeatureFlagContext } from "./contexts/FeatureFlagContext";
|
|
import PersonalAccessTokens from "./settings/PersonalAccessTokens";
|
|
|
|
const Setup = React.lazy(() => import(/* webpackPrefetch: true */ "./Setup"));
|
|
const Workspaces = React.lazy(() => import(/* webpackPrefetch: true */ "./workspaces/Workspaces"));
|
|
const Account = React.lazy(() => import(/* webpackPrefetch: true */ "./settings/Account"));
|
|
const Notifications = React.lazy(() => import(/* webpackPrefetch: true */ "./settings/Notifications"));
|
|
const Billing = React.lazy(() => import(/* webpackPrefetch: true */ "./settings/Billing"));
|
|
const Plans = React.lazy(() => import(/* webpackPrefetch: true */ "./settings/Plans"));
|
|
const Teams = React.lazy(() => import(/* webpackPrefetch: true */ "./settings/Teams"));
|
|
const EnvironmentVariables = React.lazy(() => import(/* webpackPrefetch: true */ "./settings/EnvironmentVariables"));
|
|
const SSHKeys = React.lazy(() => import(/* webpackPrefetch: true */ "./settings/SSHKeys"));
|
|
const Integrations = React.lazy(() => import(/* webpackPrefetch: true */ "./settings/Integrations"));
|
|
const Preferences = React.lazy(() => import(/* webpackPrefetch: true */ "./settings/Preferences"));
|
|
const Open = React.lazy(() => import(/* webpackPrefetch: true */ "./start/Open"));
|
|
const StartWorkspace = React.lazy(() => import(/* webpackPrefetch: true */ "./start/StartWorkspace"));
|
|
const CreateWorkspace = React.lazy(() => import(/* webpackPrefetch: true */ "./start/CreateWorkspace"));
|
|
const NewTeam = React.lazy(() => import(/* webpackPrefetch: true */ "./teams/NewTeam"));
|
|
const JoinTeam = React.lazy(() => import(/* webpackPrefetch: true */ "./teams/JoinTeam"));
|
|
const Members = React.lazy(() => import(/* webpackPrefetch: true */ "./teams/Members"));
|
|
const TeamSettings = React.lazy(() => import(/* webpackPrefetch: true */ "./teams/TeamSettings"));
|
|
const TeamBilling = React.lazy(() => import(/* webpackPrefetch: true */ "./teams/TeamBilling"));
|
|
const TeamUsage = React.lazy(() => import(/* webpackPrefetch: true */ "./teams/TeamUsage"));
|
|
const NewProject = React.lazy(() => import(/* webpackPrefetch: true */ "./projects/NewProject"));
|
|
const Projects = React.lazy(() => import(/* webpackPrefetch: true */ "./projects/Projects"));
|
|
const Project = React.lazy(() => import(/* webpackPrefetch: true */ "./projects/Project"));
|
|
const Events = React.lazy(() => import(/* webpackPrefetch: true */ "./projects/Events"));
|
|
const ProjectSettings = React.lazy(() => import(/* webpackPrefetch: true */ "./projects/ProjectSettings"));
|
|
const ProjectVariables = React.lazy(() => import(/* webpackPrefetch: true */ "./projects/ProjectVariables"));
|
|
const Prebuilds = React.lazy(() => import(/* webpackPrefetch: true */ "./projects/Prebuilds"));
|
|
const Prebuild = React.lazy(() => import(/* webpackPrefetch: true */ "./projects/Prebuild"));
|
|
const InstallGitHubApp = React.lazy(() => import(/* webpackPrefetch: true */ "./projects/InstallGitHubApp"));
|
|
const FromReferrer = React.lazy(() => import(/* webpackPrefetch: true */ "./FromReferrer"));
|
|
const UserSearch = React.lazy(() => import(/* webpackPrefetch: true */ "./admin/UserSearch"));
|
|
const WorkspacesSearch = React.lazy(() => import(/* webpackPrefetch: true */ "./admin/WorkspacesSearch"));
|
|
const AdminSettings = React.lazy(() => import(/* webpackPrefetch: true */ "./admin/Settings"));
|
|
const ProjectsSearch = React.lazy(() => import(/* webpackPrefetch: true */ "./admin/ProjectsSearch"));
|
|
const TeamsSearch = React.lazy(() => import(/* webpackPrefetch: true */ "./admin/TeamsSearch"));
|
|
const OAuthClientApproval = React.lazy(() => import(/* webpackPrefetch: true */ "./OauthClientApproval"));
|
|
const License = React.lazy(() => import(/* webpackPrefetch: true */ "./admin/License"));
|
|
const Usage = React.lazy(() => import(/* webpackPrefetch: true */ "./Usage"));
|
|
|
|
function Loading() {
|
|
return <></>;
|
|
}
|
|
|
|
function isWebsiteSlug(pathName: string) {
|
|
const slugs = [
|
|
"about",
|
|
"blog",
|
|
"careers",
|
|
"cde",
|
|
"changelog",
|
|
"chat",
|
|
"code-of-conduct",
|
|
"contact",
|
|
"docs",
|
|
"features",
|
|
"for",
|
|
"gitpod-vs-github-codespaces",
|
|
"imprint",
|
|
"media-kit",
|
|
"memes",
|
|
"pricing",
|
|
"privacy",
|
|
"security",
|
|
"screencasts",
|
|
"self-hosted",
|
|
"support",
|
|
"terms",
|
|
"values",
|
|
];
|
|
return slugs.some((slug) => pathName.startsWith("/" + slug + "/") || pathName === "/" + slug);
|
|
}
|
|
|
|
// A wrapper for <Route> that redirects to the workspaces screen if the user isn't a admin.
|
|
// This wrapper only accepts the component property
|
|
function AdminRoute({ component }: any) {
|
|
const { user } = useContext(UserContext);
|
|
return (
|
|
<Route
|
|
render={({ location }: any) =>
|
|
user?.rolesOrPermissions?.includes("admin") ? (
|
|
<Route component={component}></Route>
|
|
) : (
|
|
<Redirect
|
|
to={{
|
|
pathname: "/workspaces",
|
|
state: { from: location },
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export function getURLHash() {
|
|
return window.location.hash.replace(/^[#/]+/, "");
|
|
}
|
|
|
|
function App() {
|
|
const { user, setUser, refreshUserBillingMode } = useContext(UserContext);
|
|
const { teams, setTeams } = useContext(TeamsContext);
|
|
const { setIsDark } = useContext(ThemeContext);
|
|
const { usePublicApiTeamsService } = useContext(FeatureFlagContext);
|
|
|
|
const [loading, setLoading] = useState<boolean>(true);
|
|
const [isWhatsNewShown, setWhatsNewShown] = useState(false);
|
|
const [showUserIdePreference, setShowUserIdePreference] = useState(false);
|
|
const [isSetupRequired, setSetupRequired] = useState(false);
|
|
const history = useHistory();
|
|
|
|
useEffect(() => {
|
|
(async () => {
|
|
var user: User | undefined;
|
|
try {
|
|
user = await getGitpodService().server.getLoggedInUser();
|
|
setUser(user);
|
|
|
|
const teams = usePublicApiTeamsService
|
|
? publicApiTeamsToProtocol((await teamsService.listTeams({})).teams)
|
|
: await getGitpodService().server.getTeams();
|
|
|
|
{
|
|
// if a team was selected previously and we call the root URL (e.g. "gitpod.io"),
|
|
// let's continue with the team page
|
|
const hash = getURLHash();
|
|
const isRoot = window.location.pathname === "/" && hash === "";
|
|
if (isRoot) {
|
|
try {
|
|
const teamSlug = getSelectedTeamSlug();
|
|
if (teams.some((t) => t.slug === teamSlug)) {
|
|
history.push(`/t/${teamSlug}`);
|
|
}
|
|
} catch {}
|
|
}
|
|
}
|
|
setTeams(teams);
|
|
} catch (error) {
|
|
console.error(error);
|
|
if (error && "code" in error) {
|
|
if (error.code === ErrorCodes.SETUP_REQUIRED) {
|
|
setSetupRequired(true);
|
|
}
|
|
}
|
|
} finally {
|
|
trackLocation(!!user);
|
|
}
|
|
setLoading(false);
|
|
(window as any)._gp.path = window.location.pathname; //store current path to have access to previous when path changes
|
|
})();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const updateTheme = () => {
|
|
const isDark =
|
|
localStorage.theme === "dark" ||
|
|
(localStorage.theme !== "light" && window.matchMedia("(prefers-color-scheme: dark)").matches);
|
|
setIsDark(isDark);
|
|
};
|
|
updateTheme();
|
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
if (mediaQuery instanceof EventTarget) {
|
|
mediaQuery.addEventListener("change", updateTheme);
|
|
} else {
|
|
// backward compatibility for Safari < 14
|
|
(mediaQuery as MediaQueryList).addListener(updateTheme);
|
|
}
|
|
window.addEventListener("storage", updateTheme);
|
|
return function cleanup() {
|
|
if (mediaQuery instanceof EventTarget) {
|
|
mediaQuery.removeEventListener("change", updateTheme);
|
|
} else {
|
|
// backward compatibility for Safari < 14
|
|
(mediaQuery as MediaQueryList).removeListener(updateTheme);
|
|
}
|
|
window.removeEventListener("storage", updateTheme);
|
|
};
|
|
}, []);
|
|
|
|
// listen and notify Segment of client-side path updates
|
|
useEffect(() => {
|
|
if (isGitpodIo()) {
|
|
// Choose which experiments to run for this session/user
|
|
Experiment.set(Experiment.seed(true));
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
return history.listen((location: any) => {
|
|
const path = window.location.pathname;
|
|
trackPathChange({
|
|
prev: (window as any)._gp.path,
|
|
path: path,
|
|
});
|
|
(window as any)._gp.path = path;
|
|
});
|
|
}, [history]);
|
|
|
|
useEffect(() => {
|
|
const handleButtonOrAnchorTracking = (props: MouseEvent) => {
|
|
var curr = props.target as HTMLElement;
|
|
//check if current target or any ancestor up to document is button or anchor
|
|
while (!(curr instanceof Document)) {
|
|
if (
|
|
curr instanceof HTMLButtonElement ||
|
|
curr instanceof HTMLAnchorElement ||
|
|
(curr instanceof HTMLDivElement && curr.onclick)
|
|
) {
|
|
trackButtonOrAnchor(curr);
|
|
break; //finding first ancestor is sufficient
|
|
}
|
|
curr = curr.parentNode as HTMLElement;
|
|
}
|
|
};
|
|
window.addEventListener("click", handleButtonOrAnchorTracking, true);
|
|
return () => window.removeEventListener("click", handleButtonOrAnchorTracking, true);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (user) {
|
|
refreshSearchData("", user);
|
|
}
|
|
}, [user]);
|
|
|
|
useEffect(() => {
|
|
if (!teams) {
|
|
return;
|
|
}
|
|
// Refresh billing mode (side effect on other components per UserContext!)
|
|
refreshUserBillingMode();
|
|
}, [teams]);
|
|
|
|
// redirect to website for any website slugs
|
|
if (isGitpodIo() && isWebsiteSlug(window.location.pathname)) {
|
|
window.location.host = "www.gitpod.io";
|
|
return <div></div>;
|
|
}
|
|
|
|
if (isGitpodIo() && window.location.pathname === "/" && window.location.hash === "" && !loading && !user) {
|
|
if (!GitpodCookie.isPresent(document.cookie)) {
|
|
window.location.href = `https://www.gitpod.io`;
|
|
return <div></div>;
|
|
} else {
|
|
// explicitly render the Login page when the session is out-of-sync with the Gitpod cookie
|
|
return <Login />;
|
|
}
|
|
}
|
|
|
|
if (loading) {
|
|
return <Loading />;
|
|
}
|
|
if (isSetupRequired) {
|
|
return (
|
|
<Suspense fallback={<Loading />}>
|
|
<Setup />
|
|
</Suspense>
|
|
);
|
|
}
|
|
if (!user) {
|
|
return <Login />;
|
|
}
|
|
|
|
if (window.location.pathname.startsWith("/blocked")) {
|
|
return (
|
|
<div className="mt-48 text-center">
|
|
<img src={gitpodIcon} className="h-16 mx-auto" alt="Gitpod's logo" />
|
|
<h1 className="mt-12 text-gray-500 text-3xl">Your account has been blocked.</h1>
|
|
<p className="mt-4 mb-8 text-lg w-96 mx-auto">
|
|
Please contact support if you think this is an error. See also{" "}
|
|
<a className="hover:text-blue-600 dark:hover:text-blue-400" href="https://www.gitpod.io/terms/">
|
|
terms of service
|
|
</a>
|
|
.
|
|
</p>
|
|
<a className="mx-auto" href="mailto:support@gitpod.io?Subject=Blocked">
|
|
<button className="secondary">Contact Support</button>
|
|
</a>
|
|
</div>
|
|
);
|
|
}
|
|
const shouldWhatsNewShown = shouldSeeWhatsNew(user);
|
|
if (shouldWhatsNewShown !== isWhatsNewShown) {
|
|
setWhatsNewShown(shouldWhatsNewShown);
|
|
}
|
|
if (window.location.pathname.startsWith("/oauth-approval")) {
|
|
return (
|
|
<Suspense fallback={<Loading />}>
|
|
<OAuthClientApproval />
|
|
</Suspense>
|
|
);
|
|
}
|
|
|
|
window.addEventListener(
|
|
"hashchange",
|
|
() => {
|
|
// Refresh on hash change if the path is '/' (new context URL)
|
|
if (window.location.pathname === "/") {
|
|
window.location.reload();
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
|
|
let toRender: React.ReactElement = (
|
|
<Route>
|
|
<div className="container">
|
|
<Menu />
|
|
{isLocalPreview() && (
|
|
<div className="app-container mt-2">
|
|
<Alert type="warning" className="app-container rounded-md">
|
|
You are using a <b>local preview</b> installation, intended for exploring the product on a
|
|
single machine without requiring a Kubernetes cluster.{" "}
|
|
<a
|
|
className="gp-link hover:text-gray-600"
|
|
href="https://www.gitpod.io/community-license?utm_source=local-preview"
|
|
>
|
|
Request a community license
|
|
</a>{" "}
|
|
or{" "}
|
|
<a
|
|
className="gp-link hover:text-gray-600"
|
|
href="https://www.gitpod.io/contact/sales?utm_source=local-preview"
|
|
>
|
|
contact sales
|
|
</a>{" "}
|
|
to get a professional license for running Gitpod in production.
|
|
</Alert>
|
|
</div>
|
|
)}
|
|
<AppNotifications />
|
|
<Switch>
|
|
<Route path={projectsPathNew} exact component={NewProject} />
|
|
<Route path="/open" exact component={Open} />
|
|
<Route path="/setup" exact component={Setup} />
|
|
<Route path={workspacesPathMain} exact component={Workspaces} />
|
|
<Route path={settingsPathAccount} exact component={Account} />
|
|
<Route path={usagePathMain} exact component={Usage} />
|
|
<Route path={settingsPathIntegrations} exact component={Integrations} />
|
|
<Route path={settingsPathNotifications} exact component={Notifications} />
|
|
<Route path={settingsPathBilling} exact component={Billing} />
|
|
<Route path={settingsPathPlans} exact component={Plans} />
|
|
<Route path={settingsPathPersonalAccessTokens} exact component={PersonalAccessTokens} />
|
|
<Route path={settingsPathVariables} exact component={EnvironmentVariables} />
|
|
<Route path={settingsPathSSHKeys} exact component={SSHKeys} />
|
|
<Route path={settingsPathPreferences} exact component={Preferences} />
|
|
<Route path={projectsPathInstallGitHubApp} exact component={InstallGitHubApp} />
|
|
<Route path="/from-referrer" exact component={FromReferrer} />
|
|
|
|
<AdminRoute path="/admin/users" component={UserSearch} />
|
|
<AdminRoute path="/admin/teams" component={TeamsSearch} />
|
|
<AdminRoute path="/admin/workspaces" component={WorkspacesSearch} />
|
|
<AdminRoute path="/admin/projects" component={ProjectsSearch} />
|
|
<AdminRoute path="/admin/blocked-repositories" component={BlockedRepositories} />
|
|
<AdminRoute path="/admin/license" component={License} />
|
|
<AdminRoute path="/admin/settings" component={AdminSettings} />
|
|
|
|
<Route path={["/", "/login"]} exact>
|
|
<Redirect to={workspacesPathMain} />
|
|
</Route>
|
|
<Route path={[settingsPathMain]} exact>
|
|
<Redirect to={settingsPathAccount} />
|
|
</Route>
|
|
<Route path={["/access-control"]} exact>
|
|
<Redirect to={settingsPathIntegrations} />
|
|
</Route>
|
|
<Route path={["/subscription", "/usage", "/upgrade-subscription"]} exact>
|
|
<Redirect to={settingsPathPlans} />
|
|
</Route>
|
|
<Route path={["/admin"]} exact>
|
|
<Redirect to="/admin/users" />
|
|
</Route>
|
|
<Route path="/sorry" exact>
|
|
<div className="mt-48 text-center">
|
|
<h1 className="text-gray-500 text-3xl">Oh, no! Something went wrong!</h1>
|
|
<p className="mt-4 text-lg text-gitpod-red">{decodeURIComponent(getURLHash())}</p>
|
|
</div>
|
|
</Route>
|
|
<Route path={projectsPathMain}>
|
|
<Route exact path={projectsPathMain} component={Projects} />
|
|
<Route
|
|
exact
|
|
path={projectsPathMainWithParams}
|
|
render={({ match }) => {
|
|
const { resourceOrPrebuild } = match.params;
|
|
switch (resourceOrPrebuild) {
|
|
case "events":
|
|
return <Events />;
|
|
case "prebuilds":
|
|
return <Prebuilds />;
|
|
case "settings":
|
|
return <ProjectSettings />;
|
|
case "variables":
|
|
return <ProjectVariables />;
|
|
default:
|
|
return resourceOrPrebuild ? <Prebuild /> : <Project />;
|
|
}
|
|
}}
|
|
/>
|
|
</Route>
|
|
<Route path={settingsPathTeams}>
|
|
<Route exact path={settingsPathTeams} component={Teams} />
|
|
<Route exact path={settingsPathTeamsNew} component={NewTeam} />
|
|
<Route exact path={settingsPathTeamsJoin} component={JoinTeam} />
|
|
</Route>
|
|
{(teams || []).map((team) => (
|
|
<Route path={`/t/${team.slug}`} key={team.slug}>
|
|
<Route exact path={`/t/${team.slug}`}>
|
|
<Redirect to={`/t/${team.slug}/projects`} />
|
|
</Route>
|
|
<Route
|
|
exact
|
|
path={`/t/${team.slug}/:maybeProject/:resourceOrPrebuild?`}
|
|
render={({ match }) => {
|
|
const { maybeProject, resourceOrPrebuild } = match.params;
|
|
switch (maybeProject) {
|
|
case "projects":
|
|
return <Projects />;
|
|
case "workspaces":
|
|
return <Workspaces />;
|
|
case "members":
|
|
return <Members />;
|
|
case "settings":
|
|
return <TeamSettings />;
|
|
case "billing":
|
|
return <TeamBilling />;
|
|
case "usage":
|
|
return <TeamUsage />;
|
|
default:
|
|
break;
|
|
}
|
|
switch (resourceOrPrebuild) {
|
|
case "events":
|
|
return <Events />;
|
|
case "prebuilds":
|
|
return <Prebuilds />;
|
|
case "settings":
|
|
return <ProjectSettings />;
|
|
case "variables":
|
|
return <ProjectVariables />;
|
|
default:
|
|
return resourceOrPrebuild ? <Prebuild /> : <Project />;
|
|
}
|
|
}}
|
|
/>
|
|
</Route>
|
|
))}
|
|
<Route
|
|
path="*"
|
|
render={(_match) => {
|
|
return isGitpodIo() ? (
|
|
// delegate to our website to handle the request
|
|
(window.location.host = "www.gitpod.io")
|
|
) : (
|
|
<div className="mt-48 text-center">
|
|
<h1 className="text-gray-500 text-3xl">404</h1>
|
|
<p className="mt-4 text-lg">Page not found.</p>
|
|
</div>
|
|
);
|
|
}}
|
|
></Route>
|
|
</Switch>
|
|
<StartWorkspaceModal />
|
|
</div>
|
|
</Route>
|
|
);
|
|
|
|
const hash = getURLHash();
|
|
if (/^(https:\/\/)?github\.dev\//i.test(hash)) {
|
|
window.location.hash = hash.replace(/^(https:\/\/)?github\.dev\//i, "https://github.com/");
|
|
return <div></div>;
|
|
} else if (/^([^\/]+?=[^\/]*?|prebuild)\/(https:\/\/)?github\.dev\//i.test(hash)) {
|
|
window.location.hash = hash.replace(
|
|
/^([^\/]+?=[^\/]*?|prebuild)\/(https:\/\/)?github\.dev\//i,
|
|
"$1/https://github.com/",
|
|
);
|
|
return <div></div>;
|
|
}
|
|
// Prefix with `/#referrer` will specify an IDE for workspace
|
|
// We don't need to show IDE preference in this case
|
|
const shouldUserIdePreferenceShown = User.isOnboardingUser(user) && !hash.startsWith(ContextURL.REFERRER_PREFIX);
|
|
if (shouldUserIdePreferenceShown !== showUserIdePreference) {
|
|
setShowUserIdePreference(shouldUserIdePreferenceShown);
|
|
}
|
|
|
|
const isCreation = window.location.pathname === "/" && hash !== "";
|
|
const isWsStart = /\/start\/?/.test(window.location.pathname) && hash !== "";
|
|
if (isWhatsNewShown) {
|
|
toRender = <WhatsNew onClose={() => setWhatsNewShown(false)} />;
|
|
} else if (isCreation) {
|
|
if (showUserIdePreference) {
|
|
toRender = (
|
|
<StartPage phase={StartPhase.Checking}>
|
|
<SelectIDEModal location="workspace_start" onClose={() => setShowUserIdePreference(false)} />
|
|
</StartPage>
|
|
);
|
|
} else {
|
|
toRender = <CreateWorkspace contextUrl={hash} />;
|
|
}
|
|
} else if (isWsStart) {
|
|
toRender = <StartWorkspace {...parseProps(hash, window.location.search)} />;
|
|
} else if (/^(github|gitlab)\.com\/.+?/i.test(window.location.pathname)) {
|
|
let url = new URL(window.location.href);
|
|
url.hash = url.pathname;
|
|
url.pathname = "/";
|
|
window.location.replace(url);
|
|
return <div></div>;
|
|
}
|
|
|
|
return <Suspense fallback={<Loading />}>{toRender}</Suspense>;
|
|
}
|
|
|
|
export default App;
|