blob: 138336d71cfdc660049644f75ccc95a68c59b41e [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Defines reducers for personalization app. Reducers must be a
* pure function that returns a new state object if anything has changed.
* @see [redux tutorial]{@link https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers}
*/
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {FilePath} from 'chrome://resources/mojo/mojo/public/mojom/base/file_path.mojom-webui.js';
import {isNonEmptyArray} from '../common/utils.js';
import {PersonalizationActionName} from './personalization_actions.js';
import {Actions} from './personalization_actions.js';
import {WallpaperImage} from './personalization_app.mojom-webui.js';
import {PersonalizationState} from './personalization_state.js';
import {WallpaperActionName} from './wallpaper/wallpaper_actions.js';
import {wallpaperReducers} from './wallpaper/wallpaper_reducers.js';
import {WallpaperState} from './wallpaper/wallpaper_state.js';
export type DisplayableImage = FilePath|WallpaperImage;
export type ReducerFunction<State> =
(state: State, action: Actions, globalState: PersonalizationState) => State;
/**
* Combines reducers into a single top level reducer. Inspired by Redux's
* |combineReducers| functions.
*/
function combineReducers<T>(mapping: {[K in keyof T]: ReducerFunction<T[K]>}): (
state: T, action: Actions, globalState: PersonalizationState) => T {
function reduce(
state: T, action: Actions, globalState: PersonalizationState): T {
const newState: T =
(Object.keys(mapping) as Array<keyof T>).reduce((result, key) => {
const func = mapping[key] as ReducerFunction<T[typeof key]>;
result[key] = func(state[key], action, globalState);
return result;
}, {} as T);
const change = (Object.keys(state) as Array<keyof T>)
.some((key) => newState[key] !== state[key]);
return change ? newState : state;
}
return reduce;
}
function errorReducer(
state: PersonalizationState['error'], action: Actions,
globalState: PersonalizationState): PersonalizationState['error'] {
switch (action.name) {
case WallpaperActionName.END_SELECT_IMAGE:
const {success} = action;
if (success) {
return null;
}
return state || loadTimeData.getString('setWallpaperError');
case WallpaperActionName.SET_SELECTED_IMAGE:
const {image} = action;
if (image) {
return state;
}
return state || loadTimeData.getString('loadWallpaperError');
// Show network error toast if local images are available but online
// collections are failed to load. As local images and online collections
// are loaded asynchronously, we need to check the above condition for both
// SET_LOCAL_IMAGES and SET_COLLECTIONS actions.
case WallpaperActionName.SET_LOCAL_IMAGES:
const {images} = action;
if (isNonEmptyArray(images) &&
!globalState.wallpaper.loading.collections &&
!isNonEmptyArray(globalState.wallpaper.backdrop.collections)) {
return state || loadTimeData.getString('networkError');
}
return state;
case WallpaperActionName.SET_COLLECTIONS:
const {collections} = action;
if (!globalState.wallpaper.loading.local.images &&
isNonEmptyArray(globalState.wallpaper.local.images) &&
!isNonEmptyArray(collections)) {
return state || loadTimeData.getString('networkError');
}
return state;
case PersonalizationActionName.DISMISS_ERROR:
if (!state) {
console.warn(
'Received dismiss error action when error is already null');
}
return null;
default:
return state;
}
}
const root = combineReducers<PersonalizationState>({
wallpaper: combineReducers<WallpaperState>(wallpaperReducers),
error: errorReducer,
});
export function reduce(
state: PersonalizationState, action: Actions): PersonalizationState {
return root(state, action, state);
}