UNPKG

@adaptabletools/adaptable-cjs

Version:

Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

161 lines (160 loc) 7.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.mergeReducer = exports.MergeState = exports.MergeStateFunction = exports.ProcessKeepUserDefinedRevision = exports.AddStateSource = void 0; const tslib_1 = require("tslib"); const mergeWith_1 = tslib_1.__importDefault(require("lodash/mergeWith")); const merge_1 = tslib_1.__importDefault(require("lodash/merge")); const isArray_1 = tslib_1.__importDefault(require("lodash/isArray")); const extend_1 = tslib_1.__importDefault(require("lodash/extend")); const isObject_1 = tslib_1.__importDefault(require("lodash/isObject")); const AdaptableLogger_1 = require("../../agGrid/AdaptableLogger"); const AdaptableHelper_1 = tslib_1.__importDefault(require("../../Utilities/Helpers/AdaptableHelper")); function customizer(objValue, srcValue) { if ((0, isArray_1.default)(objValue)) { if (!Array.isArray(srcValue)) { // TODO AFL: clarify if this is still relevant, after the state & options refactoring /** * the new value might be a function: eg: UserInterface.ContextMenuItems defaults to an array * in the redux state, while the user might provide a function * so in this case, make the user provided value win */ return srcValue; } const length = srcValue ? srcValue.length : null; const result = (0, mergeWith_1.default)(objValue, srcValue, customizer); if (length != null) { // when merging arrays, lodash result has the length of the // longest array, but we don't want that to happen // so we restrict to the current length result.length = length; } return result; } } function AddStateSource(stateObject = {}, source) { const traverseStateObject = (object) => { Object.values(object).forEach((value) => { // add a Source only to AdaptableObjects which do NOT already have a source defined if (AdaptableHelper_1.default.isAdaptableObject(value) && value.Source == null) { value.Source = source; } if (value !== null && typeof value === 'object') { traverseStateObject(value); } }); }; traverseStateObject(stateObject); return stateObject; } exports.AddStateSource = AddStateSource; function ProcessKeepUserDefinedRevision(configState, currentUserState) { const traverseStateObject = (configObject, stateObject) => { Object.entries(configObject).forEach(([key, configValue]) => { const stateValue = stateObject[key]; if (Array.isArray(configValue) && Array.isArray(stateValue)) { const userDefinedItems = stateValue.filter((item) => { return AdaptableHelper_1.default.isAdaptableObject(item) && item.Source !== 'InitialState'; }); configObject[key] = configValue.concat(userDefinedItems); // we probably don't need to call traverseStateObject on the array as well, // so we do the traversing on the else branch only } else { if (configValue !== null && typeof configValue === 'object' && typeof stateValue === 'object') { traverseStateObject(configValue, stateValue); } } }); }; traverseStateObject(configState, currentUserState); return configState; } exports.ProcessKeepUserDefinedRevision = ProcessKeepUserDefinedRevision; function deepClone(obj) { return JSON.parse(JSON.stringify(obj)); } function MergeStateFunction(oldState, newState) { // return MergeState(oldState, newState); if (newState === '') { newState = {}; } // add source 'InitialState' only to initial state objects const initialState = AddStateSource(oldState, 'InitialState'); // source 'User' will be added to all other objects, after the merge (see bottom of this function) let state = newState; if (!state || typeof state !== 'object') { // in case loadState returns something different than an empty object AdaptableLogger_1.AdaptableLogger.consoleWarnBase('State is something different than expected; expected an object, but received: ', state); state = {}; } // any Module in config that doesn't exist in state will be added for (const initialStateModuleName in initialState) { state[initialStateModuleName] = state[initialStateModuleName] ?? initialState[initialStateModuleName]; } // any Module in state that has an older revision than the config will be replaced for (const stateModuleName in state) { if (initialState[stateModuleName] != undefined) { // explicitly use double equals, as we want to avoid null as well const stateRevision = state[stateModuleName].Revision ?? 0; const initialStateRevision = initialState[stateModuleName].Revision ?? 0; const stateRevisionKey = typeof stateRevision === 'object' ? stateRevision.Key : stateRevision; const initialStateRevisionBehaviour = typeof initialStateRevision === 'object' ? initialStateRevision.Behavior : 'Override'; const initialStateRevisionKey = typeof initialStateRevision === 'object' ? initialStateRevision.Key : initialStateRevision; if (initialStateRevisionKey > stateRevisionKey) { if (initialStateRevisionBehaviour === 'Override') { state[stateModuleName] = initialState[stateModuleName]; } else { state[stateModuleName] = ProcessKeepUserDefinedRevision(deepClone(initialState[stateModuleName]), state[stateModuleName]); } } } } // add 'User' source to all state objects which do NOT have 'InitialState' const finalState = AddStateSource(state, 'User'); return finalState; } exports.MergeStateFunction = MergeStateFunction; // main merge function function MergeState(oldState, newState) { const result = (0, extend_1.default)({}, oldState); for (const key in newState) { if (!newState.hasOwnProperty(key)) { continue; } const value = newState[key]; // Assign if we don't need to merge at all if (!result.hasOwnProperty(key)) { result[key] = (0, isObject_1.default)(value) && !Array.isArray(value) ? (0, merge_1.default)({}, value) : value; continue; } const oldValue = result[key]; if ((0, isObject_1.default)(value) && !Array.isArray(value)) { // use both lodash functions so that we can merge from State onto Initial Adaptable State where it exists but from the former where it doesnt. result[key] = (0, mergeWith_1.default)({}, oldValue, value, customizer); } else { result[key] = value; } } return result; } exports.MergeState = MergeState; let initialState; const mergeReducer = (rootReducer, LOAD_STATE_TYPE) => { let finalReducer = rootReducer; finalReducer = (state, action) => { if (action.type === LOAD_STATE_TYPE) { initialState = initialState ?? state; state = MergeState(initialState, action.State); // put this new state on the action, since the root reducer further copies // keys from action.State to the new state action.State = state; } return rootReducer(state, action); }; return finalReducer; }; exports.mergeReducer = mergeReducer;