/* * This file is part of ORY Editor. * * ORY Editor is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ORY Editor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with ORY Editor. If not, see . * * @license LGPL-3.0 * @copyright 2016-2018 Aeneas Rekkas * @author Aeneas Rekkas * */ import pathOr from 'ramda/src/pathOr'; import { optimizeCell, optimizeRow, optimizeRows, optimizeCells, flatten } from './helper/optimize'; import { mergeDecorator } from './helper/merge'; import { isHoveringThis } from './helper/hover'; import { resizeCells } from './helper/sizing'; import { computeRow } from './helper/inline'; import { createCell, createRow } from '../../types/editable'; import { CELL_REMOVE, CELL_UPDATE_LAYOUT, CELL_UPDATE_CONTENT, CELL_INSERT_LEFT_OF, CELL_INSERT_RIGHT_OF, CELL_INSERT_ABOVE, CELL_INSERT_BELOW, CELL_INSERT_INLINE_LEFT, CELL_INSERT_INLINE_RIGHT, CELL_DRAG_HOVER, CELL_RESIZE, CELL_FOCUS, CELL_BLUR, CELL_BLUR_ALL } from '../../actions/cell'; import { Cell, Row } from '../../types/editable'; import { AnyAction } from 'redux'; import { CellHoverAction } from './../../actions/cell/drag'; const inner = (cb: Function, action: Object) => (state: Object) => cb(state, action); const identity = (state: Cell) => state; export const cell = (s: Cell, a: AnyAction): Cell => optimizeCell( ((state: Cell, action: AnyAction): Cell => { const reduce = () => { const content = pathOr( identity, ['content', 'plugin', 'reducer'], state ); const layout = pathOr(identity, ['layout', 'plugin', 'reducer'], state); return content( layout( { ...state, hover: null, rows: rows(state.rows, action), }, action ), action ); }; switch (action.type) { case CELL_UPDATE_CONTENT: if (action.id === state.id) { // If this cell is being updated, set the data const reduced = reduce(); return { ...reduced, content: { ...(state.content || {}), state: { ...pathOr({}, ['content', 'state'], reduced), ...action.state, }, }, }; } return reduce(); case CELL_UPDATE_LAYOUT: if (action.id === state.id) { // If this cell is being updated, set the data const reduced = reduce(); return { ...reduced, layout: { ...(state.layout || {}), state: { ...pathOr({}, ['layout', 'state'], reduced), ...action.state, }, }, }; } return reduce(); case CELL_FOCUS: if (action.id === state.id) { // If this cell is being focused, set the data return { ...reduce(), focused: true, focusSource: action.source }; } return { ...reduce(), focused: false, focusSource: null }; case CELL_BLUR: if (action.id === state.id) { // If this cell is being blurred, set the data return { ...reduce(), focused: false, focusSource: null }; } return reduce(); case CELL_BLUR_ALL: return { ...reduce(), focused: false }; case CELL_DRAG_HOVER: if (isHoveringThis(state, action as CellHoverAction)) { // if this is the cell we're hovering, set the hover attribute return { ...reduce(), hover: action.position }; } // or remove it if not return reduce(); case CELL_INSERT_ABOVE: if (isHoveringThis(state, action as CellHoverAction)) { return { ...createCell(), id: action.ids.cell, hover: null, rows: rows( [ { ...createRow(), id: action.ids.others[0], cells: [ { ...action.item, id: action.ids.item, inline: null }, ], }, { ...createRow(), id: action.ids.others[1], cells: [{ ...reduce(), id: action.ids.others[2] }], }, ], { ...action, hover: null } ), }; } return reduce(); case CELL_INSERT_BELOW: if (isHoveringThis(state, action as CellHoverAction)) { return { ...createCell(), id: action.ids.cell, hover: null, rows: rows( [ { ...createRow(), id: action.ids.others[0], cells: [{ ...reduce(), id: action.ids.others[1] }], }, { ...createRow(), id: action.ids.others[2], cells: [ { ...action.item, id: action.ids.item, inline: null }, ], }, ], { ...action, hover: null } ), }; } return reduce(); default: return reduce(); } })(s, a) ); export const cells = (s: Cell[] = [], a: AnyAction): Cell[] => optimizeCells( ((state: Cell[], action: AnyAction): Cell[] => { switch (action.type) { case CELL_RESIZE: // tslint:disable-next-line:no-any return resizeCells(state.map(inner(cell, action)), action as any); case CELL_INSERT_BELOW: case CELL_INSERT_ABOVE: return state .filter((c: Cell) => c.id !== action.item.id) .map(inner(cell, action)); case CELL_INSERT_LEFT_OF: return state .filter((c: Cell) => c.id !== action.item.id) .map((c: Cell) => isHoveringThis(c, action as CellHoverAction) ? [ { ...action.item, id: action.ids.item, inline: null }, { ...c, id: action.ids.others[0] }, ] : [c] ) .reduce(flatten, []) .map(inner(cell, action)); case CELL_INSERT_RIGHT_OF: return state .filter((c: Cell) => c.id !== action.item.id) .map((c: Cell) => isHoveringThis(c, action as CellHoverAction) ? [ { ...c, id: action.ids.others[0] }, { ...action.item, id: action.ids.item, inline: null }, ] : [c] ) .reduce(flatten, []) .map(inner(cell, action)); case CELL_INSERT_INLINE_RIGHT: case CELL_INSERT_INLINE_LEFT: return state .filter((c: Cell) => c.id !== action.item.id) .map((c: Cell) => { if (isHoveringThis(c, action as CellHoverAction)) { return [ { ...createCell(), id: action.ids.cell, rows: [ { ...createRow(), id: action.ids.others[0], cells: [ { ...action.item, inline: action.type === CELL_INSERT_INLINE_RIGHT ? 'right' : 'left', id: action.ids.item, size: 0, }, { ...c, id: action.ids.others[1], inline: null, hasInlineNeighbour: action.ids.item, size: 0, }, ], }, ], }, ] as Cell[]; } return [c]; }) .reduce(flatten, []) .map(inner(cell, action)); case CELL_REMOVE: return state .filter(({ id }: Cell) => id !== action.id) .map(inner(cell, action)); default: return state.map(inner(cell, action)); } })(s, a) ); export const row = (s: Row, a: AnyAction): Row => computeRow( optimizeRow( ((state: Row, action: AnyAction): Row => { const reduce = () => ({ ...state, hover: null, cells: cells(state.cells, action), }); switch (action.type) { case CELL_INSERT_LEFT_OF: if (!isHoveringThis(state, action as CellHoverAction)) { return reduce(); } return { ...state, hover: null, cells: cells( [ { ...action.item, id: action.ids.item, inline: null }, ...state.cells, ], { ...action, hover: null } ), }; case CELL_INSERT_RIGHT_OF: if (!isHoveringThis(state, action as CellHoverAction)) { return reduce(); } return { ...state, hover: null, cells: cells( [ ...state.cells, { ...action.item, id: action.ids.item, inline: null }, ], { ...action, hover: null } ), }; case CELL_DRAG_HOVER: if (isHoveringThis(state, action as CellHoverAction)) { return { ...reduce(), hover: action.position }; } return reduce(); default: return reduce(); } })(s, a) ) ); export const rows = (s: Row[] = [], a: AnyAction): Row[] => optimizeRows( // tslint:disable-next-line:no-any mergeDecorator(a as any)( ((state: Row[], action: AnyAction): Row[] => { const reduce = () => state.map(inner(row, action)); switch (action.type) { case CELL_INSERT_ABOVE: return state .map((r: Row) => isHoveringThis(r, action as CellHoverAction) ? [ { ...createRow(), cells: [ { ...action.item, id: action.ids.item, inline: null }, ], id: action.ids.others[0], }, { ...r, id: action.ids.others[1], }, ] : [r] ) .reduce(flatten, []) .map(inner(row, action)); case CELL_INSERT_BELOW: return state .map((r: Row) => isHoveringThis(r, action as CellHoverAction) ? [ { ...r, id: action.ids.others[0], }, { ...createRow(), cells: [ { ...action.item, id: action.ids.item, inline: null }, ], id: action.ids.others[1], }, ] : [r] ) .reduce(flatten, []) .map(inner(row, action)); default: return reduce(); } })(s, a) ) );