diff --git a/src/components/AnacondaWizard.jsx b/src/components/AnacondaWizard.jsx
index c90537d9d5..42521ccf44 100644
--- a/src/components/AnacondaWizard.jsx
+++ b/src/components/AnacondaWizard.jsx
@@ -25,17 +25,16 @@ 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";
@@ -43,7 +42,6 @@ import { Accounts, getPageProps as getAccountsProps, getAccountsState, accountsT
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";
@@ -69,6 +67,7 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim
const [storageScenarioId, setStorageScenarioId] = useState(window.sessionStorage.getItem("storage-scenario-id") || getDefaultScenario().id);
const [accounts, setAccounts] = useState(getAccountsState());
const [showWizard, setShowWizard] = useState(true);
+ const [isStepDisabled, setIsStepDisabled] = useState({});
const osRelease = useContext(OsReleaseContext);
const isBootIso = useContext(SystemTypeContext) === "BOOT_ISO";
@@ -103,74 +102,120 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim
}
}
}, [localizationData]);
- const stepsOrder = [
- {
- component: InstallationLanguage,
- data: { dispatch, languages: localizationData.languages, language: localizationData.language, commonLocales: localizationData.commonLocales },
- ...getInstallationLanguageProps({ isBootIso, osRelease })
- },
- {
- component: InstallationMethod,
- data: {
- deviceData: storageData.devices,
- diskSelection: storageData.diskSelection,
- dispatch,
- storageScenarioId,
- setStorageScenarioId: (scenarioId) => {
- window.sessionStorage.setItem("storage-scenario-id", scenarioId);
- setStorageScenarioId(scenarioId);
- }
+ const stepsOrder = useMemo(() => {
+ return [
+ {
+ component: InstallationLanguage,
+ data: { dispatch, languages: localizationData.languages, language: localizationData.language, commonLocales: localizationData.commonLocales },
+ ...getInstallationLanguageProps({ isBootIso, osRelease })
},
- ...getInstallationMethodProps({ isBootIso, osRelease, isFormValid })
- },
- {
- id: "disk-configuration",
- label: _("Disk configuration"),
- steps: [{
- component: MountPointMapping,
+ {
+ component: InstallationMethod,
data: {
deviceData: storageData.devices,
diskSelection: storageData.diskSelection,
dispatch,
- partitioningData: storageData.partitioning,
- requiredMountPoints,
- reusePartitioning,
- setReusePartitioning,
+ storageScenarioId,
+ setStorageScenarioId: (scenarioId) => {
+ window.sessionStorage.setItem("storage-scenario-id", scenarioId);
+ setStorageScenarioId(scenarioId);
+ }
},
- ...getMountPointMappingProps({ storageScenarioId })
- }, {
- component: DiskEncryption,
+ ...getInstallationMethodProps({ isBootIso, osRelease, isFormValid })
+ },
+ {
+ id: "disk-configuration",
+ label: _("Disk configuration"),
+ steps: [{
+ component: MountPointMapping,
+ data: {
+ deviceData: storageData.devices,
+ diskSelection: storageData.diskSelection,
+ dispatch,
+ partitioningData: storageData.partitioning,
+ requiredMountPoints,
+ reusePartitioning,
+ setReusePartitioning,
+ },
+ ...getMountPointMappingProps({ storageScenarioId })
+ }, {
+ component: DiskEncryption,
+ data: {
+ storageEncryption,
+ setStorageEncryption,
+ passwordPolicies: runtimeData.passwordPolicies,
+ },
+ ...getDiskEncryptionProps({ storageScenarioId })
+ }]
+ },
+ {
+ component: Accounts,
data: {
- storageEncryption,
- setStorageEncryption,
+ accounts,
+ setAccounts,
passwordPolicies: runtimeData.passwordPolicies,
},
- ...getDiskEncryptionProps({ storageScenarioId })
- }]
- },
- {
- component: Accounts,
- data: {
- accounts,
- setAccounts,
- passwordPolicies: runtimeData.passwordPolicies,
+ ...getAccountsProps({ isBootIso })
},
- ...getAccountsProps({ isBootIso })
- },
- {
- component: ReviewConfiguration,
- data: {
- deviceData: storageData.devices,
- diskSelection: storageData.diskSelection,
- requests: storageData.partitioning ? storageData.partitioning.requests : null,
- language,
- localizationData,
- storageScenarioId,
- accounts,
+ {
+ component: ReviewConfiguration,
+ data: {
+ deviceData: storageData.devices,
+ diskSelection: storageData.diskSelection,
+ requests: storageData.partitioning ? storageData.partitioning.requests : null,
+ language,
+ localizationData,
+ storageScenarioId,
+ accounts,
+ },
+ ...getReviewConfigurationProps({ storageScenarioId })
},
- ...getReviewConfigurationProps({ storageScenarioId })
- },
- ];
+ ];
+ }, [accounts, dispatch, isBootIso, isFormValid, language, localizationData, osRelease, requiredMountPoints, reusePartitioning, runtimeData.passwordPolicies, storageData.devices, storageData.diskSelection, storageData.partitioning, storageEncryption, storageScenarioId]);
+
+ useEffect(() => {
+ setIsStepDisabled(prevState => {
+ const updatedState = { ...prevState };
+
+ const updateStateForStep = (step, index, isSubStep = false) => {
+ if (updatedState[step.id] === undefined) {
+ updatedState[step.id] = isSubStep || index !== 0;
+ }
+
+ if (step.steps) {
+ step.steps.forEach((subStep, subIndex) => {
+ updateStateForStep(subStep, subIndex, true);
+ });
+ }
+ };
+
+ stepsOrder.forEach((step, index) => {
+ updateStateForStep(step, index);
+ });
+ Object.keys(updatedState).forEach(stepId => {
+ if (!stepsOrder.some(step => step.id === stepId || (step.steps && step.steps.some(subStep => subStep.id === stepId)))) {
+ delete updatedState[stepId];
+ }
+ });
+
+ return updatedState;
+ });
+ }, [stepsOrder]);
+
+ const updateStepDisabledState = (currentStepId) => {
+ const newStepDisabledState = { ...isStepDisabled };
+ Object.keys(newStepDisabledState).forEach(stepId => {
+ newStepDisabledState[stepId] = isStepFollowedBy(currentStepId, stepId);
+ });
+ setIsStepDisabled(newStepDisabledState);
+ };
+
+ const componentProps = {
+ isFormDisabled,
+ onCritFail,
+ setIsFormDisabled,
+ setIsFormValid,
+ };
const getFlattenedStepsIds = (steps) => {
const stepIds = [];
@@ -189,9 +234,39 @@ 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 findPreviousVisibleStep = (currentStepId) => {
+ let previousVisibleStepId = null;
+
+ for (let i = 0; i < flattenedStepsIds.length; i++) {
+ const step = flattenedStepsIds[i];
+ if (step === currentStepId) {
+ break;
+ }
+ previousVisibleStepId = step;
+ }
+
+ return previousVisibleStepId;
+ };
+
+ const findNextVisibleStep = (currentStepId) => {
+ let nextVisibleStepId = null;
+ let foundCurrentStep = false;
+
+ for (let i = 0; i < flattenedStepsIds.length; i++) {
+ const step = flattenedStepsIds[i];
+
+ if (foundCurrentStep) {
+ nextVisibleStepId = step;
+ break;
+ }
+
+ if (step === currentStepId) {
+ foundCurrentStep = true;
+ continue;
+ }
+ }
+ return nextVisibleStepId;
+ };
const isStepFollowedBy = (earlierStepId, laterStepId) => {
const earlierStepIdx = flattenedStepsIds.findIndex(s => s === earlierStepId);
@@ -199,62 +274,64 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim
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,
+ isDisabled: isStepDisabled[s.id],
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: (
setStepNotification({ step: s.id, ...ex })}
- isFormDisabled={isFormDisabled}
- setIsFormDisabled={setIsFormDisabled}
+ {...componentProps}
{...s.data}
/>
),
- });
+ ...stepProps
+ };
} else if (s.steps) {
- step.steps = createSteps(s.steps);
+ const subSteps = createSteps(s.steps, componentProps);
+ stepProps = {
+ ...stepProps,
+ steps: [...subSteps]
+ };
}
- return step;
+ return (
+
+ );
});
- 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()
.then(
+ () => updateStepDisabledState(newStep.id),
() => cockpit.location.go([newStep.id]),
() => onCritFail({ context: cockpit.format(N_("Error was hit when going back from $0."), prevStep.prevName) })
)
.always(() => setIsFormDisabled(false));
} else {
+ updateStepDisabledState(newStep.id);
cockpit.location.go([newStep.id]);
}
};
@@ -267,11 +344,17 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim
);
}
+ const firstVisibleStepIndex = steps.findIndex(step => !step.props.isHidden) + 1;
+
return (
}
- hideClose
- mainAriaLabel={`${title} content`}
- navAriaLabel={`${title} steps`}
- onBack={goToStep}
- onGoToStep={goToStep}
- onNext={goToStep}
- steps={steps}
- isNavExpandable
- />
+ onStepChange={((event, currentStep, prevStep) => goToStep(currentStep, prevStep))}
+ >
+ {steps}
+
);
};
const Footer = ({
+ findNextVisibleStep,
+ findPreviousVisibleStep,
onCritFail,
isFormValid,
setIsFormValid,
@@ -310,13 +391,19 @@ const Footer = ({
stepsOrder,
storageEncryption,
storageScenarioId,
+ updateStepDisabledState,
accounts,
}) => {
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) => {
+ const nextStepAction = () => {
+ updateStepDisabledState(findNextVisibleStep(activeStep.id));
+ goToNextStep();
+ };
// first reset validation state to default
setIsFormValid(true);
@@ -330,7 +417,7 @@ const Footer = ({
setStepNotification({ step: activeStep.id, ...ex });
},
onSuccess: () => {
- onNext();
+ nextStepAction();
// Reset the state after the onNext call. Otherwise,
// React will try to render the current step again.
@@ -353,7 +440,7 @@ const Footer = ({
setStepNotification({ step: activeStep.id, ...ex });
},
onSuccess: () => {
- onNext();
+ nextStepAction();
// Reset the state after the onNext call. Otherwise,
// React will try to render the current step again.
@@ -366,85 +453,85 @@ const Footer = ({
.then(cryptedPassword => {
const users = accountsToDbusUsers({ ...accounts, password: cryptedPassword });
setUsers(users);
- onNext();
+ nextStepAction();
}, onCritFail({ context: N_("Password ecryption failed.") }));
} else {
- onNext();
+ nextStepAction();
}
};
- const goToPreviousStep = (activeStep, onBack, errorHandler) => {
+ const onBack = (goToPrevStep, errorHandler) => {
// first reset validation state to default
- setIsFormValid(true);
- onBack();
+ setIsFormValid(false);
+ updateStepDisabledState(findPreviousVisibleStep(activeStep.id));
+ 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 (
-
-
- {({ 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 (
-
- {activeStep.id === "installation-review" &&
- nextWaitsConfirmation &&
- { setShowWizard(false); cockpit.location.go(["installation-progress"]) }}
- setNextWaitsConfirmation={setNextWaitsConfirmation}
- storageScenarioId={storageScenarioId}
- />}
- {quitWaitsConfirmation &&
- }
- {footerHelperText}
-
-
-
-
-
-
- );
- }}
-
-
+
+
+ {activeStep.id === "installation-review" &&
+ nextWaitsConfirmation &&
+ { setShowWizard(false); cockpit.location.go(["installation-progress"]) }}
+ setNextWaitsConfirmation={setNextWaitsConfirmation}
+ storageScenarioId={storageScenarioId}
+ />}
+ {quitWaitsConfirmation &&
+ }
+ {footerHelperText}
+
+
+
+
+
+
+
);
};
diff --git a/src/components/review/ReviewConfiguration.jsx b/src/components/review/ReviewConfiguration.jsx
index 78cbb7d95a..33e4246cba 100644
--- a/src/components/review/ReviewConfiguration.jsx
+++ b/src/components/review/ReviewConfiguration.jsx
@@ -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 (
{
setNextWaitsConfirmation(false);
- onNext();
+ goToNextStep();
}}
variant={scenario.buttonVariant}
>