Home Reference Source

src/sagas/toolbar.js

import {
  select,
  put,
  call,
  all,
  take,
  fork,
  takeLatest,
  cancelled,
} from 'redux-saga/effects';
import { every, filter, forOwn, get, some, isEmpty, values } from 'lodash';
import { SET } from '../constants/models';
import { getContainerButtons } from '../selectors/toolbar';
import {
  changeButtonDisabled,
  changeButtonVisiblity,
} from '../actions/toolbar';
import evalExpression from '../utils/evalExpression';
import { REGISTER_BUTTON } from '../constants/toolbar';

/**
 * резолв кондишена кнопки
 * @param conditions
 * @param model
 * @returns {boolean}
 */

export const resolveConditions = (conditions = [], model) =>
  every(conditions, ({ expression, modelLink }) =>
    evalExpression(expression, get(model, modelLink, {}))
  );

export function* handleAction(action) {
  const buttons = yield select(
    getContainerButtons(action.payload.key || action.payload.widgetId)
  );
  yield all(values(buttons || []).map(v => call(resolveButton, v)));
}

/**
 * Функция для мониторинга изменения видимости родителя списка
 * @param key
 * @param id
 */
export function* setParentVisibleIfAllChildChangeVisible({ key, id }) {
  const buttons = yield select(getContainerButtons(key));
  const currentBtn = get(buttons, id);
  const parentId = get(currentBtn, 'parentId');

  if (parentId) {
    const currentBtnGroup = filter(buttons, ['parentId', parentId]);

    const isAllChildHidden = every(currentBtnGroup, ['visible', false]);
    const isAllChildVisible = some(currentBtnGroup, ['visible', true]);
    const isParentVisible = get(buttons, [parentId, 'visible'], false);

    if (isAllChildHidden && isParentVisible) {
      yield put(changeButtonVisiblity(key, parentId, false));
    }
    if (isAllChildVisible && !isParentVisible) {
      yield put(changeButtonVisiblity(key, parentId, true));
    }
  }
}

/**
 * резолв всех условий
 * @param button
 * @param model
 */

export function* resolveButton(button) {
  const model = yield select();
  if (button.conditions) {
    const { visible, enabled } = button.conditions;
    if (visible) {
      const nextVisible = resolveConditions(visible, model);
      yield put(changeButtonVisiblity(button.key, button.id, nextVisible));
      yield call(setParentVisibleIfAllChildChangeVisible, button);
    }
    if (enabled) {
      const nextEnable = resolveConditions(enabled, model);
      yield put(changeButtonDisabled(button.key, button.id, !nextEnable));
    }
  }
  if (button.resolveEnabled) {
    const { modelLink: btnModelLink, on } = button.resolveEnabled;
    const modelOnLink = get(model, btnModelLink, {});
    const nextEnabled = on.some(o => modelOnLink[o]);
    yield put(changeButtonDisabled(button.key, button.id, !nextEnabled));
  }
}

export function* watchModel(buttons, { payload }) {
  const { prefix, key } = payload;
  const modelLink = `models.${prefix}['${key}']`;

  if (buttons[modelLink]) {
    yield all(buttons[modelLink].map(button => fork(resolveButton, button)));
  }
}

function* watchRegister() {
  try {
    while (true) {
      const { payload: payloadRegister } = yield take(REGISTER_BUTTON);
      const { conditions } = payloadRegister;
      let buttons = {};

      if (conditions && !isEmpty(conditions)) {
        buttons = yield call(prepareButton, buttons, payloadRegister);
        yield fork(resolveButton, payloadRegister);
        // todo: Перейти на redux-saga@1.0.0 и использовать takeLeading
        yield takeLatest(SET, watchModel, buttons);
      }
    }
  } finally {
    if (yield cancelled()) {
      // todo: добавить cancel саги, когда кнопка unregister
    }
  }
}

export function prepareButton(buttons, payload) {
  const { conditions } = payload;
  let newButtons = Object.assign({}, buttons);
  let modelsLinkBuffer = [];
  forOwn(conditions, condition => {
    condition.map(({ modelLink }) => {
      if (!modelsLinkBuffer.includes(modelLink)) {
        const modelLinkArray = buttons[modelLink]
          ? [...buttons[modelLink], { ...payload }]
          : [{ ...payload }];
        newButtons = {
          ...newButtons,
          [modelLink]: modelLinkArray,
        };
        modelsLinkBuffer.push(modelLink);
      }
    });
  });

  return newButtons;
}

export const toolbarSagas = [fork(watchRegister)];