Home Reference Source

src/reducers/models.js

import {
  omit,
  map as mapFn,
  isArray,
  isObject,
  merge,
  set,
  pick,
  each,
  isString,
} from 'lodash';
import {
  SET,
  REMOVE,
  REMOVE_ALL,
  SYNC,
  UPDATE,
  UPDATE_MAP,
  MERGE,
  COPY,
  CLEAR,
} from '../constants/models';
import { omitDeep, setIn } from '../tools/helpers';

/**
 * Префиксы моделей в N2O
 * @see https://n2o.i-novus.ru/react/docs/manual/#_%D0%9C%D0%BE%D0%B4%D0%B5%D0%BB%D0%B8_%D0%B2%D0%B8%D0%B4%D0%B6%D0%B5%D1%82%D0%B0
 * @type {Object}
 * @property {object} datasource
 * @property {object} select
 * @property {object} filter
 * @property {object} multi
 * @property {object} resolve
 * @property {object} edit
 */
const modelState = {
  /* Модели от сервера */
  datasource: {},
  /* Модели для клиента */
  select: {},
  filter: {},
  multi: {},
  resolve: {},
  edit: {},
};

/**
 * Функция определяет тип модели и возвращает такой же тип
 * @param state
 * @param action
 */
function resolveUpdate(state, action) {
  const { key, field, value } = action.payload;

  if (!field) return state;

  if (isArray(state[key])) {
    return setIn(state[key], field, value);
  }
  if (isObject(state[key])) {
    return setIn(state[key], field, value);
  }

  return setIn(state, field, value);
}

function resolve(state, action) {
  switch (action.type) {
    case SET:
      return Object.assign({}, state, {
        [action.payload.key]: action.payload.model,
      });
    case REMOVE:
      return omit(state, action.payload.key);
    case SYNC:
      return Object.assign(
        {},
        state,
        action.payload.keys.map(key => ({ [key]: action.payload.model }))
      );
    case UPDATE:
      return {
        ...state,
        [action.payload.key]: resolveUpdate(state, action),
      };
    case UPDATE_MAP:
      const { value, key, field, map } = action.payload;
      const newValue = isString(value) ? [value] : value;

      return setIn(state, [key, field], mapFn(newValue, v => ({ [map]: v })));

    case COPY:
      return {
        ...state[action.payload.target.prefix],
        [action.payload.target.key]: {
          ...state[action.payload.source.prefix][action.payload.source.key],
        },
      };
    case CLEAR:
      return {
        ...state,
        [action.payload.key]: {
          ...pick(state[action.payload.key], [action.payload.exclude]),
        },
      };
    default:
      return state;
  }
}

/**
 * Редюсер для моделей
 * @ignore
 */
export default function models(state = modelState, action) {
  switch (action.type) {
    case SET:
    case REMOVE:
    case SYNC:
    case UPDATE:
    case UPDATE_MAP:
      return Object.assign({}, state, {
        [action.payload.prefix]: resolve(state[action.payload.prefix], action),
      });
    case COPY:
      return Object.assign({}, state, {
        [action.payload.target.prefix]: resolve(state, action),
      });
    case MERGE:
      return { ...merge(state, action.payload.combine) };
    case REMOVE_ALL:
      return {
        ...state,
        ...omitDeep(state, [action.payload.key]),
      };
    case CLEAR:
      const res = {};
      each(action.payload.prefixes, prefix => {
        res[prefix] = {
          ...state[prefix],
          [action.payload.key]: {
            ...pick(state[prefix][action.payload.key], [
              action.payload.exclude,
            ]),
          },
        };
      });
      return {
        ...state,
        ...res,
      };
    default:
      return state;
  }
}