Skip to content

Commit

Permalink
Merge pull request #4847 from KKoukiou/webui-usereducer
Browse files Browse the repository at this point in the history
webui: start using useReducer for managing complicated state objects centrally
  • Loading branch information
KKoukiou committed Jun 23, 2023
2 parents 40445be + 1b30b11 commit 6ef8f9d
Show file tree
Hide file tree
Showing 12 changed files with 384 additions and 159 deletions.
57 changes: 57 additions & 0 deletions ui/webui/src/actions/localization-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2023 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with This program; If not, see <http://www.gnu.org/licenses/>.
*/

import {
getCommonLocales,
getLanguages,
getLanguageData,
getLocales,
getLocaleData,
} from "../apis/localization.js";

export const getLanguagesAction = () => {
return async function fetchUserThunk (dispatch) {
const languageIds = await getLanguages();

dispatch(getCommonLocalesAction());
return languageIds.map(language => dispatch(getLanguageDataAction({ language })));
};
};

export const getLanguageDataAction = ({ language }) => {
return async function fetchUserThunk (dispatch) {
const localeIds = await getLocales({ lang: language });
const languageData = await getLanguageData({ lang: language });
const locales = await Promise.all(localeIds.map(async locale => await getLocaleData({ locale })));

return dispatch({
type: "GET_LANGUAGE_DATA",
payload: { languageData: { [language]: { languageData, locales } } }
});
};
};

export const getCommonLocalesAction = () => {
return async function fetchUserThunk (dispatch) {
const commonLocales = await getCommonLocales();

return dispatch({
type: "GET_COMMON_LOCALES",
payload: { commonLocales }
});
};
};
84 changes: 84 additions & 0 deletions ui/webui/src/actions/storage-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright (C) 2023 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with This program; If not, see <http://www.gnu.org/licenses/>.
*/

import cockpit from "cockpit";

import {
getAllDiskSelection,
getDeviceData,
getDevices,
getDiskFreeSpace,
getDiskTotalSpace,
getFormatData,
getUsableDisks,
} from "../apis/storage.js";

export const getDevicesAction = () => {
return async function fetchUserThunk (dispatch) {
const devices = await getDevices();
return devices[0].map(device => dispatch(getDeviceDataAction({ device })));
};
};

export const getDeviceDataAction = ({ device }) => {
return async function fetchUserThunk (dispatch) {
let devData = {};
const deviceData = await getDeviceData({ disk: device })
.then(res => {
devData = res[0];
return getDiskFreeSpace({ diskNames: [device] });
})
.then(free => {
// Since the getDeviceData returns an object with variants as values,
// extend it with variants to keep the format consistent
devData.free = cockpit.variant(String, free[0]);
return getDiskTotalSpace({ diskNames: [device] });
})
.then(total => {
devData.total = cockpit.variant(String, total[0]);
return getFormatData({ diskName: device });
})
.then(formatData => {
devData.formatData = formatData[0];
return ({ [device]: devData });
})
.catch(console.error);

return dispatch({
type: "GET_DEVICE_DATA",
payload: { deviceData }
});
};
};

export const getDiskSelectionAction = () => {
return async function fetchUserThunk (dispatch) {
const usableDisks = await getUsableDisks();
const diskSelection = await getAllDiskSelection();

return dispatch({
type: "GET_DISK_SELECTION",
payload: {
diskSelection: {
ignoredDisks: diskSelection[0].IgnoredDisks.v,
selectedDisks: diskSelection[0].SelectedDisks.v,
usableDisks: usableDisks[0],
}
},
});
};
};
28 changes: 27 additions & 1 deletion ui/webui/src/apis/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
import cockpit from "cockpit";

import { getDiskSelectionAction } from "../actions/storage-actions.js";

export class StorageClient {
constructor (address) {
if (StorageClient.instance) {
Expand Down Expand Up @@ -284,13 +286,21 @@ export const resetPartitioning = () => {
* @returns {Promise} Resolves a DBus path to a task
*/
export const runStorageTask = ({ task, onSuccess, onFail }) => {
// FIXME: This is a workaround for 'Succeeded' signal being emited twice
let succeededEmitted = false;
const taskProxy = new StorageClient().client.proxy(
"org.fedoraproject.Anaconda.Task",
task
);
const addEventListeners = () => {
taskProxy.addEventListener("Stopped", () => taskProxy.Finish().catch(onFail));
taskProxy.addEventListener("Succeeded", onSuccess);
taskProxy.addEventListener("Succeeded", () => {
if (succeededEmitted) {
return;
}
succeededEmitted = true;
onSuccess();
});
};
taskProxy.wait(() => {
addEventListeners();
Expand Down Expand Up @@ -390,3 +400,19 @@ export const setSelectedDisks = ({ drives }) => {
]
);
};

export const startEventMonitorStorage = ({ dispatch }) => {
return new StorageClient().client.subscribe(
{ },
(path, iface, signal, args) => {
switch (signal) {
case "PropertiesChanged":
if (args[0] === "org.fedoraproject.Anaconda.Modules.Storage.DiskSelection") {
dispatch(getDiskSelectionAction());
}
break;
default:
console.debug(`Unhandled signal on ${path}: ${iface}.${signal}`);
}
});
};
10 changes: 8 additions & 2 deletions ui/webui/src/components/AnacondaWizard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ import { InstallationProgress } from "./installation/InstallationProgress.jsx";
import { ReviewConfiguration, ReviewConfigurationConfirmModal } from "./review/ReviewConfiguration.jsx";
import { exitGui } from "../helpers/exit.js";
import { usePageLocation } from "hooks";
import { resetPartitioning } from "../apis/storage.js";
import {
resetPartitioning
} from "../apis/storage.js";

const _ = cockpit.gettext;

export const AnacondaWizard = ({ onAddErrorNotification, toggleContextHelp, hideContextHelp, title, conf }) => {
export const AnacondaWizard = ({ dispatch, storageData, localizationData, onAddErrorNotification, toggleContextHelp, hideContextHelp, title, conf }) => {
const [isFormValid, setIsFormValid] = useState(true);
const [stepNotification, setStepNotification] = useState();
const [isInProgress, setIsInProgress] = useState(false);
Expand All @@ -55,6 +57,7 @@ export const AnacondaWizard = ({ onAddErrorNotification, toggleContextHelp, hide
const stepsOrder = [
{
component: InstallationLanguage,
data: { dispatch, languages: localizationData.languages, commonLocales: localizationData.commonLocales },
id: "installation-language",
label: _("Welcome"),
},
Expand All @@ -64,6 +67,7 @@ export const AnacondaWizard = ({ onAddErrorNotification, toggleContextHelp, hide
label: _("Installation destination"),
steps: [{
component: InstallationDestination,
data: { deviceData: storageData.devices, diskSelection: storageData.diskSelection, dispatch },
id: "storage-devices",
label: _("Storage devices")
}, {
Expand All @@ -78,6 +82,7 @@ export const AnacondaWizard = ({ onAddErrorNotification, toggleContextHelp, hide
},
{
component: ReviewConfiguration,
data: { deviceData: storageData.devices, diskSelection: storageData.diskSelection },
id: "installation-review",
label: _("Review and install"),
},
Expand Down Expand Up @@ -144,6 +149,7 @@ export const AnacondaWizard = ({ onAddErrorNotification, toggleContextHelp, hide
window.sessionStorage.setItem("storage-scenario-id", scenarioId);
setStorageScenarioId(scenarioId);
}}
{...s.data}
/>
),
});
Expand Down
11 changes: 9 additions & 2 deletions ui/webui/src/components/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ import { HelpDrawer } from "./HelpDrawer.jsx";

import { BossClient } from "../apis/boss.js";
import { LocalizationClient } from "../apis/localization.js";
import { StorageClient } from "../apis/storage.js";
import { StorageClient, startEventMonitorStorage } from "../apis/storage.js";
import { PayloadsClient } from "../apis/payloads";

import { readBuildstamp, getIsFinal } from "../helpers/betanag.js";
import { readConf } from "../helpers/conf.js";
import { useReducerWithThunk, reducer, initialState } from "../reducer.js";

export const Application = () => {
const [address, setAddress] = useState();
Expand All @@ -47,6 +48,7 @@ export const Application = () => {
const [isHelpExpanded, setIsHelpExpanded] = useState(false);
const [helpContent, setHelpContent] = useState("");
const [prettyName, setPrettyName] = useState("");
const [state, dispatch] = useReducerWithThunk(reducer, initialState);

useEffect(() => {
cockpit.file("/run/anaconda/bus.address").watch(address => {
Expand All @@ -59,6 +61,8 @@ export const Application = () => {
clients.forEach(c => c.init());

setAddress(address);

startEventMonitorStorage({ dispatch });
});

readConf().then(
Expand All @@ -72,7 +76,7 @@ export const Application = () => {
);

readOsRelease().then(osRelease => setPrettyName(osRelease.PRETTY_NAME));
}, []);
}, [dispatch]);

const onAddNotification = (notificationProps) => {
setNotifications({
Expand Down Expand Up @@ -139,6 +143,9 @@ export const Application = () => {
toggleContextHelp={toggleContextHelp}
hideContextHelp={() => setIsHelpExpanded(false)}
title={title}
storageData={state.storage}
localizationData={state.localization}
dispatch={dispatch}
conf={conf}
/>
</AddressContext.Provider>
Expand Down
Loading

0 comments on commit 6ef8f9d

Please sign in to comment.