forked from enviPath/enviPy
Current Dev State
This commit is contained in:
72
static/js/ketcher2/script/ui/state/action.js
Normal file
72
static/js/ketcher2/script/ui/state/action.js
Normal file
@ -0,0 +1,72 @@
|
||||
/****************************************************************************
|
||||
* Copyright 2017 EPAM Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
***************************************************************************/
|
||||
|
||||
import acts from '../action';
|
||||
import { isEqual, isEmpty, pickBy } from 'lodash/fp';
|
||||
|
||||
function execute(activeTool, { action, editor, server, options }) {
|
||||
if (action.tool) {
|
||||
if (editor.tool(action.tool, action.opts))
|
||||
return action;
|
||||
}
|
||||
else if (typeof action === 'function')
|
||||
action(editor, server, options);
|
||||
else
|
||||
console.info('no action');
|
||||
return activeTool;
|
||||
}
|
||||
|
||||
function selected(actObj, activeTool, { editor, server }) {
|
||||
if (typeof actObj.selected === 'function')
|
||||
return actObj.selected(editor, server);
|
||||
else if (actObj.action && actObj.action.tool)
|
||||
return isEqual(activeTool, actObj.action);
|
||||
return false;
|
||||
}
|
||||
|
||||
function disabled(actObj, { editor, server, options }) {
|
||||
if (typeof actObj.disabled === 'function')
|
||||
return actObj.disabled(editor, server, options);
|
||||
return false;
|
||||
}
|
||||
|
||||
function status(key, activeTool, params) {
|
||||
let actObj = acts[key];
|
||||
return pickBy(x => x, {
|
||||
selected: selected(actObj, activeTool, params),
|
||||
disabled: disabled(actObj, params)
|
||||
});
|
||||
}
|
||||
|
||||
export default function (state=null, { type, action, ...params }) {
|
||||
switch(type) {
|
||||
case 'INIT':
|
||||
action = acts['select-lasso'].action;
|
||||
case 'ACTION':
|
||||
const activeTool = execute(state && state.activeTool, {
|
||||
...params, action
|
||||
});
|
||||
case 'UPDATE':
|
||||
return Object.keys(acts).reduce((res, key) => {
|
||||
const value = status(key, res.activeTool, params);
|
||||
if (!isEmpty(value))
|
||||
res[key] = value;
|
||||
return res;
|
||||
}, { activeTool: activeTool || state.activeTool });
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
106
static/js/ketcher2/script/ui/state/editor.js
Normal file
106
static/js/ketcher2/script/ui/state/editor.js
Normal file
@ -0,0 +1,106 @@
|
||||
/****************************************************************************
|
||||
* Copyright 2017 EPAM Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
***************************************************************************/
|
||||
|
||||
import { debounce } from 'lodash/fp';
|
||||
|
||||
import element from '../../chem/element';
|
||||
import acts from '../action';
|
||||
import { openDialog } from './';
|
||||
import { fromBond, toBond, fromSgroup, toSgroup, fromElement, toElement } from '../structconv';
|
||||
|
||||
export function initEditor(dispatch, getState) {
|
||||
const updateAction = debounce(100, () => dispatch({ type: 'UPDATE' }));
|
||||
const sleep = (time) => new Promise(resolve => setTimeout(resolve, time));
|
||||
|
||||
function resetToSelect(dispatch, getState) {
|
||||
const resetToSelect = getState().options.settings.resetToSelect;
|
||||
const activeTool = getState().actionState.activeTool.tool;
|
||||
if (resetToSelect === true || resetToSelect === activeTool) // example: 'paste'
|
||||
dispatch({ type: 'ACTION', action: acts['select-lasso'].action });
|
||||
else
|
||||
updateAction();
|
||||
}
|
||||
|
||||
return {
|
||||
onInit: editor => {
|
||||
dispatch({ type: 'INIT', editor });
|
||||
},
|
||||
onChange: () => {
|
||||
dispatch(resetToSelect);
|
||||
},
|
||||
onSelectionChange: () => {
|
||||
updateAction();
|
||||
},
|
||||
onElementEdit: selem => {
|
||||
const elem = fromElement(selem);
|
||||
let dlg = null;
|
||||
|
||||
if (element.map[elem.label]) {
|
||||
dlg = openDialog(dispatch, 'atomProps', elem);
|
||||
} else if (Object.keys(elem).length === 1 && 'ap' in elem) {
|
||||
dlg = openDialog(dispatch, 'attachmentPoints', elem.ap)
|
||||
.then((res) => ({ ap: res }));
|
||||
} else if (elem.type === 'list' || elem.type === 'not-list') {
|
||||
dlg = openDialog(dispatch, 'period-table', elem);
|
||||
} else if (elem.type === 'rlabel') {
|
||||
dlg = openDialog(dispatch, 'rgroup', elem);
|
||||
} else {
|
||||
dlg = openDialog(dispatch, 'period-table', elem);
|
||||
}
|
||||
|
||||
return dlg.then(toElement);
|
||||
},
|
||||
onQuickEdit: atom => {
|
||||
return openDialog(dispatch, 'labelEdit', atom)
|
||||
},
|
||||
onBondEdit: bond => {
|
||||
return openDialog(dispatch, 'bondProps', fromBond(bond))
|
||||
.then(toBond);
|
||||
},
|
||||
onRgroupEdit: rgroup => {
|
||||
if (Object.keys(rgroup).length > 1) {
|
||||
const rgids = [];
|
||||
getState().editor.struct().rgroups.each(rgid => rgids.push(rgid));
|
||||
|
||||
if (!rgroup.range) rgroup.range = '>0';
|
||||
|
||||
return openDialog(dispatch, 'rgroupLogic',
|
||||
Object.assign({ rgroupLabels: rgids }, rgroup));
|
||||
}
|
||||
|
||||
return openDialog(dispatch, 'rgroup', rgroup);
|
||||
},
|
||||
onSgroupEdit: sgroup => {
|
||||
return sleep(0) // huck to open dialog after dispatch sgroup tool action
|
||||
.then(() => openDialog(dispatch, 'sgroup', fromSgroup(sgroup)))
|
||||
.then(toSgroup);
|
||||
},
|
||||
onSdataEdit: sgroup => {
|
||||
return sleep(0)
|
||||
.then(() => openDialog(dispatch, sgroup.type === 'DAT' ? 'sdata' : 'sgroup', fromSgroup(sgroup)))
|
||||
.then(toSgroup);
|
||||
},
|
||||
onMessage: msg => {
|
||||
if (msg.error)
|
||||
alert(msg.error);
|
||||
else {
|
||||
let act = Object.keys(msg)[0];
|
||||
console[act](msg[act]);
|
||||
}
|
||||
},
|
||||
onMouseDown: event => {}
|
||||
};
|
||||
}
|
||||
129
static/js/ketcher2/script/ui/state/form.js
Normal file
129
static/js/ketcher2/script/ui/state/form.js
Normal file
@ -0,0 +1,129 @@
|
||||
/****************************************************************************
|
||||
* Copyright 2017 EPAM Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
***************************************************************************/
|
||||
|
||||
import { getDefaultOptions } from '../data/options-schema';
|
||||
import { initSdata, sdataReducer } from './sdata';
|
||||
|
||||
export const formsState = {
|
||||
atomProps: {
|
||||
errors: {},
|
||||
valid: true,
|
||||
result: {
|
||||
label: '',
|
||||
charge: 0,
|
||||
explicitValence: -1,
|
||||
hCount: 0,
|
||||
invRet: 0,
|
||||
isotope: 0,
|
||||
radical: 0,
|
||||
ringBondCount: 0,
|
||||
substitutionCount: 0
|
||||
}
|
||||
} ,
|
||||
attachmentPoints: {
|
||||
errors: {},
|
||||
valid: true,
|
||||
result: {
|
||||
primary: false,
|
||||
secondary: false
|
||||
}
|
||||
},
|
||||
automap: {
|
||||
errors: {},
|
||||
valid: true,
|
||||
result: {
|
||||
mode: "discard"
|
||||
}
|
||||
},
|
||||
bondProps: {
|
||||
errors: {},
|
||||
valid: true,
|
||||
result: {
|
||||
type: 'single',
|
||||
topology: 0,
|
||||
center: 0
|
||||
}
|
||||
},
|
||||
check: {
|
||||
errors: {},
|
||||
moleculeErrors: {},
|
||||
result: {
|
||||
checkOptions: ['valence', 'radicals', 'pseudoatoms', 'stereo', 'query', 'overlapping_atoms',
|
||||
'overlapping_bonds', 'rgroups', 'chiral', '3d']
|
||||
}
|
||||
},
|
||||
labelEdit: {
|
||||
errors: {},
|
||||
valid: true,
|
||||
result: {
|
||||
label: '',
|
||||
}
|
||||
},
|
||||
rgroupLogic: {
|
||||
errors: {},
|
||||
valid: true,
|
||||
result: {
|
||||
ifthen: 0,
|
||||
range: '>0',
|
||||
resth: false
|
||||
}
|
||||
},
|
||||
settings: {
|
||||
errors: {},
|
||||
valid: true,
|
||||
result: getDefaultOptions()
|
||||
},
|
||||
sgroup: {
|
||||
errors: {},
|
||||
valid: true,
|
||||
result: {
|
||||
type: 'GEN'
|
||||
}
|
||||
},
|
||||
sdata: initSdata()
|
||||
};
|
||||
|
||||
export function updateFormState(data) {
|
||||
return {
|
||||
type: 'UPDATE_FORM',
|
||||
data: data
|
||||
};
|
||||
}
|
||||
|
||||
export function checkErrors(errors) {
|
||||
return {
|
||||
type: 'UPDATE_FORM',
|
||||
data: { moleculeErrors: errors }
|
||||
};
|
||||
}
|
||||
|
||||
export function setDefaultSettings() {
|
||||
return {
|
||||
type: 'UPDATE_FORM',
|
||||
data: {
|
||||
result: getDefaultOptions(),
|
||||
valid: true,
|
||||
errors: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function formReducer(state, action, formName) {
|
||||
if (formName === 'sdata')
|
||||
return sdataReducer(state, action);
|
||||
|
||||
return Object.assign({}, state, action.data);
|
||||
}
|
||||
156
static/js/ketcher2/script/ui/state/hotkeys.js
Normal file
156
static/js/ketcher2/script/ui/state/hotkeys.js
Normal file
@ -0,0 +1,156 @@
|
||||
/****************************************************************************
|
||||
* Copyright 2017 EPAM Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
***************************************************************************/
|
||||
|
||||
import { isEqual, debounce } from 'lodash/fp';
|
||||
|
||||
import molfile from '../../chem/molfile';
|
||||
import keyNorm from '../keynorm';
|
||||
import actions from '../action';
|
||||
|
||||
import * as clipArea from '../component/cliparea';
|
||||
import * as structFormat from '../structformat';
|
||||
import { onAction, openDialog, load } from './';
|
||||
|
||||
export function initKeydownListener(element) {
|
||||
return function (dispatch, getState) {
|
||||
const hotKeys = initHotKeys();
|
||||
element.addEventListener('keydown', (event) => keyHandle(dispatch, getState, hotKeys, event));
|
||||
}
|
||||
}
|
||||
|
||||
/* HotKeys */
|
||||
function keyHandle(dispatch, getState, hotKeys, event) {
|
||||
const state = getState();
|
||||
if (state.modal) return;
|
||||
|
||||
const editor = state.editor;
|
||||
const actionState = state.actionState;
|
||||
const actionTool = actionState.activeTool;
|
||||
|
||||
const key = keyNorm(event);
|
||||
const atomsSelected = editor.selection() && editor.selection().atoms;
|
||||
|
||||
let group = null;
|
||||
|
||||
if (key && key.length === 1 && atomsSelected && key.match(/\w/)) {
|
||||
console.assert(atomsSelected.length > 0);
|
||||
openDialog(dispatch, 'labelEdit', { letter: key }).then(res => {
|
||||
dispatch(onAction({ tool: 'atom', opts: res }));
|
||||
});
|
||||
event.preventDefault();
|
||||
} else if (group = keyNorm.lookup(hotKeys, event)) {
|
||||
let index = checkGroupOnTool(group, actionTool); // index currentTool in group || -1
|
||||
index = (index + 1) % group.length;
|
||||
|
||||
let actName = group[index];
|
||||
if (actionState[actName] && actionState[actName].disabled === true)
|
||||
return event.preventDefault();
|
||||
|
||||
if (clipArea.actions.indexOf(actName) === -1) {
|
||||
let newAction = actions[actName].action;
|
||||
dispatch(onAction(newAction));
|
||||
event.preventDefault();
|
||||
} else if (window.clipboardData) // IE support
|
||||
clipArea.exec(event);
|
||||
}
|
||||
}
|
||||
|
||||
function setHotKey(key, actName, hotKeys) {
|
||||
if (Array.isArray(hotKeys[key]))
|
||||
hotKeys[key].push(actName);
|
||||
else
|
||||
hotKeys[key] = [actName];
|
||||
}
|
||||
|
||||
function initHotKeys() {
|
||||
const hotKeys = {};
|
||||
let act;
|
||||
|
||||
for (let actName in actions) {
|
||||
act = actions[actName];
|
||||
if (!act.shortcut) continue;
|
||||
|
||||
if (Array.isArray(act.shortcut))
|
||||
act.shortcut.forEach(key => setHotKey(key, actName, hotKeys));
|
||||
else
|
||||
setHotKey(act.shortcut, actName, hotKeys);
|
||||
}
|
||||
|
||||
return keyNorm(hotKeys);
|
||||
}
|
||||
|
||||
function checkGroupOnTool(group, actionTool) {
|
||||
let index = group.indexOf(actionTool.tool);
|
||||
|
||||
group.forEach((actName, i) => {
|
||||
if (isEqual(actions[actName].action, actionTool))
|
||||
index = i;
|
||||
});
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/* ClipArea */
|
||||
export function initClipboard(dispatch, getState) {
|
||||
const formats = Object.keys(structFormat.map).map(function (fmt) {
|
||||
return structFormat.map[fmt].mime;
|
||||
});
|
||||
|
||||
const debAction = debounce(0, (action) => dispatch( onAction(action) ));
|
||||
const loadStruct = debounce(0, (structStr, opts) => dispatch( load(structStr, opts) ));
|
||||
|
||||
return {
|
||||
formats: formats,
|
||||
focused: function () {
|
||||
return !getState().modal;
|
||||
},
|
||||
onCut: function () {
|
||||
let data = clipData(getState().editor);
|
||||
debAction({ tool: 'eraser', opts: 1 });
|
||||
return data;
|
||||
},
|
||||
onCopy: function () {
|
||||
let editor = getState().editor;
|
||||
let data = clipData(editor);
|
||||
editor.selection(null);
|
||||
return data;
|
||||
},
|
||||
onPaste: function (data) {
|
||||
const structStr = data['chemical/x-mdl-molfile'] ||
|
||||
data['chemical/x-mdl-rxnfile'] ||
|
||||
data['text/plain'];
|
||||
|
||||
if (structStr)
|
||||
loadStruct(structStr, { fragment: true });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function clipData(editor) {
|
||||
const res = {};
|
||||
const struct = editor.structSelected();
|
||||
|
||||
if (struct.isBlank())
|
||||
return null;
|
||||
|
||||
const type = struct.isReaction ?
|
||||
'chemical/x-mdl-molfile' : 'chemical/x-mdl-rxnfile';
|
||||
|
||||
res['text/plain'] = res[type] = molfile.stringify(struct);
|
||||
// res['chemical/x-daylight-smiles'] =
|
||||
// smiles.stringify(struct);
|
||||
return res;
|
||||
}
|
||||
167
static/js/ketcher2/script/ui/state/index.js
Normal file
167
static/js/ketcher2/script/ui/state/index.js
Normal file
@ -0,0 +1,167 @@
|
||||
/****************************************************************************
|
||||
* Copyright 2017 EPAM Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
***************************************************************************/
|
||||
|
||||
import { pick } from 'lodash/fp';
|
||||
|
||||
import { createStore, combineReducers, applyMiddleware } from 'redux';
|
||||
import thunk from 'redux-thunk'
|
||||
import { logger } from 'redux-logger';
|
||||
|
||||
import * as structFormat from '../structformat';
|
||||
import { formsState, formReducer } from './form';
|
||||
import { optionsState, optionsReducer } from './options';
|
||||
import { initTmplState, templatesReducer } from './templates';
|
||||
|
||||
import action from './action';
|
||||
import toolbar from './toolbar';
|
||||
|
||||
function modal(state = null, action) {
|
||||
const { type, data } = action;
|
||||
|
||||
if (type === 'UPDATE_FORM') {
|
||||
const formState = formReducer(state.form, action, state.name);
|
||||
return { ...state, form: formState }
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'MODAL_CLOSE':
|
||||
return null;
|
||||
case 'MODAL_OPEN':
|
||||
return {
|
||||
name: data.name,
|
||||
form: formsState[data.name] || null,
|
||||
prop: data.prop || null
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
const shared = combineReducers({
|
||||
actionState: action,
|
||||
toolbar,
|
||||
modal,
|
||||
server: (store=null) => store,
|
||||
editor: (store=null) => store,
|
||||
options: optionsReducer,
|
||||
templates: templatesReducer
|
||||
});
|
||||
|
||||
export function onAction(action) {
|
||||
if (action && action.dialog)
|
||||
return {
|
||||
type: 'MODAL_OPEN',
|
||||
data: { name: action.dialog }
|
||||
};
|
||||
|
||||
if (action && action.thunk)
|
||||
return action.thunk;
|
||||
|
||||
return {
|
||||
type: 'ACTION',
|
||||
action
|
||||
};
|
||||
}
|
||||
|
||||
export function openDialog(dispatch, dialogName, props) {
|
||||
return new Promise((resolve, reject) => {
|
||||
dispatch({
|
||||
type: 'MODAL_OPEN',
|
||||
data: {
|
||||
name: dialogName,
|
||||
prop: {
|
||||
...props,
|
||||
onResult: resolve,
|
||||
onCancel: reject
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function load(structStr, options) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const editor = state.editor;
|
||||
const server = state.server;
|
||||
|
||||
options = options || {};
|
||||
// TODO: check if structStr is parsed already
|
||||
//utils.loading('show');
|
||||
const parsed = structFormat.fromString(structStr,
|
||||
options, server);
|
||||
|
||||
parsed.catch(function (err) {
|
||||
//utils.loading('hide');
|
||||
alert("Can't parse molecule!");
|
||||
});
|
||||
|
||||
return parsed.then(function (struct) {
|
||||
//utils.loading('hide');
|
||||
console.assert(struct, 'No molecule to update');
|
||||
if (options.rescale)
|
||||
struct.rescale(); // TODO: move out parsing?
|
||||
|
||||
if (options.fragment && !struct.isBlank())
|
||||
dispatch(onAction({ tool: 'paste', opts: struct }));
|
||||
else
|
||||
editor.struct(struct);
|
||||
|
||||
return struct;
|
||||
}, function (err) {
|
||||
alert(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function root(state, action) {
|
||||
switch (action.type) {
|
||||
case 'INIT':
|
||||
global._ui_editor = action.editor;
|
||||
case 'UPDATE':
|
||||
let {type, ...data} = action;
|
||||
if (data)
|
||||
state = { ...state, ...data };
|
||||
}
|
||||
|
||||
const sh = shared(state, {
|
||||
...action,
|
||||
...pick(['editor', 'server', 'options'], state)
|
||||
});
|
||||
|
||||
return (sh === state.shared) ? state : {
|
||||
...state, ...sh
|
||||
};
|
||||
}
|
||||
|
||||
export default function(options, server) {
|
||||
// TODO: redux localStorage here
|
||||
const initState = {
|
||||
actionState: null,
|
||||
options: Object.assign(optionsState, { app: options }),
|
||||
server: server || Promise.reject("Standalone mode!"),
|
||||
editor: null,
|
||||
modal: null,
|
||||
templates: initTmplState
|
||||
};
|
||||
|
||||
const middleware = [ thunk ];
|
||||
|
||||
if (process.env.NODE_ENV !== 'production')
|
||||
middleware.push(logger);
|
||||
|
||||
return createStore(root, initState, applyMiddleware(...middleware));
|
||||
};
|
||||
33
static/js/ketcher2/script/ui/state/miew.js
Normal file
33
static/js/ketcher2/script/ui/state/miew.js
Normal file
@ -0,0 +1,33 @@
|
||||
/****************************************************************************
|
||||
* Copyright 2017 EPAM Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
***************************************************************************/
|
||||
|
||||
import { openDialog, load } from './';
|
||||
import * as structFormat from '../structformat';
|
||||
|
||||
export function miewAction(dispatch, getState) {
|
||||
const editor = getState().editor;
|
||||
const server = getState().server;
|
||||
let convert = structFormat.toString(editor.struct(),
|
||||
'cml', server);
|
||||
convert.then(function (cml) {
|
||||
openDialog(dispatch, 'miew', {
|
||||
structStr: cml
|
||||
}).then(function (res) {
|
||||
if (res.structStr)
|
||||
dispatch(load(res.structStr));
|
||||
});
|
||||
});
|
||||
}
|
||||
114
static/js/ketcher2/script/ui/state/options.js
Normal file
114
static/js/ketcher2/script/ui/state/options.js
Normal file
@ -0,0 +1,114 @@
|
||||
/****************************************************************************
|
||||
* Copyright 2017 EPAM Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
***************************************************************************/
|
||||
|
||||
import { pick } from 'lodash/fp';
|
||||
import { SERVER_OPTIONS, getDefaultOptions, validation } from '../data/options-schema';
|
||||
import { storage } from '../utils';
|
||||
|
||||
export const optionsState = {
|
||||
app: {
|
||||
server: false,
|
||||
templates: false
|
||||
},
|
||||
analyse: {
|
||||
values: null,
|
||||
roundWeight: 3,
|
||||
roundMass: 3
|
||||
},
|
||||
recognize: {
|
||||
file: null,
|
||||
structStr: null,
|
||||
fragment: false
|
||||
},
|
||||
settings: Object.assign(getDefaultOptions(), validation(storage.getItem("ketcher-opts"))),
|
||||
getServerSettings: function() {
|
||||
return pick(SERVER_OPTIONS, this.settings);
|
||||
}
|
||||
};
|
||||
|
||||
export function appUpdate(data) {
|
||||
return dispatch => {
|
||||
dispatch({ type: 'APP_OPTIONS', data });
|
||||
dispatch({ type: 'UPDATE' })
|
||||
}
|
||||
}
|
||||
|
||||
/* SETTINGS */
|
||||
export function saveSettings(newSettings) {
|
||||
storage.setItem("ketcher-opts", newSettings);
|
||||
return {
|
||||
type: 'SAVE_SETTINGS',
|
||||
data: newSettings
|
||||
};
|
||||
}
|
||||
|
||||
/* ANALYZE */
|
||||
export function changeRound(roundName, value) {
|
||||
return {
|
||||
type: 'CHANGE_ANALYSE',
|
||||
data: { [roundName]: value }
|
||||
};
|
||||
}
|
||||
|
||||
/* RECOGNIZE */
|
||||
const recognizeActions = [
|
||||
'SET_RECOGNIZE_STRUCT',
|
||||
'CHANGE_RECOGNIZE_FILE',
|
||||
'IS_FRAGMENT_RECOGNIZE'
|
||||
];
|
||||
|
||||
export function setStruct(str) {
|
||||
return {
|
||||
type: 'SET_RECOGNIZE_STRUCT',
|
||||
data: { structStr: str }
|
||||
};
|
||||
}
|
||||
|
||||
export function changeImage(file) {
|
||||
return {
|
||||
type: 'CHANGE_RECOGNIZE_FILE',
|
||||
data: {
|
||||
file: file,
|
||||
structStr: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldFragment(isFrag) {
|
||||
return {
|
||||
type: 'IS_FRAGMENT_RECOGNIZE',
|
||||
data: { fragment: isFrag }
|
||||
};
|
||||
}
|
||||
|
||||
export function optionsReducer(state = {}, action) {
|
||||
let { type, data } = action;
|
||||
|
||||
if (type === 'APP_OPTIONS')
|
||||
return {...state, app: { ...state.app, ...data }};
|
||||
|
||||
if (type === 'SAVE_SETTINGS')
|
||||
return {...state, settings: data};
|
||||
|
||||
if (type === 'CHANGE_ANALYSE')
|
||||
return {...state, analyse: { ...state.analyse, ...data }};
|
||||
|
||||
if (recognizeActions.includes(type)) {
|
||||
return {...state, recognize: { ...state.recognize, ...data }}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
114
static/js/ketcher2/script/ui/state/sdata.js
Normal file
114
static/js/ketcher2/script/ui/state/sdata.js
Normal file
@ -0,0 +1,114 @@
|
||||
/****************************************************************************
|
||||
* Copyright 2017 EPAM Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
***************************************************************************/
|
||||
|
||||
import { sdataSchema, getSdataDefault } from '../data/sdata-schema'
|
||||
|
||||
export const initSdata = () => {
|
||||
const context = getSdataDefault();
|
||||
const fieldName = getSdataDefault(context);
|
||||
const fieldValue = getSdataDefault(context, fieldName);
|
||||
const radiobuttons = 'Absolute';
|
||||
|
||||
return {
|
||||
errors: {},
|
||||
valid: true,
|
||||
result: {
|
||||
context,
|
||||
fieldName,
|
||||
fieldValue,
|
||||
radiobuttons,
|
||||
type: 'DAT'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function sdataReducer(state, action) {
|
||||
if (action.data.result.init)
|
||||
return correctErrors({
|
||||
...state,
|
||||
result: Object.assign({}, state.result, action.data.result)
|
||||
}, action.data);
|
||||
|
||||
const actionContext = action.data.result.context;
|
||||
const actionFieldName = action.data.result.fieldName;
|
||||
|
||||
let newstate = null;
|
||||
|
||||
if (actionContext !== state.result.context)
|
||||
newstate = onContextChange(state, action.data.result);
|
||||
else if (actionFieldName !== state.result.fieldName)
|
||||
newstate = onFieldNameChange(state, action.data.result);
|
||||
|
||||
newstate = newstate || {
|
||||
...state,
|
||||
result: Object.assign({}, state.result, action.data.result)
|
||||
};
|
||||
|
||||
return correctErrors(newstate, action.data);
|
||||
}
|
||||
|
||||
const correctErrors = (state, payload) => {
|
||||
const { valid, errors } = payload;
|
||||
const { fieldName, fieldValue } = state.result;
|
||||
|
||||
return {
|
||||
result: state.result,
|
||||
valid: valid && !!fieldName && !!fieldValue,
|
||||
errors: errors,
|
||||
};
|
||||
};
|
||||
|
||||
const onContextChange = (state, payload) => {
|
||||
const { context, fieldValue } = payload;
|
||||
|
||||
const fieldName = getSdataDefault(context);
|
||||
|
||||
let fValue = fieldValue;
|
||||
if (fValue === state.result.fieldValue)
|
||||
fValue = getSdataDefault(context, fieldName);
|
||||
|
||||
return {
|
||||
result: {
|
||||
...payload,
|
||||
context,
|
||||
fieldName,
|
||||
fieldValue: fValue
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const onFieldNameChange = (state, payload) => {
|
||||
let { fieldName } = payload;
|
||||
|
||||
const context = state.result.context;
|
||||
|
||||
let fieldValue = payload.fieldValue;
|
||||
|
||||
if (sdataSchema[context][fieldName]) {
|
||||
fieldValue = getSdataDefault(context, fieldName);
|
||||
}
|
||||
|
||||
if (fieldValue === state.result.fieldValue && sdataSchema[context][state.result.fieldName])
|
||||
fieldValue = '';
|
||||
|
||||
return {
|
||||
result: {
|
||||
...payload,
|
||||
fieldName,
|
||||
fieldValue,
|
||||
}
|
||||
};
|
||||
};
|
||||
155
static/js/ketcher2/script/ui/state/server.js
Normal file
155
static/js/ketcher2/script/ui/state/server.js
Normal file
@ -0,0 +1,155 @@
|
||||
/****************************************************************************
|
||||
* Copyright 2017 EPAM Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
***************************************************************************/
|
||||
|
||||
import { pick, omit } from 'lodash/fp';
|
||||
|
||||
import molfile from '../../chem/molfile';
|
||||
|
||||
import { setStruct, appUpdate } from './options';
|
||||
import { checkErrors } from './form';
|
||||
import { load } from './';
|
||||
|
||||
export function checkServer() {
|
||||
return (dispatch, getState) => {
|
||||
const server = getState().server;
|
||||
|
||||
server.then(
|
||||
(res) => dispatch(appUpdate({
|
||||
indigoVersion: res.indigoVersion,
|
||||
server: true
|
||||
})),
|
||||
(err) => console.info(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function recognize(file) {
|
||||
return (dispatch, getState) => {
|
||||
const recognize = getState().server.recognize;
|
||||
|
||||
let process = recognize(file).then(res => {
|
||||
dispatch(setStruct(res.struct));
|
||||
}, err => {
|
||||
dispatch(setStruct(null));
|
||||
setTimeout(() => alert("Error! The picture isn't recognized."), 200); // TODO: remove me...
|
||||
});
|
||||
dispatch(setStruct(process));
|
||||
};
|
||||
}
|
||||
|
||||
export function check(optsTypes) {
|
||||
return (dispatch, getState) => {
|
||||
const { editor, server } = getState();
|
||||
const options = getState().options.getServerSettings();
|
||||
options.data = { 'types': optsTypes };
|
||||
|
||||
serverCall(editor, server, 'check', options)
|
||||
.then(res => dispatch(checkErrors(res)))
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
|
||||
export function automap(res) {
|
||||
return serverTransform('automap', res);
|
||||
}
|
||||
|
||||
export function analyse() {
|
||||
return (dispatch, getState) => {
|
||||
const { editor, server } = getState();
|
||||
const options = getState().options.getServerSettings();
|
||||
options.data = {
|
||||
properties: ['molecular-weight', 'most-abundant-mass',
|
||||
'monoisotopic-mass', 'gross', 'mass-composition']
|
||||
};
|
||||
|
||||
serverCall(editor, server, 'calculate', options).then(function (values) {
|
||||
dispatch({
|
||||
type: 'CHANGE_ANALYSE',
|
||||
data: { values }
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function serverTransform(method, data, struct) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
let opts = state.options.getServerSettings();
|
||||
opts.data = data;
|
||||
|
||||
serverCall(state.editor, state.server, method, opts, struct).then(function (res) {
|
||||
dispatch( load(res.struct, { rescale: method === 'layout' }) );
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function serverCall(editor, server, method, options, struct) {
|
||||
const selection = editor.selection();
|
||||
let selectedAtoms = [];
|
||||
|
||||
if (selection)
|
||||
selectedAtoms = selection.atoms ? selection.atoms : editor.explicitSelected().atoms;
|
||||
|
||||
if (!struct) {
|
||||
const aidMap = {};
|
||||
struct = editor.struct().clone(null, null, false, aidMap);
|
||||
const reindexMap = getReindexMap(struct.getComponents());
|
||||
|
||||
selectedAtoms = selectedAtoms.map(function (aid) {
|
||||
return reindexMap[aidMap[aid]];
|
||||
});
|
||||
}
|
||||
|
||||
let request = server.then(function () {
|
||||
return server[method](Object.assign({
|
||||
struct: molfile.stringify(struct, { ignoreErrors: true })
|
||||
}, selectedAtoms && selectedAtoms.length > 0 ? {
|
||||
selected: selectedAtoms
|
||||
} : null, options.data), omit('data', options));
|
||||
});
|
||||
//utils.loading('show');
|
||||
request.catch(function (err) {
|
||||
alert(err);
|
||||
}).then(function (er) {
|
||||
//utils.loading('hide');
|
||||
});
|
||||
return request;
|
||||
}
|
||||
|
||||
function getReindexMap(components) {
|
||||
return flatten(components.reactants)
|
||||
.concat(flatten(components.products))
|
||||
.reduce(function (acc, item, index) {
|
||||
acc[item] = index;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Flats passed object
|
||||
* Ex: [ [1, 2], [3, [4, 5] ] ] -> [1, 2, 3, 4, 5]
|
||||
* { a: 1, b: { c: 2, d: 3 } } -> [1, 2, 3]
|
||||
* @param source { object }
|
||||
*/
|
||||
function flatten(source) {
|
||||
if (typeof source !== 'object')
|
||||
return source;
|
||||
|
||||
return Object.keys(source).reduce(function (acc, key) {
|
||||
const item = source[key];
|
||||
return acc.concat(flatten(item));
|
||||
}, []);
|
||||
}
|
||||
250
static/js/ketcher2/script/ui/state/templates.js
Normal file
250
static/js/ketcher2/script/ui/state/templates.js
Normal file
@ -0,0 +1,250 @@
|
||||
/****************************************************************************
|
||||
* Copyright 2017 EPAM Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
***************************************************************************/
|
||||
|
||||
import { omit } from 'lodash/fp';
|
||||
import sdf from '../../chem/sdf';
|
||||
import molfile from '../../chem/molfile';
|
||||
import { appUpdate } from './options';
|
||||
import { openDialog } from './';
|
||||
import { storage } from '../utils';
|
||||
|
||||
/* TEMPLATES */
|
||||
export function selectTmpl(tmpl) {
|
||||
return {
|
||||
type: 'TMPL_SELECT',
|
||||
data: { selected: tmpl }
|
||||
}
|
||||
}
|
||||
|
||||
export function changeGroup(group) {
|
||||
return {
|
||||
type: 'TMPL_CHANGE_GROUP',
|
||||
data: { group: group, selected: null }
|
||||
}
|
||||
}
|
||||
|
||||
export function changeFilter(filter) {
|
||||
return {
|
||||
type: 'TMPL_CHANGE_FILTER',
|
||||
data: { filter: filter.trim(), selected: null } // TODO: change this
|
||||
}
|
||||
}
|
||||
|
||||
/* TEMPLATE-ATTACH-EDIT */
|
||||
export function initAttach(name, attach) {
|
||||
return {
|
||||
type: 'INIT_ATTACH',
|
||||
data: {
|
||||
name,
|
||||
atomid: attach.atomid,
|
||||
bondid: attach.bondid
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function setAttachPoints(attach) {
|
||||
return {
|
||||
type: 'SET_ATTACH_POINTS',
|
||||
data: {
|
||||
atomid: attach.atomid,
|
||||
bondid: attach.bondid
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function setTmplName(name) {
|
||||
return {
|
||||
type: 'SET_TMPL_NAME',
|
||||
data: { name }
|
||||
};
|
||||
}
|
||||
|
||||
export function editTmpl(tmpl) {
|
||||
return (dispatch, getState) => {
|
||||
openDialog(dispatch, 'attach', { tmpl }).then(
|
||||
({ name, attach }) => {
|
||||
tmpl.struct.name = name;
|
||||
tmpl.props = Object.assign({}, tmpl.props, attach);
|
||||
|
||||
if (tmpl.props.group === 'User Templates')
|
||||
updateLocalStore(getState().templates.lib);
|
||||
openDialog(dispatch, 'templates');
|
||||
}, () => {
|
||||
openDialog(dispatch, 'templates');
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* SAVE */
|
||||
export function saveUserTmpl(structStr) {
|
||||
const tmpl = { struct: molfile.parse(structStr), props: {} };
|
||||
|
||||
return (dispatch, getState) => {
|
||||
openDialog(dispatch, 'attach', { tmpl }).then(
|
||||
({ name, attach }) => {
|
||||
tmpl.struct.name = name;
|
||||
tmpl.props = { ...attach, group: 'User Templates' };
|
||||
|
||||
let lib = getState().templates.lib.concat(tmpl);
|
||||
dispatch(initLib(lib));
|
||||
updateLocalStore(lib);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function updateLocalStore(lib) {
|
||||
const userLib = lib
|
||||
.filter(item => item.props.group === 'User Templates')
|
||||
.map(item => {
|
||||
return {
|
||||
struct: molfile.stringify(item.struct),
|
||||
props: Object.assign({}, omit(['group'], item.props))
|
||||
};
|
||||
});
|
||||
|
||||
storage.setItem("ketcher-tmpls", userLib);
|
||||
}
|
||||
|
||||
/* REDUCER */
|
||||
export const initTmplState = {
|
||||
lib: [],
|
||||
selected: null,
|
||||
filter: '',
|
||||
group: null,
|
||||
attach: {}
|
||||
};
|
||||
|
||||
const tmplActions = [
|
||||
'TMPL_INIT',
|
||||
'TMPL_SELECT',
|
||||
'TMPL_CHANGE_GROUP',
|
||||
'TMPL_CHANGE_FILTER'
|
||||
];
|
||||
|
||||
const attachActions = [
|
||||
'INIT_ATTACH',
|
||||
'SET_ATTACH_POINTS',
|
||||
'SET_TMPL_NAME'
|
||||
];
|
||||
|
||||
export function templatesReducer(state = initTmplState, action) {
|
||||
if (tmplActions.includes(action.type)) {
|
||||
return Object.assign({}, state, action.data);
|
||||
}
|
||||
|
||||
if (attachActions.includes(action.type)) {
|
||||
const attach = Object.assign({}, state.attach, action.data);
|
||||
return { ...state, attach };
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/* INIT TEMPLATES LIBRARY */
|
||||
function initLib(lib) {
|
||||
return {
|
||||
type: 'TMPL_INIT',
|
||||
data: { lib: lib }
|
||||
}
|
||||
}
|
||||
|
||||
export function initTmplLib(dispatch, baseUrl, cacheEl) {
|
||||
prefetchStatic(baseUrl + 'library.sdf').then(text => {
|
||||
const tmpls = sdf.parse(text);
|
||||
const prefetch = prefetchRender(tmpls, baseUrl, cacheEl);
|
||||
|
||||
return prefetch.then(cachedFiles => (
|
||||
tmpls.map(tmpl => {
|
||||
const pr = prefetchSplit(tmpl);
|
||||
if (pr.file)
|
||||
tmpl.props.prerender = cachedFiles.indexOf(pr.file) !== -1 ? `#${pr.id}` : '';
|
||||
|
||||
return tmpl;
|
||||
})
|
||||
));
|
||||
}).then(res => {
|
||||
const lib = res.concat(userTmpls());
|
||||
dispatch(initLib(lib));
|
||||
dispatch(appUpdate({ templates: true }));
|
||||
});
|
||||
}
|
||||
|
||||
function userTmpls() {
|
||||
const userLib = storage.getItem("ketcher-tmpls");
|
||||
if (!Array.isArray(userLib) || userLib.length === 0) return [];
|
||||
|
||||
return userLib
|
||||
.map(tmpl => {
|
||||
try {
|
||||
if (tmpl.props === '') tmpl.props = {};
|
||||
tmpl.props.group = 'User Templates';
|
||||
|
||||
return {
|
||||
struct: molfile.parse(tmpl.struct),
|
||||
props: tmpl.props
|
||||
};
|
||||
} catch (ex) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(tmpl => tmpl !== null);
|
||||
}
|
||||
|
||||
function prefetchStatic(url) {
|
||||
return fetch(url, { credentials: 'same-origin' }).then(function (resp) {
|
||||
if (resp.ok)
|
||||
return resp.text();
|
||||
throw "Could not fetch " + url;
|
||||
});
|
||||
}
|
||||
|
||||
function prefetchSplit(tmpl) {
|
||||
const pr = tmpl.props.prerender;
|
||||
const res = pr && pr.split('#', 2);
|
||||
|
||||
return {
|
||||
file: pr && res[0],
|
||||
id: pr && res[1]
|
||||
};
|
||||
}
|
||||
|
||||
function prefetchRender(tmpls, baseUrl, cacheEl) {
|
||||
const files = tmpls.reduce((res, tmpl) => {
|
||||
const file = prefetchSplit(tmpl).file;
|
||||
|
||||
if (file && res.indexOf(file) === -1)
|
||||
res.push(file);
|
||||
|
||||
return res;
|
||||
}, []);
|
||||
|
||||
const fetch = Promise.all(files.map(fn => (
|
||||
prefetchStatic(baseUrl + fn).catch(() => null)
|
||||
)));
|
||||
|
||||
return fetch.then(svgs => {
|
||||
svgs.forEach(svgContent => {
|
||||
if (svgContent)
|
||||
cacheEl.innerHTML += svgContent;
|
||||
});
|
||||
|
||||
return files.filter((file, i) => (
|
||||
!!svgs[i]
|
||||
));
|
||||
});
|
||||
}
|
||||
110
static/js/ketcher2/script/ui/state/toolbar.js
Normal file
110
static/js/ketcher2/script/ui/state/toolbar.js
Normal file
@ -0,0 +1,110 @@
|
||||
/****************************************************************************
|
||||
* Copyright 2017 EPAM Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
***************************************************************************/
|
||||
|
||||
import { capitalize, debounce, isEqual } from 'lodash/fp';
|
||||
import { basic as basicAtoms } from '../action/atoms';
|
||||
import tools from '../action/tools';
|
||||
|
||||
const initial = {
|
||||
freqAtoms: [],
|
||||
currentAtom: 0,
|
||||
opened: null,
|
||||
visibleTools: {}
|
||||
};
|
||||
const MAX_ATOMS = 7;
|
||||
|
||||
export function initResize() {
|
||||
return function (dispatch, getState) {
|
||||
const onResize = debounce(100, () => {
|
||||
getState().editor.render.update();
|
||||
dispatch({ type: 'CLEAR_VISIBLE' })
|
||||
});
|
||||
|
||||
addEventListener('resize', onResize);
|
||||
}
|
||||
}
|
||||
|
||||
export default function (state=initial, action) {
|
||||
let { type, data } = action;
|
||||
|
||||
switch (type) {
|
||||
case 'ACTION':
|
||||
let visibleTool = toolInMenu(action.action);
|
||||
return visibleTool
|
||||
? { ...state, opened: null, visibleTools: { ...state.visibleTools, ...visibleTool } }
|
||||
: state;
|
||||
case 'ADD_ATOMS':
|
||||
const newState = addFreqAtom(data, state.freqAtoms, state.currentAtom);
|
||||
return { ...state, ...newState };
|
||||
case 'CLEAR_VISIBLE':
|
||||
return { ...state, opened: null, visibleTools: {} };
|
||||
case 'OPENED':
|
||||
return { ...state, opened: data };
|
||||
case 'UPDATE':
|
||||
return { ...state, opened: null };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function addFreqAtom(label, freqAtoms, index) {
|
||||
label = capitalize(label);
|
||||
if (basicAtoms.indexOf(label) > -1 || freqAtoms.indexOf(label) !== -1) return { freqAtoms };
|
||||
|
||||
freqAtoms[index] = label;
|
||||
index = (index + 1) % MAX_ATOMS;
|
||||
|
||||
return { freqAtoms, currentAtom: index };
|
||||
}
|
||||
|
||||
export function addAtoms(atomLabel) {
|
||||
return {
|
||||
type: 'ADD_ATOMS',
|
||||
data: atomLabel
|
||||
};
|
||||
}
|
||||
|
||||
function getToolFromAction(action) {
|
||||
let tool = null;
|
||||
|
||||
for (let toolName in tools) {
|
||||
if (tools.hasOwnProperty(toolName) && isEqual(action, tools[toolName].action))
|
||||
tool = toolName;
|
||||
}
|
||||
|
||||
return tool;
|
||||
}
|
||||
|
||||
function toolInMenu(action) {
|
||||
let tool = getToolFromAction(action);
|
||||
|
||||
let sel = document.getElementById(tool);
|
||||
let dropdown = sel && hiddenAncestor(sel);
|
||||
|
||||
return dropdown ? { [dropdown.id]: sel.id } : null;
|
||||
}
|
||||
|
||||
export function hiddenAncestor(el, base) {
|
||||
base = base || document.body;
|
||||
let findEl = el;
|
||||
|
||||
while (window.getComputedStyle(findEl).overflow !== 'hidden' && !findEl.classList.contains('opened')) {
|
||||
if (findEl === base) return null;
|
||||
findEl = findEl.parentNode;
|
||||
}
|
||||
|
||||
return findEl;
|
||||
}
|
||||
Reference in New Issue
Block a user