debian-image-builder-frontend/src/Components/CreateImageWizardV2/CreateImageWizard.tsx
Thomas Lavocat 9c78456c57 wizard/image_output: add the image output step
Port the image output step to the new wizard v2.

This is also the first step in the new redesigned wizard and this commit
message is an opportunity to detail a bit the organisation of it:

The code is organised as followed:
- CreateImageWizard is the root and contains all the code associated
  with the PF wizard. (i.e Wizard and WizardStep).
- Each step has its code under the step/ sub directory.
- The step directory is named after the step (i.e ImageOutput for the
  Image Output step
- At the root of the step directory there is a file which contains the
  code of the step (i.e ImageOutput.tsx).
- The main component of the step can access many subcomponents and they
  have to be stored alongside it in its directory.

State management:
- The state management is only done with react use states, prop drilling
  and state lifting. This is to ensure simplicity as it makes the wizard
- If necessary in the future, there might be needs for context and
  reducers, but it's quite not the case RN. And using them come at the
  cost of making exploring the code harder.
- CreateImageWizard will declare all the states
2023-11-14 09:33:36 +01:00

144 lines
4.4 KiB
TypeScript

import React, { useState } from 'react';
import {
Button,
Wizard,
WizardFooterWrapper,
WizardStep,
useWizardContext,
} from '@patternfly/react-core';
import { useNavigate } from 'react-router-dom';
import {
EnvironmentStateType,
filterEnvironment,
hasUserSelectedAtLeastOneEnv,
useGetAllowedTargets,
} from './steps/ImageOutput/Environment';
import ImageOutputStep from './steps/ImageOutput/ImageOutput';
import { RHEL_9, X86_64 } from '../../constants';
import './CreateImageWizard.scss';
import { ArchitectureItem, Distributions } from '../../store/imageBuilderApi';
import { resolveRelPath } from '../../Utilities/path';
import { ImageBuilderHeader } from '../sharedComponents/ImageBuilderHeader';
/**
* @return true if the array in prevAllowedTargets is equivalent to the array
* allowedTargets, false otherwise
*/
const isIdenticalToPrev = (
prevAllowedTargets: string[],
allowedTargets: string[]
) => {
let identicalToPrev = true;
if (allowedTargets.length === prevAllowedTargets.length) {
allowedTargets.forEach((elem) => {
if (!prevAllowedTargets.includes(elem)) {
identicalToPrev = false;
}
});
} else {
identicalToPrev = false;
}
return identicalToPrev;
};
type CustomWizardFooterPropType = {
isNextDisabled: boolean;
};
/**
* The custom wizard footer is only switching the order of the buttons compared
* to the default wizard footer from the PF5 library.
*/
const CustomWizardFooter = ({ isNextDisabled }: CustomWizardFooterPropType) => {
const { goToNextStep, goToPrevStep, close } = useWizardContext();
return (
<WizardFooterWrapper>
<Button
variant="primary"
onClick={goToNextStep}
isDisabled={isNextDisabled}
>
Next
</Button>
<Button variant="secondary" onClick={goToPrevStep}>
Back
</Button>
<Button variant="link" onClick={close}>
Cancel
</Button>
</WizardFooterWrapper>
);
};
const CreateImageWizard = () => {
const navigate = useNavigate();
// Image output step states
const [release, setRelease] = useState<Distributions>(RHEL_9);
const [arch, setArch] = useState<ArchitectureItem['arch']>(X86_64);
const {
data: allowedTargets,
isFetching,
isSuccess,
isError,
} = useGetAllowedTargets({
architecture: arch,
release: release,
});
const [environment, setEnvironment] = useState<EnvironmentStateType>(
filterEnvironment(
{
aws: { selected: false, authorized: false },
azure: { selected: false, authorized: false },
gcp: { selected: false, authorized: false },
oci: { selected: false, authorized: false },
'vsphere-ova': { selected: false, authorized: false },
vsphere: { selected: false, authorized: false },
'guest-image': { selected: false, authorized: false },
'image-installer': { selected: false, authorized: false },
wsl: { selected: false, authorized: false },
},
allowedTargets
)
);
// Update of the environment when the architecture and release are changed.
// This pattern prevents the usage of a useEffect See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes
const [prevAllowedTargets, setPrevAllowedTargets] = useState(allowedTargets);
if (!isIdenticalToPrev(prevAllowedTargets, allowedTargets)) {
setPrevAllowedTargets(allowedTargets);
setEnvironment(filterEnvironment(environment, allowedTargets));
}
return (
<>
<ImageBuilderHeader />
<section className="pf-l-page__main-section pf-c-page__main-section">
<Wizard onClose={() => navigate(resolveRelPath(''))} isVisitRequired>
<WizardStep
name="Image output"
id="step-image-output"
footer={
<CustomWizardFooter
isNextDisabled={!hasUserSelectedAtLeastOneEnv(environment)}
/>
}
>
<ImageOutputStep
release={release}
setRelease={setRelease}
arch={arch}
setArch={setArch}
environment={environment}
setEnvironment={setEnvironment}
isFetching={isFetching}
isError={isError}
isSuccess={isSuccess}
/>
</WizardStep>
</Wizard>
</section>
</>
);
};
export default CreateImageWizard;