/* * Copyright (C) 2023 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ import { useCallback, useReducer } from "react"; /* Initial state for the storeage store substate */ export const storageInitialState = { appliedPartitioning: null, deviceTrees: { "": { actions: [], devices: {}, mountPoints: {}, }, }, diskSelection: { ignoredDisks: [], selectedDisks: [], usableDisks: [] }, luks: { confirmPassphrase: "", encrypted: false, passphrase: "", }, mountPoints: {}, partitioning: {}, storageScenarioId: null, }; /* Initial state for the localization store substate */ export const localizationInitialState = { commonLocales: [], compositorLayouts: [], keyboardLayouts: [], language: "", languageKickstarted: false, languages: {}, plannedVconsole: undefined, plannedXlayouts: undefined, userConfigured: false, xlayouts: undefined, }; /* Initial state for the boss store substate */ export const bossInitialState = { installationStatus: null, pendingError: { message: "", type: "" }, }; /* Intial state for the network store substate */ export const networkInitialState = { connected: null }; /* Intial state for the runtime store substate */ export const runtimeInitialState = { connected: null, }; /* Initial state for the timezone store substate */ export const timezoneInitialState = { allValidTimezones: {}, kickstarted: false, timezone: "", userConfigured: false, }; export const miscInitialState = { isFetching: false, }; /* Initial state for the payload store substate */ export const payloadInitialState = { environments: [], groups: [], packagesKickstarted: false, selection: null, type: null, }; /* Initial state for the users store substate */ /* FIXME: This is not storing information from the anaconda backend, but also non-submitted user input */ /* The Store is meant to store information from the backend only */ export const usersInitialState = { adminUserExists: false, canModifyRootConfiguration: true, canModifyUserConfiguration: true, confirmPassword: "", isRootEnabled: false, isRootPasswordSet: false, password: "", rootConfirmPassword: "", rootPassword: "", users: [], }; /* Initial state for the global store */ export const initialState = { boss: bossInitialState, localization: localizationInitialState, misc: miscInitialState, network: networkInitialState, payload: payloadInitialState, runtime: runtimeInitialState, storage: storageInitialState, timezone: timezoneInitialState, users: usersInitialState, }; /* Custom hook to use the reducer with async actions */ export const useReducerWithThunk = (reducer, initialState) => { const [state, dispatch] = useReducer(reducer, initialState); function customDispatch (action) { if (typeof action === "function") { return action(customDispatch); } else { dispatch(action); } } // Memoize so you can include it in the dependency array without causing infinite loops // eslint-disable-next-line react-hooks/exhaustive-deps const stableDispatch = useCallback(customDispatch, [dispatch]); return [state, stableDispatch]; }; export const reducer = (state, action) => { return ({ boss: bossReducer(state.boss, action), localization: localizationReducer(state.localization, action), misc: miscReducer(state.misc, action), network: networkReducer(state.network, action), payload: payloadReducer(state.payload, action), runtime: runtimeReducer(state.runtime, action), storage: storageReducer(state.storage, action), timezone: timezoneReducer(state.timezone, action), users: usersReducer(state.users, action) }); }; export const storageReducer = (state = storageInitialState, action) => { if (action.type === "GET_DEVICES_DATA") { return { ...state, deviceTrees: { ...state.deviceTrees, [state.appliedPartitioning ? state.partitioning.deviceTree.path : ""]: { actions: action.payload.actions, devices: action.payload.devices, existingSystems: action.payload.existingSystems, mountPoints: action.payload.mountPoints, } } }; } else if (action.type === "GET_DISK_SELECTION") { return { ...state, diskSelection: action.payload.diskSelection }; } else if (action.type === "GET_PARTITIONING_DATA") { return { ...state, partitioning: { ...state.partitioning, ...action.payload.partitioningData, deviceTree: action.payload.deviceTree, storageScenarioId: state.storageScenarioId } }; } else if (action.type === "SET_APPLIED_PARTITIONING") { return { ...state, appliedPartitioning: action.payload.appliedPartitioning }; } else if (action.type === "SET_STORAGE_SCENARIO") { return { ...state, storageScenarioId: action.payload.scenario }; } else if (action.type === "SET_LUKS_ENCRYPTION_DATA") { const newLuksState = { ...state.luks, ...action.payload, }; if (action.payload.encrypted === false) { newLuksState.passphrase = ""; newLuksState.confirmPassphrase = ""; } return { ...state, luks: newLuksState, }; } else { return state; } }; export const localizationReducer = (state = localizationInitialState, action) => { if (action.type === "GET_LANGUAGE_DATA") { return { ...state, languages: { ...state.languages, ...action.payload.languageData } }; } else if (action.type === "GET_COMMON_LOCALES") { return { ...state, commonLocales: action.payload.commonLocales }; } else if (action.type === "GET_LANGUAGE") { return { ...state, language: action.payload.language }; } else if (action.type === "GET_KEYBOARD_LAYOUTS") { return { ...state, keyboardLayouts: action.payload.keyboardLayouts }; } else if (action.type === "GET_PLANNED_KEYBOARD_CONFIGURATION") { return { ...state, plannedVconsole: action.payload.plannedVconsole, plannedXlayouts: action.payload.plannedXlayouts, xlayouts: action.payload.xlayouts, }; } else if (action.type === "SET_LANGUAGE_KICKSTARTED") { return { ...state, languageKickstarted: action.payload.languageKickstarted }; } else if (action.type === "SET_LANGUAGE_USER_CONFIGURED") { return { ...state, userConfigured: true }; } else { return state; } }; export const bossReducer = (state = bossInitialState, action) => { if (action.type === "GET_INSTALLATION_STATUS") { return { ...state, installationStatus: action.payload.status }; } else if (action.type === "GET_PENDING_ERROR") { return { ...state, pendingError: { message: action.payload.message, type: action.payload.type } }; } else { return state; } }; export const networkReducer = (state = networkInitialState, action) => { if (action.type === "GET_NETWORK_CONNECTED") { return { ...state, connected: action.payload.connected }; } else if (action.type === "GET_NETWORK_HOSTNAME") { return { ...state, hostname: action.payload.hostname }; } else { return state; } }; const miscReducer = (state = miscInitialState, action) => { if (action.type === "SET_IS_FETCHING") { return { ...state, isFetching: action.payload.isFetching }; } else { return state; } }; export const runtimeReducer = (state = runtimeInitialState, action) => { if (action.type === "GET_RUNTIME_PASSWORD_POLICIES") { return { ...state, passwordPolicies: action.payload.passwordPolicies }; } else { return state; } }; export const timezoneReducer = (state = timezoneInitialState, action) => { if (action.type === "SET_TIMEZONE") { const { kickstarted, timezone } = action.payload; return { ...state, ...(kickstarted !== undefined && { kickstarted }), ...(timezone !== undefined && { timezone }), }; } else if (action.type === "SET_TIMEZONE_USER_CONFIGURED") { return { ...state, userConfigured: true }; } else if (action.type === "SET_ALL_VALID_TIMEZONES") { return { ...state, allValidTimezones: action.payload.allValidTimezones }; } else { return state; } }; export const payloadReducer = (state = payloadInitialState, action) => { if (action.type === "SET_PAYLOAD_SELECTION") { return { ...state, selection: action.payload.selection, ...(action.payload.packagesKickstarted !== undefined ? { packagesKickstarted: action.payload.packagesKickstarted } : {}), }; } else if (action.type === "SET_PAYLOAD_ENVIRONMENTS") { return { ...state, environments: action.payload.environments || [] }; } else if (action.type === "SET_PAYLOAD_TYPE") { return { ...state, type: action.payload.type }; } else if (action.type === "SET_PAYLOAD_GROUPS") { return { ...state, groups: action.payload.groups }; } else { return state; } }; export const usersReducer = (state = usersInitialState, action) => { if (action.type === "SET_USERS") { const { firstUser, ...rest } = action.payload; let next = { ...state, ...rest }; if (firstUser !== undefined) { const existing = state.users ?? []; const [currentFirst = {}, ...tail] = existing; const updatedFirst = { ...currentFirst, gecos: firstUser.gecos, name: firstUser.name, }; const updatedUsers = (firstUser.name || firstUser.gecos || tail.length > 0) ? [updatedFirst, ...tail] : []; next = { ...next, users: updatedUsers, }; if (firstUser.password !== undefined) { next.password = firstUser.password; } if (firstUser.confirmPassword !== undefined) { next.confirmPassword = firstUser.confirmPassword; } } return next; } else if (action.type === "SET_USER_CONFIGURATION_POLICY") { return { ...state, ...action.payload }; } else { return state; } };