/*
* 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 { Component } from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import pathOr from 'ramda/src/pathOr';
import Mousetrap from 'mousetrap';
import { undo, redo } from '../../actions/undo';
import { removeCell, focusCell, blurAllCells } from '../../actions/cell';
import { isEditMode } from '../../selector/display';
import { focus } from '../../selector/focus';
import {
node,
editable,
editables,
searchNodeEverywhere
} from '../../selector/editable';
import { RootState } from '../../types/state';
import { EditableType, ComponetizedCell } from '../../types/editable';
type Props = {
children: React.ReactChildren;
id: string;
focus: string;
isEditMode: boolean;
editable: EditableType;
undo(id: string): void;
redo(id: string): void;
removeCell(id: string): void;
focusCell(id: string): void;
blurAllCells(): void;
updateCellContent(): void;
updateCellLayout(): void;
node(cell: string, editable: string): Object;
searchNodeEverywhere(
id: string
): { editable: EditableType; node: ComponetizedCell };
};
const hotKeyHandler = (n: Object, key: string) =>
pathOr(
pathOr(() => Promise.resolve(), ['content', 'plugin', key], n),
['layout', 'plugin', key],
n
);
const nextLeaf = (order: Array<{ id: string }> = [], current: string) => {
let last;
return order.find((c: { id: string; isLeaf: boolean }) => {
if (last === current) {
return c.isLeaf;
}
last = c.id;
return false;
});
};
const previousLeaf = (order: Array<{ id: string }>, current: string) =>
nextLeaf([...order].reverse(), current);
const falser = (err: Error) => {
if (err) {
// tslint:disable-next-line:no-console
console.log(err);
}
};
if (Mousetrap && Mousetrap.prototype) {
Mousetrap.prototype.stopCallback = () => false;
}
let wasInitialized = false;
class Decorator extends Component {
props: Props;
handlers = {
undo: () => {
const { id } = this.props;
this.props.undo(id);
},
redo: () => {
const { id } = this.props;
this.props.redo(id);
},
// remove cells
remove: (e: Event) => {
if (!this.props.isEditMode) {
return;
}
const maybeNode = this.props.searchNodeEverywhere(this.props.focus);
if (!maybeNode) {
return;
}
const { node: n } = maybeNode;
hotKeyHandler(n, 'handleRemoveHotKey')(e, n)
.then(() => this.props.removeCell(this.props.focus))
.catch(falser);
},
// focus next cell
focusNext: (e: Event) => {
if (!this.props.isEditMode) {
return;
}
const maybeNode = this.props.searchNodeEverywhere(this.props.focus);
if (!maybeNode) {
return;
}
const { node: n } = maybeNode;
hotKeyHandler(n, 'handleFocusNextHotKey')(e, n)
.then(() => {
const found = nextLeaf(
this.props.editable.cellOrder,
this.props.focus
);
if (found) {
this.props.blurAllCells();
this.props.focusCell(found.id);
}
})
.catch(falser);
},
// focus previous cell
focusPrev: (e: Event) => {
if (!this.props.isEditMode) {
return;
}
const maybeNode = this.props.searchNodeEverywhere(this.props.focus);
if (!maybeNode) {
return;
}
const { node: n } = maybeNode;
hotKeyHandler(n, 'handleFocusPreviousHotKey')(e, n)
.then(() => {
const found = previousLeaf(
this.props.editable.cellOrder,
this.props.focus
);
if (found) {
this.props.blurAllCells();
this.props.focusCell(found.id);
}
})
.catch(falser);
},
};
componentDidMount() {
if (!wasInitialized) {
if (!Mousetrap) {
return;
}
Mousetrap.bind(['ctrl+z', 'command+z'], this.handlers.undo);
Mousetrap.bind(
['ctrl+shift+z', 'ctrl+y', 'command+shift+z', 'command+y'],
this.handlers.redo
);
Mousetrap.bind(['del', 'backspace'], this.handlers.remove);
Mousetrap.bind(['down', 'right'], this.handlers.focusNext);
Mousetrap.bind(['up', 'left'], this.handlers.focusPrev);
wasInitialized = true;
}
}
render() {
const { children } = this.props;
return children;
}
}
const mapStateToProps = createStructuredSelector({
isEditMode,
focus,
// tslint:disable-next-line:no-any
node: (state: any) => (id: string, _editable: string) =>
node(state, { id, editable: _editable }),
searchNodeEverywhere: (state: RootState) => (id: string) =>
searchNodeEverywhere(state, id),
// tslint:disable-next-line:no-any
editable: (state: any, props: any) => (id?: string) =>
editable(state, id ? { id } : props),
editables,
});
const mapDispatchToProps = {
undo,
redo,
removeCell,
focusCell: (id: string) => focusCell(id)(),
blurAllCells,
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Decorator);