Skip to content

Commit

Permalink
Bye tabs, hi URLs (conda-incubator#389)
Browse files Browse the repository at this point in the history
  • Loading branch information
gabalafou committed Jul 9, 2024
1 parent 00cb273 commit 19cc689
Show file tree
Hide file tree
Showing 36 changed files with 403 additions and 589 deletions.
18 changes: 3 additions & 15 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,20 @@
import { ThemeProvider } from "@mui/material";
import React from "react";
import { Provider } from "react-redux";
import { BrowserRouter as Router } from "react-router-dom";
import { Route, Routes } from "react-router";
import { RouterProvider } from "react-router-dom";

import { PageLayout } from "./layouts";
import {
IPreferences,
PrefContext,
prefDefault,
prefGlobal
} from "./preferences";
import { router } from "./routes";
import { store } from "./store";
import { condaStoreTheme, grayscaleTheme } from "./theme";

import "../style/index.css";

const AppRouter = () => {
// for now, trivial routing is sufficient
return (
<Router>
<Routes>
<Route path="*" element={<PageLayout />} />
</Routes>
</Router>
);
};

export interface IAppProps {
pref?: Partial<IPreferences>;
}
Expand Down Expand Up @@ -86,7 +74,7 @@ export class App<
}
>
<Provider store={store}>
<AppRouter />
<RouterProvider router={router} />
</Provider>
</ThemeProvider>
</PrefContext.Provider>
Expand Down
40 changes: 26 additions & 14 deletions src/features/environmentCreate/components/EnvironmentCreate.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import Box from "@mui/material/Box";
import Alert from "@mui/material/Alert";
import { stringify } from "yaml";
Expand All @@ -16,23 +17,38 @@ import {
} from "../../../features/metadata";
import {
environmentOpened,
closeCreateNewEnvironmentTab
closeCreateNewEnvironmentTab,
openCreateNewEnvironmentTab
} from "../../../features/tabs";
import { useAppDispatch, useAppSelector } from "../../../hooks";
import { SpecificationCreate, SpecificationReadOnly } from "./Specification";
import { descriptionChanged, nameChanged } from "../environmentCreateSlice";
import createLabel from "../../../common/config/labels";

export interface IEnvCreate {
environmentNotification: (notification: any) => void;
}
import { showNotification } from "../../notification/notificationSlice";

interface ICreateEnvironmentArgs {
code: { channels: string[]; dependencies: string[] };
}

export const EnvironmentCreate = ({ environmentNotification }: IEnvCreate) => {
export const EnvironmentCreate = () => {
const dispatch = useAppDispatch();

// Url routing params
// If user loads the app at /<namespace_name>/new-environment
// This will put the app in the correct state
const { namespaceName } = useParams<{
namespaceName: string;
}>();

useEffect(() => {
if (namespaceName) {
dispatch(modeChanged(EnvironmentDetailsModes.CREATE));
dispatch(openCreateNewEnvironmentTab(namespaceName));
}
}, [namespaceName]);

const navigate = useNavigate();

const { mode } = useAppSelector(state => state.environmentDetails);
const { name, description } = useAppSelector(
state => state.environmentCreate
Expand Down Expand Up @@ -84,17 +100,13 @@ export const EnvironmentCreate = ({ environmentNotification }: IEnvCreate) => {
dispatch(
environmentOpened({
environment,
selectedEnvironmentId: newEnvId,
canUpdate: true
})
);
// After new environment has been created, navigate to the new environment's web page
navigate(`/${namespace}/${name}`);
dispatch(currentBuildIdChanged(data.build_id));
environmentNotification({
data: {
show: true,
description: createLabel(name, "create")
}
});
dispatch(showNotification(createLabel(name, "create")));
} catch (e) {
setError({
message: e?.data?.message ?? createLabel(undefined, "error"),
Expand Down
116 changes: 83 additions & 33 deletions src/features/environmentDetails/components/EnvironmentDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, { useEffect, useState } from "react";
import { skipToken } from "@reduxjs/toolkit/query/react";
import { useNavigate, useParams } from "react-router-dom";
import Box from "@mui/material/Box";
import Alert from "@mui/material/Alert";
import { stringify } from "yaml";
Expand All @@ -8,7 +10,9 @@ import { SpecificationEdit, SpecificationReadOnly } from "./Specification";
import { useGetBuildQuery } from "../environmentDetailsApiSlice";
import {
updateEnvironmentBuildId,
environmentClosed
environmentClosed,
environmentOpened,
toggleNewEnvironmentView
} from "../../../features/tabs";
import {
useGetBuildPackagesQuery,
Expand All @@ -34,30 +38,81 @@ import artifactList from "../../../utils/helpers/artifact";
import createLabel from "../../../common/config/labels";
import { AlertDialog } from "../../../components/Dialog";
import { useAppDispatch, useAppSelector } from "../../../hooks";
import { CondaSpecificationPip } from "../../../common/models";
import {
CondaSpecificationPip,
Environment,
Namespace
} from "../../../common/models";
import { useInterval } from "../../../utils/helpers";
import { showNotification } from "../../notification/notificationSlice";
import { useScrollRef } from "../../../layouts/PageLayout";

interface IEnvDetails {
scrollRef: any;
environmentNotification: (notification: any) => void;
}
interface IUpdateEnvironmentArgs {
dependencies: (string | CondaSpecificationPip)[];
channels: string[];
}

const INTERVAL_REFRESHING = 5000;

export const EnvironmentDetails = ({
scrollRef,
environmentNotification
}: IEnvDetails) => {
export const EnvironmentDetails = () => {
const dispatch = useAppDispatch();

// Url routing params
// If user loads the app at /<namespace_name>/<environment_name>
// This will put the app in the correct state
const { namespaceName, environmentName } = useParams<{
namespaceName: string;
environmentName: string;
}>();
const namespaces: Namespace[] = useAppSelector(
state => state.namespaces.data
);
const namespace = namespaces.find(({ name }) => name === namespaceName);
const environments: Environment[] = useAppSelector(
state => state.environments.data
);
const environment = environments.find(
environment =>
environment.namespace.name === namespaceName &&
environment.name === environmentName
);
useEffect(() => {
if (namespace && environment) {
dispatch(
environmentOpened({
environment,
canUpdate: namespace.canUpdate
})
);
dispatch(modeChanged(EnvironmentDetailsModes.READ));
dispatch(toggleNewEnvironmentView(false));
}
}, [
// We only want to run this effect when:
//
// 1. User navigates to different environment = change of
// (namespaceName, environmentName) in the URL
// 2. The corresponding (namespace, environment) data have been fetched
//
// We cannot pass [namespace, environment] as the dependencies to
// useEffect() because whenever an environment is created or updated, a
// refetch of the data is triggered, which creates new data objects for
// [namespace, environment] in the Redux store, which would cause this
// effect to rerun, but, again, we only want to run this effect when the
// user navigates to a new (namespaceName, environmentName), hence the `&&
// namespace.name` and `&& environment.name`.
namespace && namespace.name,
environment && environment.name
]);

const navigate = useNavigate();

const { mode } = useAppSelector(state => state.environmentDetails);
const { page, dependencies } = useAppSelector(state => state.dependencies);
const { selectedEnvironment } = useAppSelector(state => state.tabs);
const { currentBuild } = useAppSelector(state => state.enviroments);
const [name, setName] = useState(selectedEnvironment?.name || "");
const scrollRef = useScrollRef();

const [descriptionIsUpdated, setDescriptionIsUpdated] = useState(false);
const [description, setDescription] = useState(
Expand All @@ -81,7 +136,7 @@ export const EnvironmentDetails = ({
const [updateBuildId] = useUpdateBuildIdMutation();
const [deleteEnvironment] = useDeleteEnvironmentMutation();

useGetEnviromentBuildsQuery(selectedEnvironment, {
useGetEnviromentBuildsQuery(selectedEnvironment ?? skipToken, {
pollingInterval: INTERVAL_REFRESHING
});

Expand Down Expand Up @@ -112,7 +167,7 @@ export const EnvironmentDetails = ({
};

const loadArtifacts = async () => {
if (artifactType.includes("DOCKER_MANIFEST")) {
if (!currentBuildId || artifactType.includes("DOCKER_MANIFEST")) {
return;
}

Expand All @@ -122,7 +177,7 @@ export const EnvironmentDetails = ({
};

const loadDependencies = async () => {
if (dependencies.length) {
if (!currentBuildId || dependencies.length) {
return;
}

Expand Down Expand Up @@ -171,12 +226,7 @@ export const EnvironmentDetails = ({
dispatch(modeChanged(EnvironmentDetailsModes.READ));
setCurrentBuildId(data.build_id);
dispatch(currentBuildIdChanged(data.build_id));
environmentNotification({
data: {
show: true,
description: createLabel(environment, "update")
}
});
dispatch(showNotification(createLabel(environment, "update")));
} catch (e) {
setError({
message:
Expand All @@ -187,7 +237,7 @@ export const EnvironmentDetails = ({
visible: true
});
}
scrollRef.current.scrollTo(0, 0);
scrollRef.current?.scrollTo(0, 0);
};

const updateBuild = async (buildId: number) => {
Expand All @@ -201,12 +251,9 @@ export const EnvironmentDetails = ({
buildId
}).unwrap();
dispatch(updateEnvironmentBuildId(buildId));
environmentNotification({
data: {
show: true,
description: createLabel(selectedEnvironment.name, "updateBuild")
}
});
dispatch(
showNotification(createLabel(selectedEnvironment.name, "updateBuild"))
);
} catch (e) {
setError({
message: createLabel(undefined, "error"),
Expand All @@ -232,19 +279,17 @@ export const EnvironmentDetails = ({
selectedEnvironmentId: selectedEnvironment.id
})
);
environmentNotification({
data: {
show: true,
description: createLabel(selectedEnvironment.name, "delete")
}
});
dispatch(
showNotification(createLabel(selectedEnvironment.name, "delete"))
);
navigate("/");
} catch (e) {
setError({
message: createLabel(undefined, "error"),
visible: true
});
}
scrollRef.current.scrollTo(0, 0);
scrollRef.current?.scrollTo(0, 0);
setShowDialog(false);
};

Expand All @@ -255,10 +300,15 @@ export const EnvironmentDetails = ({
})();
}, INTERVAL_REFRESHING);

if (!selectedEnvironment) {
return null;
}

return (
<Box sx={{ padding: "15px 12px" }}>
<EnvironmentDetailsHeader
envName={name}
namespace={namespace?.name}
onUpdateName={setName}
showEditButton={selectedEnvironment?.canUpdate}
/>
Expand Down
6 changes: 3 additions & 3 deletions src/features/environments/components/Environment.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from "react";
import { Link } from "react-router-dom";
import CircleIcon from "@mui/icons-material/Circle";
import ListItemIcon from "@mui/material/ListItemIcon";
import Button from "@mui/material/Button";
Expand All @@ -12,13 +13,11 @@ interface IEnvironmentProps {
* @param selectedEnvironmentId id of the currently selected environment
*/
environment: EnvironmentModel;
onClick: () => void;
selectedEnvironmentId: number | undefined;
}

export const Environment = ({
environment,
onClick,
selectedEnvironmentId
}: IEnvironmentProps) => {
const isSelected = selectedEnvironmentId === environment.id;
Expand All @@ -44,7 +43,8 @@ export const Environment = ({
/>
</ListItemIcon>
<Button
onClick={onClick}
component={Link}
to={`/${environment.namespace.name}/${environment.name}`}
sx={{
color: isSelected
? theme.palette.primary.main
Expand Down
Loading

0 comments on commit 19cc689

Please sign in to comment.