@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
JavaScript
;
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;