Skip to content

Commit

Permalink
Port deprecated Wizard component to new PF5 implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
adamkankovsky committed Nov 24, 2023
1 parent eaac83c commit 6c58d4a
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 119 deletions.
233 changes: 116 additions & 117 deletions src/components/AnacondaWizard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,23 @@ import {
PageSection,
PageSectionTypes,
PageSectionVariants,
Stack
} from "@patternfly/react-core";
import {
Stack,
useWizardContext,
Wizard,
WizardFooter,
WizardContextConsumer
} from "@patternfly/react-core/deprecated";
WizardFooterWrapper,
WizardStep
} from "@patternfly/react-core";

import { AnacondaPage } from "./AnacondaPage.jsx";
import { InstallationMethod, getPageProps as getInstallationMethodProps } from "./storage/InstallationMethod.jsx";
import { getDefaultScenario } from "./storage/InstallationScenario.jsx";
import { getDefaultScenario, getScenario } from "./storage/InstallationScenario.jsx";
import { MountPointMapping, getPageProps as getMountPointMappingProps } from "./storage/MountPointMapping.jsx";
import { DiskEncryption, getStorageEncryptionState, getPageProps as getDiskEncryptionProps } from "./storage/DiskEncryption.jsx";
import { InstallationLanguage, getPageProps as getInstallationLanguageProps } from "./localization/InstallationLanguage.jsx";
import { Accounts, getPageProps as getAccountsProps, getAccountsState, accountsToDbusUsers, cryptUserPassword } from "./users/Accounts.jsx";
import { InstallationProgress } from "./installation/InstallationProgress.jsx";
import { ReviewConfiguration, ReviewConfigurationConfirmModal, getPageProps as getReviewConfigurationProps } from "./review/ReviewConfiguration.jsx";
import { exitGui } from "../helpers/exit.js";
import { usePageLocation } from "hooks";
import {
getRequiredMountPoints,
} from "../apis/storage_devicetree.js";
Expand Down Expand Up @@ -172,6 +170,13 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim
},
];

const componentProps = {
isFormDisabled,
onCritFail,
setIsFormDisabled,
setIsFormValid,
};

const getFlattenedStepsIds = (steps) => {
const stepIds = [];
for (const step of steps) {
Expand All @@ -189,63 +194,58 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim
};
const flattenedStepsIds = getFlattenedStepsIds(stepsOrder);

const { path } = usePageLocation();
const firstStepId = stepsOrder.filter(step => !step.isHidden)[0].id;
const currentStepId = path[0] || firstStepId;

const isStepFollowedBy = (earlierStepId, laterStepId) => {
const earlierStepIdx = flattenedStepsIds.findIndex(s => s === earlierStepId);
const laterStepIdx = flattenedStepsIds.findIndex(s => s === laterStepId);
return earlierStepIdx < laterStepIdx;
};

const canJumpToStep = (stepId, currentStepId) => {
return stepId === currentStepId || isStepFollowedBy(stepId, currentStepId);
};

const createSteps = (stepsOrder) => {
const steps = stepsOrder.filter(s => !s.isHidden).map(s => {
let step = ({
const createSteps = (stepsOrder, componentProps) => {
return stepsOrder.map(s => {
let stepProps = {
id: s.id,
isHidden: s.isHidden,
name: s.label,
stepNavItemProps: { id: s.id },
canJumpTo: canJumpToStep(s.id, currentStepId),
});
...(s.steps?.length && { isExpandable: true }),
};
if (s.component) {
step = ({
...step,
component: (
stepProps = {
children: (
<AnacondaPage step={s.id} title={s.title} stepNotification={stepNotification}>
<s.component
idPrefix={s.id}
setIsFormValid={setIsFormValid}
onCritFail={onCritFail}
setStepNotification={ex => setStepNotification({ step: s.id, ...ex })}
isFormDisabled={isFormDisabled}
setIsFormDisabled={setIsFormDisabled}
{...componentProps}
{...s.data}
/>
</AnacondaPage>
),
});
...stepProps
};
} else if (s.steps) {
step.steps = createSteps(s.steps);
const subSteps = createSteps(s.steps, componentProps);
stepProps = {
...stepProps,
steps: [...subSteps]
};
}
return step;
return (
<WizardStep key={s.id} {...stepProps} />
);
});
return steps;
};
const steps = createSteps(stepsOrder);
const steps = createSteps(stepsOrder, componentProps);

const goToStep = (newStep, prevStep) => {
if (prevStep.prevId !== newStep.id) {
if (prevStep.id !== newStep.id) {
// first reset validation state to default
setIsFormValid(false);
}

// Reset the applied partitioning when going back from a step after creating partitioning to a step
// before creating partitioning.
if ((prevStep.prevId === "accounts" || isStepFollowedBy("accounts", prevStep.prevId)) &&
if ((prevStep.id === "accounts" || isStepFollowedBy("accounts", prevStep.id)) &&
isStepFollowedBy(newStep.id, "accounts")) {
setIsFormDisabled(true);
resetPartitioning()
Expand All @@ -267,10 +267,14 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim
);
}

const firstVisibleStepIndex = steps.findIndex(step => !step.props.isHidden) + 1;

return (
<PageSection type={PageSectionTypes.wizard} variant={PageSectionVariants.light}>
<Wizard
id="installation-wizard"
isVisitRequired
startIndex={firstVisibleStepIndex}
footer={<Footer
onCritFail={onCritFail}
isFormValid={isFormValid}
Expand All @@ -285,15 +289,10 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim
storageScenarioId={storageScenarioId}
accounts={accounts}
/>}
hideClose
mainAriaLabel={`${title} content`}
navAriaLabel={`${title} steps`}
onBack={goToStep}
onGoToStep={goToStep}
onNext={goToStep}
steps={steps}
isNavExpandable
/>
onStepChange={((event, currentStep, prevStep) => goToStep(currentStep, prevStep))}
>
{steps}
</Wizard>
</PageSection>
);
};
Expand All @@ -314,9 +313,10 @@ const Footer = ({
}) => {
const [nextWaitsConfirmation, setNextWaitsConfirmation] = useState(false);
const [quitWaitsConfirmation, setQuitWaitsConfirmation] = useState(false);
const { activeStep, goToNextStep, goToPrevStep } = useWizardContext();
const isBootIso = useContext(SystemTypeContext) === "BOOT_ISO";

const goToNextStep = (activeStep, onNext) => {
const onNext = (activeStep, goToNextStep) => {
// first reset validation state to default
setIsFormValid(true);

Expand All @@ -330,7 +330,7 @@ const Footer = ({
setStepNotification({ step: activeStep.id, ...ex });
},
onSuccess: () => {
onNext();
goToNextStep();

// Reset the state after the onNext call. Otherwise,
// React will try to render the current step again.
Expand All @@ -353,7 +353,7 @@ const Footer = ({
setStepNotification({ step: activeStep.id, ...ex });
},
onSuccess: () => {
onNext();
goToNextStep();

// Reset the state after the onNext call. Otherwise,
// React will try to render the current step again.
Expand All @@ -366,85 +366,84 @@ const Footer = ({
.then(cryptedPassword => {
const users = accountsToDbusUsers({ ...accounts, password: cryptedPassword });
setUsers(users);
onNext();
goToNextStep();
}, onCritFail({ context: N_("Password ecryption failed.") }));
} else {
onNext();
goToNextStep();
}
};

const goToPreviousStep = (activeStep, onBack, errorHandler) => {
const onBack = (goToPrevStep, errorHandler) => {
// first reset validation state to default
setIsFormValid(true);
onBack();
setIsFormValid(false);
goToPrevStep();
};

const isFirstScreen = (
activeStep.id === "installation-language" || (activeStep.id === "installation-method" && !isBootIso)
);

const nextButtonText = (
activeStep.id === "installation-review"
? getScenario(storageScenarioId).buttonLabel
: _("Next")
);

const footerHelperText = stepsOrder.find(step => step.id === activeStep.id)?.footerHelperText;

return (
<WizardFooter>
<WizardContextConsumer>
{({ activeStep, onNext, onBack }) => {
const currentStep = stepsOrder.find(s => s.id === activeStep.id);
const footerHelperText = currentStep?.footerHelperText;
const isFirstScreen = stepsOrder.filter(step => !step.isHidden)[0].id === activeStep.id;
const nextButtonText = currentStep?.nextButtonText || _("Next");
const nextButtonVariant = currentStep?.nextButtonVariant || "primary";

return (
<Stack hasGutter>
{activeStep.id === "installation-review" &&
nextWaitsConfirmation &&
<ReviewConfigurationConfirmModal
idPrefix={activeStep.id}
onNext={() => { setShowWizard(false); cockpit.location.go(["installation-progress"]) }}
setNextWaitsConfirmation={setNextWaitsConfirmation}
storageScenarioId={storageScenarioId}
/>}
{quitWaitsConfirmation &&
<QuitInstallationConfirmModal
exitGui={exitGui}
setQuitWaitsConfirmation={setQuitWaitsConfirmation}
/>}
{footerHelperText}
<ActionList>
<Button
id="installation-back-btn"
variant="secondary"
isDisabled={isFirstScreen || isFormDisabled}
onClick={() => goToPreviousStep(
activeStep,
onBack,
onCritFail({ context: cockpit.format(N_("Error was hit when going back from $0."), activeStep.name) })
)}>
{_("Back")}
</Button>
<Button
id="installation-next-btn"
variant={nextButtonVariant}
isDisabled={
!isFormValid ||
isFormDisabled ||
nextWaitsConfirmation
}
onClick={() => goToNextStep(activeStep, onNext)}>
{nextButtonText}
</Button>
<Button
id="installation-quit-btn"
isDisabled={isFormDisabled}
style={{ marginLeft: "var(--pf-v5-c-wizard__footer-cancel--MarginLeft)" }}
variant="link"
onClick={() => {
setQuitWaitsConfirmation(true);
}}
>
{isBootIso ? _("Reboot") : _("Quit")}
</Button>
</ActionList>
</Stack>
);
}}
</WizardContextConsumer>
</WizardFooter>
<WizardFooterWrapper>
<Stack hasGutter>
{activeStep.id === "installation-review" &&
nextWaitsConfirmation &&
<ReviewConfigurationConfirmModal
idPrefix={activeStep.id}
goToNextStep={() => { setShowWizard(false); cockpit.location.go(["installation-progress"]) }}
setNextWaitsConfirmation={setNextWaitsConfirmation}
storageScenarioId={storageScenarioId}
/>}
{quitWaitsConfirmation &&
<QuitInstallationConfirmModal
exitGui={exitGui}
setQuitWaitsConfirmation={setQuitWaitsConfirmation}
/>}
{footerHelperText}
<ActionList>
<Button
id="installation-back-btn"
variant="secondary"
isDisabled={isFirstScreen || isFormDisabled}
onClick={() => onBack(
goToPrevStep,
onCritFail({ context: cockpit.format(N_("Error was hit when going back from $0."), activeStep.name) })
)}>
{_("Back")}
</Button>
<Button
id="installation-next-btn"
variant={activeStep.id === "installation-review" ? "warning" : "primary"}
isDisabled={
!isFormValid ||
isFormDisabled ||
nextWaitsConfirmation
}
onClick={() => onNext(activeStep, goToNextStep)}>
{nextButtonText}
</Button>
<Button
id="installation-quit-btn"
isDisabled={isFormDisabled}
style={{ marginLeft: "var(--pf-v5-c-wizard__footer-cancel--MarginLeft)" }}
variant="link"
onClick={() => {
setQuitWaitsConfirmation(true);
}}
>
{isBootIso ? _("Reboot") : _("Quit")}
</Button>
</ActionList>
</Stack>
</WizardFooterWrapper>
);
};

Expand Down
4 changes: 2 additions & 2 deletions src/components/review/ReviewConfiguration.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export const ReviewConfiguration = ({ deviceData, diskSelection, language, local
);
};

export const ReviewConfigurationConfirmModal = ({ idPrefix, onNext, setNextWaitsConfirmation, storageScenarioId }) => {
export const ReviewConfigurationConfirmModal = ({ idPrefix, goToNextStep, setNextWaitsConfirmation, storageScenarioId }) => {
const scenario = getScenario(storageScenarioId);
return (
<Modal
Expand All @@ -197,7 +197,7 @@ export const ReviewConfigurationConfirmModal = ({ idPrefix, onNext, setNextWaits
key="confirm"
onClick={() => {
setNextWaitsConfirmation(false);
onNext();
goToNextStep();
}}
variant={scenario.buttonVariant}
>
Expand Down

0 comments on commit 6c58d4a

Please sign in to comment.