Skip to content

Commit

Permalink
fix(web): restructure internals of layout
Browse files Browse the repository at this point in the history
A step forward to simplify or at least improve how the application is
laid out with full header and sidebar or not depending on the route or
the action the installer is displaying or triggering.

There are a lot of changes here and there, but basically what this
commit does is

* Drop SimpleLayout component
* Rename Main to Layout and add two convenience predefined variants:
  Full and Plain.
* Adapt layout/Header for displaying more or less things depending on
  props, sent via Layout component.
* Remove the installer options "cog button" in favor of a dropdown in
  the header right corner and move the installer options as an element
  inside. Such a dropdown will hold the "Download logs" actions in the
  short term.
* Adapt needed parts to accommodate above changes.
  • Loading branch information
dgdavid committed Oct 24, 2024
1 parent 9cf3608 commit f10153b
Show file tree
Hide file tree
Showing 16 changed files with 459 additions and 351 deletions.
48 changes: 28 additions & 20 deletions web/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,19 @@

import React from "react";
import { Navigate, Outlet, useLocation } from "react-router-dom";
import { Loading } from "./components/layout";
import { ServerError } from "~/components/core";
import { Loading, PlainLayout } from "~/components/layout";
import { Questions } from "~/components/questions";
import { ServerError, Installation } from "~/components/core";
import { useInstallerL10n } from "./context/installerL10n";
import { useInstallerL10n } from "~/context/installerL10n";
import { useInstallerClientStatus } from "~/context/installer";
import { useProduct, useProductChanges } from "./queries/software";
import { useProduct, useProductChanges } from "~/queries/software";
import { useL10nConfigChanges } from "~/queries/l10n";
import { useIssuesChanges } from "./queries/issues";
import { useInstallerStatus, useInstallerStatusChanges } from "./queries/status";
import { useDeprecatedChanges } from "./queries/storage";
import { PATHS as PRODUCT_PATHS } from "./routes/products";
import SimpleLayout from "./SimpleLayout";
import { InstallationPhase } from "./types/status";
import { useIssuesChanges } from "~/queries/issues";
import { useInstallerStatus, useInstallerStatusChanges } from "~/queries/status";
import { useDeprecatedChanges } from "~/queries/storage";
import { PATHS as PRODUCT_PATHS } from "~/routes/products";
import { PATHS as ROOT_PATHS } from "~/router";
import { InstallationPhase } from "~/types/status";

/**
* Main application component.
Expand All @@ -56,25 +56,33 @@ function App() {
useDeprecatedChanges();

const Content = () => {
if (error) return <ServerError />;
if (error)
return (
<PlainLayout>
<ServerError />
</PlainLayout>
);

if (phase === InstallationPhase.Install && isBusy) {
return <Navigate to={ROOT_PATHS.installationProgress} />;
}

if (phase === InstallationPhase.Install) {
return <Installation isBusy={isBusy} />;
if (phase === InstallationPhase.Install && !isBusy) {
return <Navigate to={ROOT_PATHS.installationFinished} />;
}

if (!products || !connected)
if (!products || !connected) return <Loading />;

if (phase === InstallationPhase.Startup && isBusy) {
return (
<SimpleLayout showOutlet={false}>
<PlainLayout>
<Loading />
</SimpleLayout>
</PlainLayout>
);

if (phase === InstallationPhase.Startup && isBusy) {
return <Loading />;
}

if (selectedProduct === undefined && location.pathname !== PRODUCT_PATHS.root) {
return <Navigate to="/products" />;
return <Navigate to={PRODUCT_PATHS.products} />;
}

if (
Expand Down
21 changes: 17 additions & 4 deletions web/src/App.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ jest.mock("~/context/installer", () => ({
// Mock some components,
// See https://www.chakshunyu.com/blog/how-to-mock-a-react-component-in-jest/#default-export
jest.mock("~/components/questions/Questions", () => () => <div>Questions Mock</div>);
jest.mock("~/components/core/Installation", () => () => <div>Installation Mock</div>);
jest.mock("~/components/layout/Loading", () => () => <div>Loading Mock</div>);
jest.mock("~/components/product/ProductSelectionProgress", () => () => <div>Product progress</div>);

Expand Down Expand Up @@ -163,15 +162,29 @@ describe("App", () => {
});
});

describe("on the installaiton phase", () => {
describe("on the busy installaiton phase", () => {
beforeEach(() => {
mockClientStatus.phase = InstallationPhase.Install;
mockClientStatus.isBusy = true;
mockSelectedProduct = { id: "Fake product" };
});

it("navigates to installation progress", async () => {
installerRender(<App />, { withL10n: true });
await screen.findByText("Navigating to /installation/progress");
});
});

describe("on the idle installaiton phase", () => {
beforeEach(() => {
mockClientStatus.phase = InstallationPhase.Install;
mockClientStatus.isBusy = false;
mockSelectedProduct = { id: "Fake product" };
});

it("renders the application content", async () => {
it("navigates to installation finished", async () => {
installerRender(<App />, { withL10n: true });
await screen.findByText("Installation Mock");
await screen.findByText("Navigating to /installation/finished");
});
});
});
62 changes: 0 additions & 62 deletions web/src/SimpleLayout.jsx

This file was deleted.

30 changes: 0 additions & 30 deletions web/src/components/core/Installation.jsx

This file was deleted.

97 changes: 53 additions & 44 deletions web/src/components/core/InstallationFinished.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ import {
Stack,
Text,
} from "@patternfly/react-core";
import SimpleLayout from "~/SimpleLayout";
import { Center, Icon } from "~/components/layout";
import { EncryptionMethods } from "~/types/storage";
import { _ } from "~/i18n";
import alignmentStyles from "@patternfly/react-styles/css/utilities/Alignment/alignment";
import { useInstallerStatus } from "~/queries/status";
import { useProposalResult } from "~/queries/storage";
import { finishInstallation } from "~/api/manager";
import { InstallationPhase } from "~/types/status";
import { Navigate } from "react-router-dom";
import { PATHS } from "~/router";

const TpmHint = () => {
const [isExpanded, setIsExpanded] = useState(false);
Expand Down Expand Up @@ -76,55 +78,62 @@ the machine needs to boot directly to the new boot loader.",
const SuccessIcon = () => <Icon name="check_circle" className="icon-xxxl color-success" />;

function InstallationFinished() {
const { useIguana } = useInstallerStatus({ suspense: true });
const { phase, isBusy, useIguana } = useInstallerStatus({ suspense: true });
const {
settings: { encryptionPassword, encryptionMethod },
} = useProposalResult();

if (phase !== InstallationPhase.Install) {
return <Navigate to={PATHS.root} />;
}

if (isBusy) {
return <Navigate to={PATHS.installationProgress} />;
}

const usingTpm = encryptionPassword?.length > 0 && encryptionMethod === EncryptionMethods.TPM;

return (
<SimpleLayout showOutlet={false}>
<Center>
<Grid hasGutter>
<GridItem sm={8} smOffset={2}>
<Card isRounded>
<CardBody>
<Stack hasGutter>
<EmptyState variant="xl">
<EmptyStateHeader
titleText={_("Congratulations!")}
headingLevel="h2"
icon={<EmptyStateIcon icon={SuccessIcon} />}
/>
<EmptyStateBody>
<Flex
rowGap={{ default: "rowGapMd" }}
justifyContent={{ default: "justifyContentCenter" }}
>
<Text>{_("The installation on your machine is complete.")}</Text>
<Text>
{useIguana
? _("At this point you can power off the machine.")
: _(
"At this point you can reboot the machine to log in to the new system.",
)}
</Text>
{usingTpm && <TpmHint />}
</Flex>
</EmptyStateBody>
</EmptyState>
<Flex direction={{ default: "rowReverse" }}>
<Button size="lg" variant="primary" onClick={finishInstallation}>
{useIguana ? _("Finish") : _("Reboot")}
</Button>
</Flex>
</Stack>
</CardBody>
</Card>
</GridItem>
</Grid>
</Center>
</SimpleLayout>
<Center>
<Grid hasGutter>
<GridItem sm={8} smOffset={2}>
<Card isRounded>
<CardBody>
<Stack hasGutter>
<EmptyState variant="xl">
<EmptyStateHeader
titleText={_("Congratulations!")}
headingLevel="h2"
icon={<EmptyStateIcon icon={SuccessIcon} />}
/>
<EmptyStateBody>
<Flex
rowGap={{ default: "rowGapMd" }}
justifyContent={{ default: "justifyContentCenter" }}
>
<Text>{_("The installation on your machine is complete.")}</Text>
<Text>
{useIguana
? _("At this point you can power off the machine.")
: _(
"At this point you can reboot the machine to log in to the new system.",
)}
</Text>
{usingTpm && <TpmHint />}
</Flex>
</EmptyStateBody>
</EmptyState>
<Flex direction={{ default: "rowReverse" }}>
<Button size="lg" variant="primary" onClick={finishInstallation}>
{useIguana ? _("Finish") : _("Reboot")}
</Button>
</Flex>
</Stack>
</CardBody>
</Card>
</GridItem>
</Grid>
</Center>
);
}

Expand Down
21 changes: 15 additions & 6 deletions web/src/components/core/InstallationProgress.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,23 @@
import React from "react";
import { _ } from "~/i18n";
import ProgressReport from "./ProgressReport";
import SimpleLayout from "~/SimpleLayout";
import { InstallationPhase } from "~/types/status";
import { PATHS } from "~/router";
import { Navigate } from "react-router-dom";
import { useInstallerClientStatus } from "~/context/installer";

function InstallationProgress() {
return (
<SimpleLayout showOutlet={false}>
<ProgressReport title={_("Installing the system, please wait ...")} />
</SimpleLayout>
);
const { isBusy, phase } = useInstallerClientStatus({ suspense: true });

if (phase !== InstallationPhase.Install) {
return <Navigate to={PATHS.root} replace />;
}

if (!isBusy) {
return <Navigate to={PATHS.installationFinished} replace />;
}

return <ProgressReport title={_("Installing the system, please wait ...")} />;
}

export default InstallationProgress;
Loading

0 comments on commit f10153b

Please sign in to comment.