src/sagas/widgetDependency.js
import {
select,
fork,
actionChannel,
take,
call,
put,
} from 'redux-saga/effects';
import {
keys,
isEqual,
reduce,
map,
includes,
some,
isEmpty,
forOwn,
} from 'lodash';
import {
REGISTER_DEPENDENCY,
UPDATE_WIDGET_DEPENDENCY,
} from '../constants/dependency';
import { CLEAR, COPY, SET } from '../constants/models';
import { DEPENDENCY_TYPES } from '../core/dependencyTypes';
import {
dataRequestWidget,
showWidget,
hideWidget,
enableWidget,
disableWidget,
} from '../actions/widgets';
import { getModelsByDependency } from '../selectors/models';
import { makeWidgetVisibleSelector } from '../selectors/widgets';
import propsResolver from '../utils/propsResolver';
export const reduceFunction = (isTrue, { model, config }) => {
return isTrue && propsResolver('`' + config.condition + '`', model);
};
export const sortDependency = dependency => {
let tmpFetch = {};
let newDependency = {};
forOwn(dependency, (v, k) => {
if (k !== 'fetch') {
newDependency[k] = v;
} else {
tmpFetch[k] = v;
}
});
if (!isEmpty(tmpFetch)) {
newDependency = {
...newDependency,
...tmpFetch,
};
}
return newDependency;
};
/**
* Наблюдение за регистрацией зависимостей виджетов и изменением модели
* @returns {IterableIterator<*>}
*/
export function* watchDependency() {
let widgetsDependencies = {};
const channel = yield actionChannel([
REGISTER_DEPENDENCY,
SET,
COPY,
CLEAR,
UPDATE_WIDGET_DEPENDENCY,
]);
while (true) {
const prevState = yield select();
const action = yield take(channel);
const { type, payload } = action;
const { widgetId, dependency, key } = payload;
const state = yield select();
switch (type) {
case REGISTER_DEPENDENCY: {
widgetsDependencies = yield call(
registerWidgetDependency,
widgetsDependencies,
widgetId,
dependency
);
yield call(
resolveWidgetDependency,
prevState,
state,
widgetsDependencies,
widgetId
);
break;
}
case SET:
case COPY:
case CLEAR: {
yield call(
resolveWidgetDependency,
prevState,
state,
widgetsDependencies,
key
);
break;
}
case UPDATE_WIDGET_DEPENDENCY: {
yield call(forceUpdateDependency, state, widgetsDependencies, widgetId);
break;
}
default:
break;
}
}
}
export function* forceUpdateDependency(state, widgetsDependencies, widgetId) {
const widgetDependenciesKeys = keys(widgetsDependencies);
for (let i = 0; i < widgetDependenciesKeys.length; i++) {
const widgetDependencyItem = widgetsDependencies[widgetDependenciesKeys[i]];
const dependencyItem = widgetDependencyItem.dependency;
const dependencyItemKeys = keys(dependencyItem);
for (let j = 0; j < dependencyItemKeys.length; j++) {
const someDependency = dependencyItem[dependencyItemKeys[j]];
if (some(someDependency, ({ on }) => includes(on, widgetId))) {
const isVisible = makeWidgetVisibleSelector(widgetId)(state);
const dependencyType = dependencyItemKeys[j];
const model = getModelsByDependency(someDependency)(state);
yield call(
resolveDependency,
dependencyType,
widgetDependencyItem.widgetId,
model,
isVisible
);
}
}
}
}
/**
* Добавляет в хранилище новопришедший виджет с его widgetId и dependency
* @param widgetsDependencies
* @param widgetId
* @param dependency
* @returns {{}}
*/
export function registerWidgetDependency(
widgetsDependencies,
widgetId,
dependency
) {
if (dependency) {
let parents = [];
dependency = sortDependency(dependency);
map(dependency, dep => {
map(dep, d => {
if (d.on) {
parents.push(d.on);
}
});
});
return {
...widgetsDependencies,
[widgetId]: {
widgetId,
dependency,
parents,
},
};
}
return widgetsDependencies;
}
/**
* Резолв всех зависимостей виджета
* @param prevState
* @param state
* @param widgetsDependencies
* @returns {IterableIterator<*|CallEffect>}
*/
export function* resolveWidgetDependency(
prevState,
state,
widgetsDependencies
) {
const dependenciesKeys = keys(widgetsDependencies);
for (let i = 0; i < dependenciesKeys.length; i++) {
const { dependency, widgetId } = widgetsDependencies[dependenciesKeys[i]];
const widgetDependenciesKeys = keys(dependency);
for (let j = 0; j < widgetDependenciesKeys.length; j++) {
const prevIsVisible = makeWidgetVisibleSelector(widgetId)(prevState);
const isVisible = yield select(makeWidgetVisibleSelector(widgetId));
const prevModel = getModelsByDependency(
dependency[widgetDependenciesKeys[j]]
)(prevState);
const model = getModelsByDependency(
dependency[widgetDependenciesKeys[j]]
)(state);
if (!isEqual(prevModel, model)) {
yield call(
resolveDependency,
widgetDependenciesKeys[j],
widgetId,
model,
isVisible,
prevIsVisible
);
}
}
}
}
/**
* Резолв конкретной зависимости по типу
* @param dependencyType
* @param widgetId
* @param model
* @param isVisible
* @param prevIsVisible
* @returns {IterableIterator<*|CallEffect>}
*/
export function* resolveDependency(
dependencyType,
widgetId,
model,
isVisible,
prevIsVisible
) {
switch (dependencyType) {
case DEPENDENCY_TYPES.fetch: {
if (prevIsVisible !== false && isVisible) {
yield call(resolveFetchDependency, widgetId);
}
break;
}
case DEPENDENCY_TYPES.visible: {
yield call(resolveVisibleDependency, widgetId, model);
break;
}
case DEPENDENCY_TYPES.enabled: {
yield call(resolveEnabledDependency, widgetId, model);
break;
}
default:
break;
}
}
/**
* Резолв запросов
* @param widgetId
* @returns {IterableIterator<*>}
*/
export function* resolveFetchDependency(widgetId) {
yield put(dataRequestWidget(widgetId));
}
/**
* Резолв видимости
* @param widgetId
* @param model
* @returns {IterableIterator<*>}
*/
export function* resolveVisibleDependency(widgetId, model) {
const visible = reduce(model, reduceFunction, true);
if (visible) {
yield put(showWidget(widgetId));
} else {
yield put(hideWidget(widgetId));
}
}
/**
* Резолв активности
* @param widgetId
* @param model
* @returns {IterableIterator<*>}
*/
export function* resolveEnabledDependency(widgetId, model) {
const enabled = reduce(model, reduceFunction, true);
if (enabled) {
yield put(enableWidget(widgetId));
} else {
yield put(disableWidget(widgetId));
}
}
export const widgetDependencySagas = [fork(watchDependency)];