forked from enviPath/enviPy
Current Dev State
This commit is contained in:
52
static/js/ketcher2/script/editor/tool/apoint.js
Normal file
52
static/js/ketcher2/script/editor/tool/apoint.js
Normal file
@ -0,0 +1,52 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Action = require('../action');
|
||||
|
||||
function APointTool(editor) {
|
||||
if (!(this instanceof APointTool))
|
||||
return new APointTool(editor);
|
||||
|
||||
this.editor = editor;
|
||||
this.editor.selection(null);
|
||||
}
|
||||
|
||||
APointTool.prototype.mousemove = function (event) {
|
||||
this.editor.hover(this.editor.findItem(event, ['atoms']));
|
||||
};
|
||||
|
||||
APointTool.prototype.mouseup = function (event) {
|
||||
var editor = this.editor;
|
||||
var struct = editor.render.ctab.molecule;
|
||||
var ci = editor.findItem(event, ['atoms']);
|
||||
|
||||
if (ci && ci.map === 'atoms') {
|
||||
this.editor.hover(null);
|
||||
var atom = struct.atoms.get(ci.id);
|
||||
var res = editor.event.elementEdit.dispatch({
|
||||
attpnt: atom.attpnt
|
||||
});
|
||||
Promise.resolve(res).then(function (newatom) {
|
||||
if (atom.attpnt != newatom.attpnt) {
|
||||
var action = Action.fromAtomsAttrs(editor.render.ctab, ci.id, newatom);
|
||||
editor.update(action);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = APointTool;
|
||||
119
static/js/ketcher2/script/editor/tool/atom.js
Normal file
119
static/js/ketcher2/script/editor/tool/atom.js
Normal file
@ -0,0 +1,119 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Struct = require('../../chem/struct');
|
||||
var Action = require('../action');
|
||||
var utils = require('./utils');
|
||||
|
||||
function AtomTool(editor, atomProps) {
|
||||
if (!(this instanceof AtomTool)) {
|
||||
if (!editor.selection() || !editor.selection().atoms)
|
||||
return new AtomTool(editor, atomProps);
|
||||
|
||||
var action = Action.fromAtomsAttrs(editor.render.ctab, editor.selection().atoms,
|
||||
atomProps, true);
|
||||
editor.update(action);
|
||||
editor.selection(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
this.editor = editor;
|
||||
this.atomProps = atomProps;
|
||||
this.bondProps = { type: 1, stereo: Struct.Bond.PATTERN.STEREO.NONE };
|
||||
}
|
||||
|
||||
AtomTool.prototype.mousedown = function (event) {
|
||||
this.editor.hover(null);
|
||||
var ci = this.editor.findItem(event, ['atoms']);
|
||||
if (!ci) { // ci.type == 'Canvas'
|
||||
this.dragCtx = {};
|
||||
} else if (ci.map === 'atoms') {
|
||||
this.dragCtx = { item: ci };
|
||||
}
|
||||
};
|
||||
AtomTool.prototype.mousemove = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
if (!this.dragCtx || !this.dragCtx.item) {
|
||||
this.editor.hover(this.editor.findItem(event, ['atoms']));
|
||||
return;
|
||||
}
|
||||
|
||||
var dragCtx = this.dragCtx;
|
||||
var ci = this.editor.findItem(event, ['atoms']);
|
||||
|
||||
if (ci && ci.map === 'atoms' && ci.id === dragCtx.item.id) {
|
||||
// fromAtomsAttrs
|
||||
this.editor.hover(this.editor.findItem(event, ['atoms']));
|
||||
return;
|
||||
}
|
||||
|
||||
// fromAtomAddition
|
||||
var atom = rnd.ctab.molecule.atoms.get(dragCtx.item.id);
|
||||
|
||||
var newAtomPos = utils.calcNewAtomPos(atom.pp, rnd.page2obj(event));
|
||||
if (dragCtx.action)
|
||||
dragCtx.action.perform(rnd.ctab);
|
||||
|
||||
dragCtx.action = Action.fromBondAddition(rnd.ctab,
|
||||
this.bondProps, dragCtx.item.id, Object.assign({}, this.atomProps), newAtomPos, newAtomPos
|
||||
)[0];
|
||||
this.editor.update(dragCtx.action, true);
|
||||
};
|
||||
AtomTool.prototype.mouseup = function (event) {
|
||||
if (this.dragCtx) {
|
||||
var dragCtx = this.dragCtx;
|
||||
var rnd = this.editor.render;
|
||||
this.editor.update(dragCtx.action || (
|
||||
dragCtx.item ?
|
||||
Action.fromAtomsAttrs(rnd.ctab, dragCtx.item.id, this.atomProps, true) :
|
||||
Action.fromAtomAddition(rnd.ctab, rnd.page2obj(event), this.atomProps)
|
||||
));
|
||||
delete this.dragCtx;
|
||||
}
|
||||
};
|
||||
|
||||
function atomLongtapEvent(tool, render) {
|
||||
const dragCtx = tool.dragCtx;
|
||||
const editor = tool.editor;
|
||||
|
||||
const atomid = dragCtx.item && dragCtx.item.id;
|
||||
const atom = atomid ? // edit atom or add atom
|
||||
render.ctab.molecule.atoms.get(atomid) :
|
||||
new Struct.Atom({ label: '' });
|
||||
|
||||
// TODO: longtab event
|
||||
dragCtx.timeout = setTimeout(function () {
|
||||
delete tool.dragCtx;
|
||||
editor.selection(null);
|
||||
const res = editor.event.quickEdit.dispatch(atom);
|
||||
Promise.resolve(res).then(function (newatom) {
|
||||
const action = atomid ?
|
||||
Action.fromAtomsAttrs(render.ctab, atomid, newatom) :
|
||||
Action.fromAtomAddition(render.ctab, dragCtx.xy0, newatom);
|
||||
editor.update(action);
|
||||
});
|
||||
}, 750);
|
||||
dragCtx.stopTapping = function () {
|
||||
if (dragCtx.timeout) {
|
||||
clearTimeout(dragCtx.timeout);
|
||||
delete tool.dragCtx.timeout;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Object.assign(AtomTool, {
|
||||
atomLongtapEvent: atomLongtapEvent
|
||||
});
|
||||
65
static/js/ketcher2/script/editor/tool/attach.js
Normal file
65
static/js/ketcher2/script/editor/tool/attach.js
Normal file
@ -0,0 +1,65 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var element = require('../../chem/element');
|
||||
|
||||
function AttachTool(editor, attachPoints) {
|
||||
if (!(this instanceof AttachTool))
|
||||
return new AttachTool(editor, attachPoints);
|
||||
|
||||
this.attach = attachPoints || { atomid: 0, bondid: 0 };
|
||||
this.editor = editor;
|
||||
|
||||
this.editor.selection({
|
||||
atoms: [this.attach.atomid],
|
||||
bonds: [this.attach.bondid]
|
||||
});
|
||||
}
|
||||
|
||||
AttachTool.prototype.mousemove = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
|
||||
var ci = this.editor.findItem(event, ['atoms', 'bonds']);
|
||||
var struct = rnd.ctab.molecule;
|
||||
if (ci && ((ci.map === 'atoms' && element.map[struct.atoms.get(ci.id).label]) || ci.map === 'bonds'))
|
||||
this.editor.hover(ci);
|
||||
else
|
||||
this.editor.hover(null);
|
||||
return true;
|
||||
};
|
||||
|
||||
AttachTool.prototype.mouseup = function (event) {
|
||||
var editor = this.editor;
|
||||
var rnd = editor.render;
|
||||
var struct = rnd.ctab.molecule;
|
||||
var ci = editor.findItem(event, ['atoms', 'bonds']);
|
||||
|
||||
if (ci && ((ci.map === 'atoms' && element.map[struct.atoms.get(ci.id).label]) || ci.map === 'bonds')) {
|
||||
if (ci.map === 'atoms')
|
||||
this.attach.atomid = ci.id;
|
||||
else
|
||||
this.attach.bondid = ci.id;
|
||||
|
||||
this.editor.selection({
|
||||
atoms: [this.attach.atomid],
|
||||
bonds: [this.attach.bondid]
|
||||
});
|
||||
this.editor.event.attachEdit.dispatch(this.attach);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
module.exports = AttachTool;
|
||||
171
static/js/ketcher2/script/editor/tool/bond.js
Normal file
171
static/js/ketcher2/script/editor/tool/bond.js
Normal file
@ -0,0 +1,171 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Vec2 = require('../../util/vec2');
|
||||
var Struct = require('../../chem/struct');
|
||||
var Action = require('../action');
|
||||
var utils = require('./utils');
|
||||
|
||||
function BondTool(editor, bondProps) {
|
||||
if (!(this instanceof BondTool)) {
|
||||
// Action.fromBondAttrs(editor.render.ctab,
|
||||
// editor.selection().bonds, {
|
||||
// type: bondType(mode).type,
|
||||
// stereo: Bond.PATTERN.STEREO.NONE })
|
||||
editor.selection(null);
|
||||
return new BondTool(editor, bondProps);
|
||||
}
|
||||
|
||||
this.editor = editor;
|
||||
this.atomProps = { label: 'C' };
|
||||
this.bondProps = bondProps;
|
||||
}
|
||||
|
||||
BondTool.prototype.mousedown = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
this.editor.hover(null);
|
||||
this.dragCtx = {
|
||||
xy0: rnd.page2obj(event),
|
||||
item: this.editor.findItem(event, ['atoms', 'bonds'])
|
||||
};
|
||||
if (!this.dragCtx.item) // ci.type == 'Canvas'
|
||||
delete this.dragCtx.item;
|
||||
return true;
|
||||
};
|
||||
|
||||
BondTool.prototype.mousemove = function (event) { // eslint-disable-line max-statements
|
||||
var editor = this.editor;
|
||||
var rnd = editor.render;
|
||||
if ('dragCtx' in this) {
|
||||
var dragCtx = this.dragCtx;
|
||||
if (!('item' in dragCtx) || dragCtx.item.map === 'atoms') {
|
||||
if ('action' in dragCtx)
|
||||
dragCtx.action.perform(rnd.ctab);
|
||||
var i1, i2, p1, p2;
|
||||
if (('item' in dragCtx && dragCtx.item.map === 'atoms')) {
|
||||
// first mousedown event intersect with any atom
|
||||
i1 = dragCtx.item.id;
|
||||
i2 = editor.findItem(event, ['atoms'], dragCtx.item);
|
||||
} else {
|
||||
// first mousedown event intersect with any canvas
|
||||
i1 = this.atomProps;
|
||||
p1 = dragCtx.xy0;
|
||||
i2 = editor.findItem(event, ['atoms']);
|
||||
}
|
||||
var dist = Number.MAX_VALUE;
|
||||
if (i2 && i2.map === 'atoms') {
|
||||
// after mousedown events is appered, cursor is moved and then cursor intersects any atoms
|
||||
i2 = i2.id;
|
||||
} else {
|
||||
i2 = this.atomProps;
|
||||
var xy1 = rnd.page2obj(event);
|
||||
dist = Vec2.dist(dragCtx.xy0, xy1);
|
||||
if (p1) {
|
||||
// rotation only, leght of bond = 1;
|
||||
p2 = utils.calcNewAtomPos(p1, xy1);
|
||||
} else {
|
||||
// first mousedown event intersect with any atom and
|
||||
// rotation only, leght of bond = 1;
|
||||
var atom = rnd.ctab.molecule.atoms.get(i1);
|
||||
p1 = utils.calcNewAtomPos(atom.pp.get_xy0(), xy1);
|
||||
}
|
||||
}
|
||||
// don't rotate the bond if the distance between the start and end point is too small
|
||||
if (dist > 0.3)
|
||||
dragCtx.action = Action.fromBondAddition(rnd.ctab, this.bondProps, i1, i2, p1, p2)[0];
|
||||
else
|
||||
delete dragCtx.action;
|
||||
this.editor.update(dragCtx.action, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
this.editor.hover(this.editor.findItem(event, ['atoms', 'bonds']));
|
||||
return true;
|
||||
};
|
||||
|
||||
BondTool.prototype.mouseup = function (event) { // eslint-disable-line max-statements
|
||||
if ('dragCtx' in this) {
|
||||
var dragCtx = this.dragCtx;
|
||||
var rnd = this.editor.render;
|
||||
var struct = rnd.ctab.molecule;
|
||||
if ('action' in dragCtx) {
|
||||
this.editor.update(dragCtx.action);
|
||||
} else if (!('item' in dragCtx)) {
|
||||
var xy = rnd.page2obj(event);
|
||||
var v = new Vec2(1.0 / 2, 0).rotate(
|
||||
this.bondProps.type == Struct.Bond.PATTERN.TYPE.SINGLE ? -Math.PI / 6 : 0
|
||||
);
|
||||
var bondAddition = Action.fromBondAddition(rnd.ctab,
|
||||
this.bondProps, { label: 'C' }, { label: 'C' },
|
||||
Vec2.diff(xy, v), Vec2.sum(xy, v));
|
||||
|
||||
this.editor.update(bondAddition[0]);
|
||||
} else if (dragCtx.item.map === 'atoms') {
|
||||
// when does it hapend?
|
||||
this.editor.update(Action.fromBondAddition(rnd.ctab, this.bondProps, dragCtx.item.id)[0]);
|
||||
} else if (dragCtx.item.map === 'bonds') {
|
||||
var bondProps = Object.assign({}, this.bondProps);
|
||||
var bond = struct.bonds.get(dragCtx.item.id);
|
||||
|
||||
this.editor.update(bondChangingAction(rnd.ctab, dragCtx.item.id, bond, bondProps));
|
||||
}
|
||||
delete this.dragCtx;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param itemID - bond id in structure
|
||||
* @param bond - bond for change
|
||||
* @param bondProps - bondTool properties
|
||||
* @returns Action
|
||||
*/
|
||||
function bondChangingAction(restruct, itemID, bond, bondProps) {
|
||||
if (bondProps.stereo !== Struct.Bond.PATTERN.STEREO.NONE && //
|
||||
bondProps.type === Struct.Bond.PATTERN.TYPE.SINGLE &&
|
||||
bond.type === bondProps.type && bond.stereo === bondProps.stereo)
|
||||
// if bondTool is stereo and equal to bond for change
|
||||
return Action.fromBondFlipping(restruct, itemID);
|
||||
|
||||
var loop = plainBondTypes.indexOf(bondProps.type) >= 0 ? plainBondTypes : null;
|
||||
if (bondProps.stereo === Struct.Bond.PATTERN.STEREO.NONE &&
|
||||
bondProps.type === Struct.Bond.PATTERN.TYPE.SINGLE &&
|
||||
bond.stereo === Struct.Bond.PATTERN.STEREO.NONE &&
|
||||
loop)
|
||||
// if `Single bond` tool is chosen and bond for change in `plainBondTypes`
|
||||
bondProps.type = loop[(loop.indexOf(bond.type) + 1) % loop.length];
|
||||
|
||||
return Action.fromBondAttrs(restruct, itemID, bondProps,
|
||||
bondFlipRequired(restruct.molecule, bond, bondProps));
|
||||
}
|
||||
|
||||
function bondFlipRequired(struct, bond, attrs) {
|
||||
return attrs.type == Struct.Bond.PATTERN.TYPE.SINGLE &&
|
||||
bond.stereo == Struct.Bond.PATTERN.STEREO.NONE &&
|
||||
attrs.stereo != Struct.Bond.PATTERN.STEREO.NONE &&
|
||||
struct.atoms.get(bond.begin).neighbors.length <
|
||||
struct.atoms.get(bond.end).neighbors.length;
|
||||
}
|
||||
|
||||
var plainBondTypes = [
|
||||
Struct.Bond.PATTERN.TYPE.SINGLE,
|
||||
Struct.Bond.PATTERN.TYPE.DOUBLE,
|
||||
Struct.Bond.PATTERN.TYPE.TRIPLE
|
||||
];
|
||||
|
||||
module.exports = Object.assign(BondTool, {
|
||||
bondChangingAction: bondChangingAction
|
||||
});
|
||||
111
static/js/ketcher2/script/editor/tool/chain.js
Normal file
111
static/js/ketcher2/script/editor/tool/chain.js
Normal file
@ -0,0 +1,111 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Vec2 = require('../../util/vec2');
|
||||
var Struct = require('../../chem/struct');
|
||||
var Action = require('../action');
|
||||
var utils = require('./utils');
|
||||
|
||||
var Atom = require('./atom');
|
||||
var Bond = require('./bond');
|
||||
|
||||
function ChainTool(editor) {
|
||||
if (!(this instanceof ChainTool))
|
||||
return new ChainTool(editor);
|
||||
|
||||
this.editor = editor;
|
||||
this.editor.selection(null);
|
||||
}
|
||||
|
||||
ChainTool.prototype.mousedown = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
var ci = this.editor.findItem(event, ['atoms', 'bonds']);
|
||||
this.editor.hover(null);
|
||||
this.dragCtx = {
|
||||
xy0: rnd.page2obj(event),
|
||||
item: ci
|
||||
};
|
||||
if (ci && ci.map === 'atoms') {
|
||||
this.editor.selection({ atoms: [ci.id] }); // for change atom
|
||||
// this event has to be stopped in others events by `tool.dragCtx.stopTapping()`
|
||||
Atom.atomLongtapEvent(this, rnd);
|
||||
}
|
||||
if (!this.dragCtx.item) // ci.type == 'Canvas'
|
||||
delete this.dragCtx.item;
|
||||
return true;
|
||||
};
|
||||
|
||||
ChainTool.prototype.mousemove = function (event) { // eslint-disable-line max-statements
|
||||
var editor = this.editor;
|
||||
var rnd = editor.render;
|
||||
if (this.dragCtx) {
|
||||
if ('stopTapping' in this.dragCtx)
|
||||
this.dragCtx.stopTapping();
|
||||
this.editor.selection(null);
|
||||
var dragCtx = this.dragCtx;
|
||||
if (!('item' in dragCtx) || dragCtx.item.map === 'atoms') {
|
||||
if ('action' in dragCtx)
|
||||
dragCtx.action.perform(rnd.ctab);
|
||||
|
||||
var atoms = rnd.ctab.molecule.atoms;
|
||||
var pos0 = dragCtx.item ? atoms.get(dragCtx.item.id).pp :
|
||||
dragCtx.xy0;
|
||||
var pos1 = rnd.page2obj(event);
|
||||
var sectCount = Math.ceil(Vec2.diff(pos1, pos0).length());
|
||||
var angle = event.ctrlKey ? utils.calcAngle(pos0, pos1) :
|
||||
utils.fracAngle(pos0, pos1);
|
||||
|
||||
dragCtx.action = Action.fromChain(rnd.ctab, pos0, angle, sectCount,
|
||||
dragCtx.item ? dragCtx.item.id : null);
|
||||
editor.event.message.dispatch({
|
||||
info: sectCount + " sectors"
|
||||
});
|
||||
this.editor.update(dragCtx.action, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
this.editor.hover(this.editor.findItem(event, ['atoms', 'bonds']));
|
||||
return true;
|
||||
};
|
||||
|
||||
ChainTool.prototype.mouseup = function () {
|
||||
var rnd = this.editor.render;
|
||||
var struct = rnd.ctab.molecule;
|
||||
if (this.dragCtx) {
|
||||
if ('stopTapping' in this.dragCtx)
|
||||
this.dragCtx.stopTapping();
|
||||
var dragCtx = this.dragCtx;
|
||||
|
||||
var action = dragCtx.action;
|
||||
if (!action && dragCtx.item && dragCtx.item.map === 'bonds') {
|
||||
var bond = struct.bonds.get(dragCtx.item.id);
|
||||
|
||||
action = Bond.bondChangingAction(rnd.ctab, dragCtx.item.id, bond, {
|
||||
type: Struct.Bond.PATTERN.TYPE.SINGLE,
|
||||
stereo: Struct.Bond.PATTERN.STEREO.NONE
|
||||
});
|
||||
}
|
||||
delete this.dragCtx;
|
||||
if (action)
|
||||
this.editor.update(action);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
ChainTool.prototype.cancel = ChainTool.prototype.mouseleave =
|
||||
ChainTool.prototype.mouseup;
|
||||
|
||||
module.exports = ChainTool;
|
||||
53
static/js/ketcher2/script/editor/tool/charge.js
Normal file
53
static/js/ketcher2/script/editor/tool/charge.js
Normal file
@ -0,0 +1,53 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Action = require('../action');
|
||||
var element = require('../../chem/element');
|
||||
|
||||
function ChargeTool(editor, charge) {
|
||||
if (!(this instanceof ChargeTool))
|
||||
return new ChargeTool(editor, charge);
|
||||
|
||||
this.editor = editor;
|
||||
this.editor.selection(null);
|
||||
this.charge = charge;
|
||||
}
|
||||
|
||||
ChargeTool.prototype.mousemove = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
var ci = this.editor.findItem(event, ['atoms']);
|
||||
var struct = rnd.ctab.molecule;
|
||||
if (ci && ci.map === 'atoms' && element.map[struct.atoms.get(ci.id).label])
|
||||
this.editor.hover(ci);
|
||||
else
|
||||
this.editor.hover(null);
|
||||
return true;
|
||||
};
|
||||
ChargeTool.prototype.mouseup = function (event) {
|
||||
var editor = this.editor;
|
||||
var rnd = editor.render;
|
||||
var struct = rnd.ctab.molecule;
|
||||
var ci = editor.findItem(event, ['atoms']);
|
||||
if (ci && ci.map === 'atoms' && element.map[struct.atoms.get(ci.id).label]) {
|
||||
this.editor.hover(null);
|
||||
this.editor.update(Action.fromAtomsAttrs(rnd.ctab, ci.id, {
|
||||
charge: struct.atoms.get(ci.id).charge + this.charge
|
||||
}));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
module.exports = ChargeTool;
|
||||
34
static/js/ketcher2/script/editor/tool/chiral-flag.js
Normal file
34
static/js/ketcher2/script/editor/tool/chiral-flag.js
Normal file
@ -0,0 +1,34 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Action = require('../action');
|
||||
|
||||
function ChiralFlagTool(editor) {
|
||||
if (!(this instanceof ChiralFlagTool)) {
|
||||
this.editor = editor;
|
||||
const rnd = this.editor.render;
|
||||
|
||||
let action = null;
|
||||
if (rnd.ctab.molecule.isChiral === false)
|
||||
action = Action.fromChiralFlagAddition(rnd.ctab);
|
||||
else
|
||||
action = Action.fromChiralFlagDeletion(rnd.ctab);
|
||||
|
||||
this.editor.update(action);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ChiralFlagTool;
|
||||
86
static/js/ketcher2/script/editor/tool/eraser.js
Normal file
86
static/js/ketcher2/script/editor/tool/eraser.js
Normal file
@ -0,0 +1,86 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Action = require('../action');
|
||||
var LassoHelper = require('./helper/lasso');
|
||||
|
||||
function EraserTool(editor, mode) {
|
||||
if (!(this instanceof EraserTool)) {
|
||||
if (!editor.selection())
|
||||
return new EraserTool(editor, mode);
|
||||
|
||||
var action = Action.fromFragmentDeletion(editor.render.ctab, editor.selection());
|
||||
editor.update(action);
|
||||
editor.selection(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
this.editor = editor;
|
||||
|
||||
this.maps = ['atoms', 'bonds', 'rxnArrows', 'rxnPluses', 'sgroups', 'sgroupData', 'chiralFlags'];
|
||||
this.lassoHelper = new LassoHelper(mode || 0, editor);
|
||||
}
|
||||
|
||||
EraserTool.prototype.mousedown = function (event) {
|
||||
var ci = this.editor.findItem(event, this.maps);
|
||||
if (!ci) // ci.type == 'Canvas'
|
||||
this.lassoHelper.begin(event);
|
||||
};
|
||||
|
||||
EraserTool.prototype.mousemove = function (event) {
|
||||
if (this.lassoHelper.running())
|
||||
this.editor.selection(this.lassoHelper.addPoint(event));
|
||||
else
|
||||
this.editor.hover(this.editor.findItem(event, this.maps));
|
||||
};
|
||||
|
||||
EraserTool.prototype.mouseleave = function (event) {
|
||||
if (this.lassoHelper.running(event))
|
||||
this.lassoHelper.end(event);
|
||||
};
|
||||
|
||||
EraserTool.prototype.mouseup = function (event) { // eslint-disable-line max-statements
|
||||
var rnd = this.editor.render;
|
||||
if (this.lassoHelper.running()) { // TODO it catches more events than needed, to be re-factored
|
||||
this.editor.update(Action.fromFragmentDeletion(rnd.ctab, this.lassoHelper.end(event)));
|
||||
this.editor.selection(null);
|
||||
} else {
|
||||
var ci = this.editor.findItem(event, this.maps);
|
||||
if (ci) { // ci.type != 'Canvas'
|
||||
this.editor.hover(null);
|
||||
if (ci.map === 'atoms') {
|
||||
this.editor.update(Action.fromAtomDeletion(rnd.ctab, ci.id));
|
||||
} else if (ci.map === 'bonds') {
|
||||
this.editor.update(Action.fromBondDeletion(rnd.ctab, ci.id));
|
||||
} else if (ci.map === 'sgroups' || ci.map === 'sgroupData') {
|
||||
this.editor.update(Action.fromSgroupDeletion(rnd.ctab, ci.id));
|
||||
} else if (ci.map === 'rxnArrows') {
|
||||
this.editor.update(Action.fromArrowDeletion(rnd.ctab, ci.id));
|
||||
} else if (ci.map === 'rxnPluses') {
|
||||
this.editor.update(Action.fromPlusDeletion(rnd.ctab, ci.id));
|
||||
} else if (ci.map === 'chiralFlags') {
|
||||
this.editor.update(Action.fromChiralFlagDeletion(rnd.ctab));
|
||||
} else {
|
||||
// TODO re-factoring needed - should be "map-independent"
|
||||
console.error('EraserTool: unable to delete the object ' + ci.map + '[' + ci.id + ']');
|
||||
return;
|
||||
}
|
||||
this.editor.selection(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = EraserTool;
|
||||
83
static/js/ketcher2/script/editor/tool/helper/lasso.js
Normal file
83
static/js/ketcher2/script/editor/tool/helper/lasso.js
Normal file
@ -0,0 +1,83 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var locate = require('./locate');
|
||||
var draw = require('../../../render/draw');
|
||||
var scale = require('../../../util/scale');
|
||||
|
||||
function LassoHelper(mode, editor, fragment) {
|
||||
this.mode = mode;
|
||||
this.fragment = fragment;
|
||||
this.editor = editor;
|
||||
}
|
||||
LassoHelper.prototype.getSelection = function () {
|
||||
var rnd = this.editor.render;
|
||||
if (this.mode == 0)
|
||||
return locate.inPolygon(rnd.ctab, this.points);
|
||||
else if (this.mode == 1)
|
||||
return locate.inRectangle(rnd.ctab, this.points[0], this.points[1]);
|
||||
else
|
||||
throw new Error('Selector mode unknown'); // eslint-disable-line no-else-return
|
||||
};
|
||||
|
||||
LassoHelper.prototype.begin = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
this.points = [rnd.page2obj(event)];
|
||||
if (this.mode == 1)
|
||||
this.points.push(this.points[0]);
|
||||
};
|
||||
|
||||
LassoHelper.prototype.running = function () {
|
||||
return !!this.points;
|
||||
};
|
||||
|
||||
LassoHelper.prototype.addPoint = function (event) {
|
||||
if (this.points) {
|
||||
var rnd = this.editor.render;
|
||||
if (this.mode == 0)
|
||||
this.points.push(rnd.page2obj(event));
|
||||
else if (this.mode == 1)
|
||||
this.points = [this.points[0], rnd.page2obj(event)];
|
||||
this.update();
|
||||
return this.getSelection();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
LassoHelper.prototype.update = function () {
|
||||
if (this.selection) {
|
||||
this.selection.remove();
|
||||
this.selection = null;
|
||||
}
|
||||
if (this.points && this.points.length > 1) {
|
||||
var rnd = this.editor.render;
|
||||
var dp = this.points.map(function (p) {
|
||||
return scale.obj2scaled(p, rnd.options).add(rnd.options.offset);
|
||||
});
|
||||
this.selection = this.mode == 0 ?
|
||||
draw.selectionPolygon(rnd.paper, dp, rnd.options) :
|
||||
draw.selectionRectangle(rnd.paper, dp[0], dp[1], rnd.options);
|
||||
}
|
||||
};
|
||||
|
||||
LassoHelper.prototype.end = function () {
|
||||
var ret = this.getSelection();
|
||||
this.points = null;
|
||||
this.update(null);
|
||||
return ret;
|
||||
};
|
||||
|
||||
module.exports = LassoHelper;
|
||||
160
static/js/ketcher2/script/editor/tool/helper/locate.js
Normal file
160
static/js/ketcher2/script/editor/tool/helper/locate.js
Normal file
@ -0,0 +1,160 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Vec2 = require('../../../util/vec2');
|
||||
|
||||
function getElementsInRectangle(restruct, p0, p1) {
|
||||
var bondList = [];
|
||||
var atomList = [];
|
||||
|
||||
var x0 = Math.min(p0.x, p1.x),
|
||||
x1 = Math.max(p0.x, p1.x),
|
||||
y0 = Math.min(p0.y, p1.y),
|
||||
y1 = Math.max(p0.y, p1.y);
|
||||
restruct.bonds.each(function (bid, bond) {
|
||||
var centre = Vec2.lc2(restruct.atoms.get(bond.b.begin).a.pp, 0.5,
|
||||
restruct.atoms.get(bond.b.end).a.pp, 0.5);
|
||||
if (centre.x > x0 && centre.x < x1 && centre.y > y0 && centre.y < y1)
|
||||
bondList.push(bid);
|
||||
});
|
||||
restruct.atoms.each(function (aid, atom) {
|
||||
if (atom.a.pp.x > x0 && atom.a.pp.x < x1 && atom.a.pp.y > y0 && atom.a.pp.y < y1)
|
||||
atomList.push(aid);
|
||||
});
|
||||
var rxnArrowsList = [];
|
||||
var rxnPlusesList = [];
|
||||
restruct.rxnArrows.each(function (id, item) {
|
||||
if (item.item.pp.x > x0 && item.item.pp.x < x1 && item.item.pp.y > y0 && item.item.pp.y < y1)
|
||||
rxnArrowsList.push(id);
|
||||
});
|
||||
restruct.rxnPluses.each(function (id, item) {
|
||||
if (item.item.pp.x > x0 && item.item.pp.x < x1 && item.item.pp.y > y0 && item.item.pp.y < y1)
|
||||
rxnPlusesList.push(id);
|
||||
});
|
||||
var chiralFlagList = [];
|
||||
restruct.chiralFlags.each(function (id, item) {
|
||||
if (item.pp.x > x0 && item.pp.x < x1 && item.pp.y > y0 && item.pp.y < y1)
|
||||
chiralFlagList.push(id);
|
||||
});
|
||||
var sgroupDataList = [];
|
||||
restruct.sgroupData.each(function (id, item) {
|
||||
if (item.sgroup.pp.x > x0 && item.sgroup.pp.x < x1 && item.sgroup.pp.y > y0 && item.sgroup.pp.y < y1)
|
||||
sgroupDataList.push(id);
|
||||
});
|
||||
return {
|
||||
atoms: atomList,
|
||||
bonds: bondList,
|
||||
rxnArrows: rxnArrowsList,
|
||||
rxnPluses: rxnPlusesList,
|
||||
chiralFlags: chiralFlagList,
|
||||
sgroupData: sgroupDataList
|
||||
};
|
||||
}
|
||||
|
||||
function getElementsInPolygon(restruct, rr) { // eslint-disable-line max-statements
|
||||
var bondList = [];
|
||||
var atomList = [];
|
||||
var r = [];
|
||||
for (var i = 0; i < rr.length; ++i)
|
||||
r[i] = new Vec2(rr[i].x, rr[i].y);
|
||||
restruct.bonds.each(function (bid, bond) {
|
||||
var centre = Vec2.lc2(restruct.atoms.get(bond.b.begin).a.pp, 0.5,
|
||||
restruct.atoms.get(bond.b.end).a.pp, 0.5);
|
||||
if (isPointInPolygon(r, centre))
|
||||
bondList.push(bid);
|
||||
});
|
||||
restruct.atoms.each(function (aid, atom) {
|
||||
if (isPointInPolygon(r, atom.a.pp))
|
||||
atomList.push(aid);
|
||||
});
|
||||
var rxnArrowsList = [];
|
||||
var rxnPlusesList = [];
|
||||
restruct.rxnArrows.each(function (id, item) {
|
||||
if (isPointInPolygon(r, item.item.pp))
|
||||
rxnArrowsList.push(id);
|
||||
});
|
||||
restruct.rxnPluses.each(function (id, item) {
|
||||
if (isPointInPolygon(r, item.item.pp))
|
||||
rxnPlusesList.push(id);
|
||||
});
|
||||
var chiralFlagList = [];
|
||||
restruct.chiralFlags.each(function (id, item) {
|
||||
if (isPointInPolygon(r, item.pp))
|
||||
chiralFlagList.push(id);
|
||||
});
|
||||
var sgroupDataList = [];
|
||||
restruct.sgroupData.each(function (id, item) {
|
||||
if (isPointInPolygon(r, item.sgroup.pp))
|
||||
sgroupDataList.push(id);
|
||||
});
|
||||
|
||||
return {
|
||||
atoms: atomList,
|
||||
bonds: bondList,
|
||||
rxnArrows: rxnArrowsList,
|
||||
rxnPluses: rxnPlusesList,
|
||||
chiralFlags: chiralFlagList,
|
||||
sgroupData: sgroupDataList
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: test me see testPolygon from
|
||||
// 'Remove unused methods from render' commit
|
||||
function isPointInPolygon(r, p) { // eslint-disable-line max-statements
|
||||
var d = new Vec2(0, 1);
|
||||
var n = d.rotate(Math.PI / 2);
|
||||
var v0 = Vec2.diff(r[r.length - 1], p);
|
||||
var n0 = Vec2.dot(n, v0);
|
||||
var d0 = Vec2.dot(d, v0);
|
||||
var w0 = null;
|
||||
var counter = 0;
|
||||
var eps = 1e-5;
|
||||
var flag1 = false,
|
||||
flag0 = false;
|
||||
|
||||
for (var i = 0; i < r.length; ++i) {
|
||||
var v1 = Vec2.diff(r[i], p);
|
||||
var w1 = Vec2.diff(v1, v0);
|
||||
var n1 = Vec2.dot(n, v1);
|
||||
var d1 = Vec2.dot(d, v1);
|
||||
flag1 = false;
|
||||
if (n1 * n0 < 0) {
|
||||
if (d1 * d0 > -eps) {
|
||||
if (d0 > -eps)
|
||||
flag1 = true;
|
||||
/* eslint-disable no-mixed-operators*/
|
||||
} else if ((Math.abs(n0) * Math.abs(d1) - Math.abs(n1) * Math.abs(d0)) * d1 > 0) {
|
||||
/* eslint-enable no-mixed-operators*/
|
||||
flag1 = true;
|
||||
}
|
||||
}
|
||||
if (flag1 && flag0 && Vec2.dot(w1, n) * Vec2.dot(w0, n) >= 0)
|
||||
flag1 = false;
|
||||
if (flag1)
|
||||
counter++;
|
||||
v0 = v1;
|
||||
n0 = n1;
|
||||
d0 = d1;
|
||||
w0 = w1;
|
||||
flag0 = flag1;
|
||||
}
|
||||
return (counter % 2) != 0;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
inRectangle: getElementsInRectangle,
|
||||
inPolygon: getElementsInPolygon
|
||||
};
|
||||
59
static/js/ketcher2/script/editor/tool/paste.js
Normal file
59
static/js/ketcher2/script/editor/tool/paste.js
Normal file
@ -0,0 +1,59 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Action = require('../action');
|
||||
|
||||
function PasteTool(editor, struct) {
|
||||
if (!(this instanceof PasteTool))
|
||||
return new PasteTool(editor, struct);
|
||||
|
||||
this.editor = editor;
|
||||
this.editor.selection(null);
|
||||
this.struct = struct;
|
||||
|
||||
var rnd = editor.render;
|
||||
var point = editor.lastEvent ?
|
||||
rnd.page2obj(editor.lastEvent) : null;
|
||||
this.action = Action.fromPaste(rnd.ctab, this.struct, point);
|
||||
this.editor.update(this.action, true);
|
||||
}
|
||||
|
||||
PasteTool.prototype.mousemove = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
if (this.action)
|
||||
this.action.perform(rnd.ctab);
|
||||
this.action = Action.fromPaste(rnd.ctab, this.struct, rnd.page2obj(event));
|
||||
this.editor.update(this.action, true);
|
||||
};
|
||||
|
||||
PasteTool.prototype.mouseup = function () {
|
||||
if (this.action) {
|
||||
var action = this.action;
|
||||
delete this.action;
|
||||
this.editor.update(action);
|
||||
}
|
||||
};
|
||||
|
||||
PasteTool.prototype.cancel = PasteTool.prototype.mouseleave = function () {
|
||||
var rnd = this.editor.render;
|
||||
if (this.action) {
|
||||
this.action.perform(rnd.ctab); // revert the action
|
||||
delete this.action;
|
||||
rnd.update();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = PasteTool;
|
||||
65
static/js/ketcher2/script/editor/tool/reactionarrow.js
Normal file
65
static/js/ketcher2/script/editor/tool/reactionarrow.js
Normal file
@ -0,0 +1,65 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Action = require('../action');
|
||||
|
||||
function ReactionArrowTool(editor) {
|
||||
if (!(this instanceof ReactionArrowTool))
|
||||
return new ReactionArrowTool(editor);
|
||||
|
||||
this.editor = editor;
|
||||
this.editor.selection(null);
|
||||
}
|
||||
|
||||
ReactionArrowTool.prototype.mousedown = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
var ci = this.editor.findItem(event, ['rxnArrows']);
|
||||
if (ci && ci.map === 'rxnArrows') {
|
||||
this.editor.hover(null);
|
||||
this.editor.selection({ rxnArrows: [ci.id] });
|
||||
this.dragCtx = {
|
||||
xy0: rnd.page2obj(event),
|
||||
action: new Action()
|
||||
};
|
||||
}
|
||||
};
|
||||
ReactionArrowTool.prototype.mousemove = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
if ('dragCtx' in this) {
|
||||
if (this.dragCtx.action)
|
||||
this.dragCtx.action.perform(rnd.ctab);
|
||||
|
||||
this.dragCtx.action = Action.fromMultipleMove(
|
||||
rnd.ctab,
|
||||
this.editor.selection() || {},
|
||||
rnd.page2obj(event).sub(this.dragCtx.xy0)
|
||||
);
|
||||
this.editor.update(this.dragCtx.action, true);
|
||||
} else {
|
||||
this.editor.hover(this.editor.findItem(event, ['rxnArrows']));
|
||||
}
|
||||
};
|
||||
ReactionArrowTool.prototype.mouseup = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
if (this.dragCtx) {
|
||||
this.editor.update(this.dragCtx.action); // TODO investigate, subsequent undo/redo fails
|
||||
delete this.dragCtx;
|
||||
} else if (rnd.ctab.molecule.rxnArrows.count() < 1) {
|
||||
this.editor.update(Action.fromArrowAddition(rnd.ctab, rnd.page2obj(event)));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ReactionArrowTool;
|
||||
129
static/js/ketcher2/script/editor/tool/reactionmap.js
Normal file
129
static/js/ketcher2/script/editor/tool/reactionmap.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.
|
||||
***************************************************************************/
|
||||
|
||||
var Set = require('../../util/set');
|
||||
var scale = require('../../util/scale');
|
||||
var Action = require('../action');
|
||||
var draw = require('../../render/draw');
|
||||
|
||||
function ReactionMapTool(editor) {
|
||||
if (!(this instanceof ReactionMapTool))
|
||||
return new ReactionMapTool(editor);
|
||||
|
||||
this.editor = editor;
|
||||
this.editor.selection(null);
|
||||
|
||||
this.rcs = this.editor.render.ctab.molecule.getComponents();
|
||||
}
|
||||
|
||||
ReactionMapTool.prototype.mousedown = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
var ci = this.editor.findItem(event, ['atoms']);
|
||||
if (ci && ci.map === 'atoms') {
|
||||
this.editor.hover(null);
|
||||
this.dragCtx = {
|
||||
item: ci,
|
||||
xy0: rnd.page2obj(event)
|
||||
};
|
||||
}
|
||||
};
|
||||
ReactionMapTool.prototype.mousemove = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
if ('dragCtx' in this) {
|
||||
var ci = this.editor.findItem(event, ['atoms'], this.dragCtx.item);
|
||||
var atoms = rnd.ctab.molecule.atoms;
|
||||
if (ci && ci.map === 'atoms' && isValidMap(this.rcs, this.dragCtx.item.id, ci.id)) {
|
||||
this.editor.hover(ci);
|
||||
this.updateLine(atoms.get(this.dragCtx.item.id).pp, atoms.get(ci.id).pp);
|
||||
} else {
|
||||
this.editor.hover(null);
|
||||
this.updateLine(atoms.get(this.dragCtx.item.id).pp, rnd.page2obj(event));
|
||||
}
|
||||
} else {
|
||||
this.editor.hover(this.editor.findItem(event, ['atoms']));
|
||||
}
|
||||
};
|
||||
|
||||
ReactionMapTool.prototype.updateLine = function (p1, p2) {
|
||||
if (this.line) {
|
||||
this.line.remove();
|
||||
this.line = null;
|
||||
}
|
||||
if (p1 && p2) {
|
||||
var rnd = this.editor.render;
|
||||
this.line = draw.selectionLine(rnd.paper,
|
||||
scale.obj2scaled(p1, rnd.options).add(rnd.options.offset),
|
||||
scale.obj2scaled(p2, rnd.options).add(rnd.options.offset),
|
||||
rnd.options);
|
||||
}
|
||||
};
|
||||
|
||||
ReactionMapTool.prototype.mouseup = function (event) { // eslint-disable-line max-statements
|
||||
if ('dragCtx' in this) {
|
||||
var rnd = this.editor.render;
|
||||
var ci = this.editor.findItem(event, ['atoms'], this.dragCtx.item);
|
||||
if (ci && ci.map === 'atoms' && isValidMap(this.rcs, this.dragCtx.item.id, ci.id)) {
|
||||
var action = new Action();
|
||||
var atoms = rnd.ctab.molecule.atoms;
|
||||
var atom1 = atoms.get(this.dragCtx.item.id);
|
||||
var atom2 = atoms.get(ci.id);
|
||||
var aam1 = atom1.aam;
|
||||
var aam2 = atom2.aam;
|
||||
if (!aam1 || aam1 != aam2) {
|
||||
if (aam1 && aam1 != aam2 || !aam1 && aam2) { // eslint-disable-line no-mixed-operators
|
||||
atoms.each(
|
||||
function (aid, atom) {
|
||||
if (aid != this.dragCtx.item.id && (aam1 && atom.aam == aam1 || aam2 && atom.aam == aam2)) // eslint-disable-line no-mixed-operators
|
||||
action.mergeWith(Action.fromAtomsAttrs(rnd.ctab, aid, { aam: 0 }));
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
if (aam1) {
|
||||
action.mergeWith(Action.fromAtomsAttrs(rnd.ctab, ci.id, { aam: aam1 }));
|
||||
} else {
|
||||
var aam = 0;
|
||||
atoms.each(function (aid, atom) {
|
||||
aam = Math.max(aam, atom.aam || 0);
|
||||
});
|
||||
action.mergeWith(Action.fromAtomsAttrs(rnd.ctab, this.dragCtx.item.id, { aam: aam + 1 }));
|
||||
action.mergeWith(Action.fromAtomsAttrs(rnd.ctab, ci.id, { aam: aam + 1 }));
|
||||
}
|
||||
this.editor.update(action);
|
||||
}
|
||||
}
|
||||
this.updateLine(null);
|
||||
delete this.dragCtx;
|
||||
}
|
||||
this.editor.hover(null);
|
||||
};
|
||||
|
||||
function isValidMap(rcs, aid1, aid2) {
|
||||
var t1, t2;
|
||||
for (var ri = 0; (!t1 || !t2) && ri < rcs.reactants.length; ri++) {
|
||||
var ro = Set.list(rcs.reactants[ri]);
|
||||
if (!t1 && ro.indexOf(aid1) >= 0) t1 = 'r';
|
||||
if (!t2 && ro.indexOf(aid2) >= 0) t2 = 'r';
|
||||
}
|
||||
for (var pi = 0; (!t1 || !t2) && pi < rcs.products.length; pi++) {
|
||||
var po = Set.list(rcs.products[pi]);
|
||||
if (!t1 && po.indexOf(aid1) >= 0) t1 = 'p';
|
||||
if (!t2 && po.indexOf(aid2) >= 0) t2 = 'p';
|
||||
}
|
||||
return t1 && t2 && t1 != t2;
|
||||
}
|
||||
|
||||
module.exports = ReactionMapTool;
|
||||
60
static/js/ketcher2/script/editor/tool/reactionplus.js
Normal file
60
static/js/ketcher2/script/editor/tool/reactionplus.js
Normal file
@ -0,0 +1,60 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Action = require('../action');
|
||||
|
||||
function ReactionPlusTool(editor) {
|
||||
if (!(this instanceof ReactionPlusTool))
|
||||
return new ReactionPlusTool(editor);
|
||||
|
||||
this.editor = editor;
|
||||
this.editor.selection(null);
|
||||
}
|
||||
ReactionPlusTool.prototype.mousedown = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
var ci = this.editor.findItem(event, ['rxnPluses']);
|
||||
if (ci && ci.map === 'rxnPluses') {
|
||||
this.editor.hover(null);
|
||||
this.editor.selection({ rxnPluses: [ci.id] });
|
||||
this.dragCtx = { xy0: rnd.page2obj(event) };
|
||||
}
|
||||
};
|
||||
ReactionPlusTool.prototype.mousemove = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
if ('dragCtx' in this) {
|
||||
if (this.dragCtx.action)
|
||||
this.dragCtx.action.perform(rnd.ctab);
|
||||
this.dragCtx.action = Action.fromMultipleMove(
|
||||
rnd.ctab,
|
||||
this.editor.selection() || {},
|
||||
rnd.page2obj(event).sub(this.dragCtx.xy0)
|
||||
);
|
||||
this.editor.update(this.dragCtx.action, true);
|
||||
} else {
|
||||
this.editor.hover(this.editor.findItem(event, ['rxnPluses']));
|
||||
}
|
||||
};
|
||||
ReactionPlusTool.prototype.mouseup = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
if (this.dragCtx) {
|
||||
this.editor.update(this.dragCtx.action); // TODO investigate, subsequent undo/redo fails
|
||||
delete this.dragCtx;
|
||||
} else {
|
||||
this.editor.update(Action.fromPlusAddition(rnd.ctab, rnd.page2obj(event)));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ReactionPlusTool;
|
||||
51
static/js/ketcher2/script/editor/tool/reactionunmap.js
Normal file
51
static/js/ketcher2/script/editor/tool/reactionunmap.js
Normal file
@ -0,0 +1,51 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Action = require('../action');
|
||||
|
||||
function ReactionUnmapTool(editor) {
|
||||
if (!(this instanceof ReactionUnmapTool))
|
||||
return new ReactionUnmapTool(editor);
|
||||
|
||||
this.editor = editor;
|
||||
this.editor.selection(null);
|
||||
}
|
||||
ReactionUnmapTool.prototype.mousemove = function (event) {
|
||||
var ci = this.editor.findItem(event, ['atoms']);
|
||||
if (ci && ci.map === 'atoms')
|
||||
this.editor.hover(this.editor.render.ctab.molecule.atoms.get(ci.id).aam ? ci : null);
|
||||
else
|
||||
this.editor.hover(null);
|
||||
};
|
||||
ReactionUnmapTool.prototype.mouseup = function (event) {
|
||||
var ci = this.editor.findItem(event, ['atoms']);
|
||||
var atoms = this.editor.render.ctab.molecule.atoms;
|
||||
if (ci && ci.map === 'atoms' && atoms.get(ci.id).aam) {
|
||||
var action = new Action();
|
||||
var aam = atoms.get(ci.id).aam;
|
||||
atoms.each(
|
||||
function (aid, atom) {
|
||||
if (atom.aam == aam)
|
||||
action.mergeWith(Action.fromAtomsAttrs(this.editor.render.ctab, aid, { aam: 0 }));
|
||||
},
|
||||
this
|
||||
);
|
||||
this.editor.update(action);
|
||||
}
|
||||
this.editor.hover(null);
|
||||
};
|
||||
|
||||
module.exports = ReactionUnmapTool;
|
||||
69
static/js/ketcher2/script/editor/tool/rgroupatom.js
Normal file
69
static/js/ketcher2/script/editor/tool/rgroupatom.js
Normal file
@ -0,0 +1,69 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Struct = require('../../chem/struct');
|
||||
var Action = require('../action');
|
||||
|
||||
function RGroupAtomTool(editor) {
|
||||
if (!(this instanceof RGroupAtomTool)) {
|
||||
// TODO: map atoms with labels
|
||||
editor.selection(null);
|
||||
return new RGroupAtomTool(editor);
|
||||
}
|
||||
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
RGroupAtomTool.prototype.mousemove = function (event) {
|
||||
this.editor.hover(this.editor.findItem(event, ['atoms']));
|
||||
};
|
||||
|
||||
RGroupAtomTool.prototype.mouseup = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
var ci = this.editor.findItem(event, ['atoms']);
|
||||
if (!ci) { // ci.type == 'Canvas'
|
||||
this.editor.hover(null);
|
||||
propsDialog(this.editor, null, rnd.page2obj(event));
|
||||
return true;
|
||||
} else if (ci.map === 'atoms') {
|
||||
this.editor.hover(null);
|
||||
propsDialog(this.editor, ci.id);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
function propsDialog(editor, id, pos) {
|
||||
var struct = editor.render.ctab.molecule;
|
||||
var atom = (id || id === 0) ? struct.atoms.get(id) : null;
|
||||
var rglabel = atom ? atom.rglabel : 0;
|
||||
var label = atom ? atom.label : 'R#';
|
||||
|
||||
var res = editor.event.elementEdit.dispatch({
|
||||
label: 'R#', rglabel: rglabel
|
||||
});
|
||||
|
||||
Promise.resolve(res).then(function (elem) {
|
||||
elem = Object.assign({}, Struct.Atom.attrlist, elem); // TODO review: using Atom.attrlist as a source of default property values
|
||||
if (!id && id !== 0 && elem.rglabel) {
|
||||
editor.update(Action.fromAtomAddition(editor.render.ctab, pos, elem));
|
||||
} else if (rglabel != elem.rglabel || label !== 'R#') {
|
||||
elem.aam = atom.aam; // WTF??
|
||||
editor.update(Action.fromAtomsAttrs(editor.render.ctab, id, elem));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = RGroupAtomTool;
|
||||
71
static/js/ketcher2/script/editor/tool/rgroupfragment.js
Normal file
71
static/js/ketcher2/script/editor/tool/rgroupfragment.js
Normal file
@ -0,0 +1,71 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Struct = require('../../chem/struct');
|
||||
var Action = require('../action');
|
||||
|
||||
function RGroupFragmentTool(editor) {
|
||||
if (!(this instanceof RGroupFragmentTool)) {
|
||||
// TODO: check if it's a fragments already
|
||||
editor.selection(null);
|
||||
return new RGroupFragmentTool(editor);
|
||||
}
|
||||
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
RGroupFragmentTool.prototype.mousemove = function (event) {
|
||||
this.editor.hover(this.editor.findItem(event, ['frags', 'rgroups']));
|
||||
};
|
||||
|
||||
RGroupFragmentTool.prototype.mouseup = function (event) {
|
||||
const editor = this.editor;
|
||||
const struct = editor.render.ctab.molecule;
|
||||
const ci = editor.findItem(event, ['frags', 'rgroups']);
|
||||
|
||||
if (ci) {
|
||||
this.editor.hover(null);
|
||||
|
||||
const label = (ci.map === 'rgroups') ? ci.id :
|
||||
Struct.RGroup.findRGroupByFragment(struct.rgroups, ci.id) || null;
|
||||
|
||||
const rg = Object.assign({ label: label },
|
||||
ci.map === 'frags' ? null :
|
||||
struct.rgroups.get(ci.id));
|
||||
|
||||
const res = editor.event.rgroupEdit.dispatch(rg);
|
||||
|
||||
Promise.resolve(res).then(newRg => {
|
||||
const restruct = editor.render.ctab;
|
||||
|
||||
let action = null;
|
||||
if (ci.map !== 'rgroups') {
|
||||
const rgidOld = Struct.RGroup.findRGroupByFragment(restruct.molecule.rgroups, ci.id);
|
||||
|
||||
action = Action.fromRGroupFragment(restruct, newRg.label, ci.id)
|
||||
.mergeWith(Action.fromUpdateIfThen(restruct, newRg.label, rgidOld));
|
||||
} else {
|
||||
action = Action.fromRGroupAttrs(restruct, ci.id, newRg);
|
||||
}
|
||||
|
||||
editor.update(action);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = RGroupFragmentTool;
|
||||
154
static/js/ketcher2/script/editor/tool/rotate.js
Normal file
154
static/js/ketcher2/script/editor/tool/rotate.js
Normal file
@ -0,0 +1,154 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Vec2 = require('../../util/vec2');
|
||||
|
||||
var Action = require('../action');
|
||||
var utils = require('./utils');
|
||||
|
||||
function RotateTool(editor, dir) {
|
||||
if (!(this instanceof RotateTool)) {
|
||||
if (!dir)
|
||||
return new RotateTool(editor);
|
||||
|
||||
var restruct = editor.render.ctab;
|
||||
var selection = editor.selection();
|
||||
var singleBond = selection && selection.bonds &&
|
||||
Object.keys(selection).length === 1 &&
|
||||
selection.bonds.length == 1;
|
||||
|
||||
var action = !singleBond ? Action.fromFlip(restruct, selection, dir) :
|
||||
Action.fromBondAlign(restruct, selection.bonds[0], dir);
|
||||
editor.update(action);
|
||||
return null;
|
||||
}
|
||||
|
||||
this.editor = editor;
|
||||
|
||||
if (!editor.selection() || !editor.selection().atoms)
|
||||
// otherwise, clear selection
|
||||
this.editor.selection(null);
|
||||
}
|
||||
|
||||
RotateTool.prototype.mousedown = function (event) {
|
||||
var xy0 = new Vec2();
|
||||
var selection = this.editor.selection();
|
||||
var rnd = this.editor.render;
|
||||
var struct = rnd.ctab.molecule;
|
||||
|
||||
if (selection && selection.atoms) {
|
||||
console.assert(selection.atoms.length > 0);
|
||||
|
||||
var rotId = null;
|
||||
var rotAll = false;
|
||||
|
||||
selection.atoms.forEach(function (aid) {
|
||||
var atom = struct.atoms.get(aid);
|
||||
|
||||
xy0.add_(atom.pp); // eslint-disable-line no-underscore-dangle
|
||||
|
||||
if (rotAll)
|
||||
return;
|
||||
|
||||
atom.neighbors.find(function (nei) {
|
||||
var hb = struct.halfBonds.get(nei);
|
||||
|
||||
if (selection.atoms.indexOf(hb.end) === -1) {
|
||||
if (hb.loop >= 0) {
|
||||
var neiAtom = struct.atoms.get(aid);
|
||||
if (!neiAtom.neighbors.find(function (neiNei) {
|
||||
var neiHb = struct.halfBonds.get(neiNei);
|
||||
return neiHb.loop >= 0 && selection.atoms.indexOf(neiHb.end) !== -1;
|
||||
})) {
|
||||
rotAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (rotId == null) {
|
||||
rotId = aid;
|
||||
} else if (rotId != aid) {
|
||||
rotAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
if (!rotAll && rotId != null)
|
||||
xy0 = struct.atoms.get(rotId).pp;
|
||||
else
|
||||
xy0 = xy0.scaled(1 / selection.atoms.length);
|
||||
} else {
|
||||
struct.atoms.each(function (id, atom) {
|
||||
xy0.add_(atom.pp); // eslint-disable-line no-underscore-dangle
|
||||
});
|
||||
// poor man struct center (without chiral, sdata, etc)
|
||||
xy0 = xy0.scaled(1 / struct.atoms.count());
|
||||
}
|
||||
this.dragCtx = {
|
||||
xy0: xy0,
|
||||
angle1: utils.calcAngle(xy0, rnd.page2obj(event))
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
RotateTool.prototype.mousemove = function (event) { // eslint-disable-line max-statements
|
||||
if ('dragCtx' in this) {
|
||||
var rnd = this.editor.render;
|
||||
var dragCtx = this.dragCtx;
|
||||
|
||||
var pos = rnd.page2obj(event);
|
||||
var angle = utils.calcAngle(dragCtx.xy0, pos) - dragCtx.angle1;
|
||||
if (!event.ctrlKey)
|
||||
angle = utils.fracAngle(angle);
|
||||
|
||||
var degrees = utils.degrees(angle);
|
||||
|
||||
if ('angle' in dragCtx && dragCtx.angle == degrees) return true;
|
||||
if ('action' in dragCtx)
|
||||
dragCtx.action.perform(rnd.ctab);
|
||||
|
||||
dragCtx.angle = degrees;
|
||||
dragCtx.action = Action.fromRotate(rnd.ctab, this.editor.selection(), dragCtx.xy0, angle);
|
||||
|
||||
if (degrees > 180)
|
||||
degrees -= 360;
|
||||
else if (degrees <= -180)
|
||||
degrees += 360;
|
||||
this.editor.event.message.dispatch({ info: degrees + 'º' });
|
||||
|
||||
this.editor.update(dragCtx.action, true);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
RotateTool.prototype.mouseup = function () {
|
||||
if (this.dragCtx) {
|
||||
var action = this.dragCtx.action;
|
||||
delete this.dragCtx;
|
||||
if (action)
|
||||
this.editor.update(action);
|
||||
else
|
||||
this.editor.selection(null);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
RotateTool.prototype.cancel = RotateTool.prototype.mouseleave =
|
||||
RotateTool.prototype.mouseup;
|
||||
|
||||
module.exports = RotateTool;
|
||||
254
static/js/ketcher2/script/editor/tool/select.js
Normal file
254
static/js/ketcher2/script/editor/tool/select.js
Normal file
@ -0,0 +1,254 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Set = require('../../util/set');
|
||||
|
||||
var Action = require('../action');
|
||||
var Struct = require('../../chem/struct');
|
||||
|
||||
var LassoHelper = require('./helper/lasso');
|
||||
|
||||
var SGroup = require('./sgroup');
|
||||
var Atom = require('./atom');
|
||||
|
||||
function SelectTool(editor, mode) {
|
||||
if (!(this instanceof SelectTool))
|
||||
return new SelectTool(editor, mode);
|
||||
|
||||
this.editor = editor;
|
||||
|
||||
this.lassoHelper = new LassoHelper(mode === 'lasso' ? 0 : 1, editor, mode === 'fragment');
|
||||
}
|
||||
|
||||
SelectTool.prototype.mousedown = function (event) { // eslint-disable-line max-statements
|
||||
var rnd = this.editor.render;
|
||||
var ctab = rnd.ctab;
|
||||
var struct = ctab.molecule;
|
||||
this.editor.hover(null); // TODO review hovering for touch devicess
|
||||
var selectFragment = (this.lassoHelper.fragment || event.ctrlKey);
|
||||
var ci = this.editor.findItem(
|
||||
event,
|
||||
selectFragment ?
|
||||
['frags', 'sgroups', 'sgroupData', 'rgroups', 'rxnArrows', 'rxnPluses', 'chiralFlags'] :
|
||||
['atoms', 'bonds', 'sgroups', 'sgroupData', 'rgroups', 'rxnArrows', 'rxnPluses', 'chiralFlags']
|
||||
);
|
||||
this.dragCtx = {
|
||||
item: ci,
|
||||
xy0: rnd.page2obj(event)
|
||||
};
|
||||
if (!ci) { // ci.type == 'Canvas'
|
||||
Atom.atomLongtapEvent(this, rnd);
|
||||
delete this.dragCtx.item;
|
||||
|
||||
if (!this.lassoHelper.fragment)
|
||||
this.lassoHelper.begin(event);
|
||||
} else {
|
||||
this.editor.hover(null);
|
||||
if (!isSelected(rnd, this.editor.selection(), ci)) {
|
||||
var sel = closestToSel(ci);
|
||||
if (ci.map === 'frags') {
|
||||
var frag = ctab.frags.get(ci.id);
|
||||
sel = {
|
||||
atoms: frag.fragGetAtoms(rnd, ci.id),
|
||||
bonds: frag.fragGetBonds(rnd, ci.id)
|
||||
};
|
||||
} else if (ci.map === 'sgroups') {
|
||||
var sgroup = ctab.sgroups.get(ci.id).item;
|
||||
sel = {
|
||||
atoms: Struct.SGroup.getAtoms(struct, sgroup),
|
||||
bonds: Struct.SGroup.getBonds(struct, sgroup)
|
||||
};
|
||||
} else if (ci.map === 'rgroups') {
|
||||
var rgroup = ctab.rgroups.get(ci.id);
|
||||
sel = {
|
||||
atoms: rgroup.getAtoms(rnd),
|
||||
bonds: rgroup.getBonds(rnd)
|
||||
};
|
||||
}
|
||||
this.editor.selection(!event.shiftKey ? sel :
|
||||
selMerge(sel, this.editor.selection()));
|
||||
}
|
||||
if (ci.map === 'atoms')
|
||||
// this event has to be stopped in others events by `tool.dragCtx.stopTapping()`
|
||||
Atom.atomLongtapEvent(this, rnd);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
SelectTool.prototype.mousemove = function (event) {
|
||||
var rnd = this.editor.render;
|
||||
if (this.dragCtx && this.dragCtx.stopTapping)
|
||||
this.dragCtx.stopTapping();
|
||||
|
||||
if (this.dragCtx && this.dragCtx.item) {
|
||||
// moving selected objects
|
||||
if (this.dragCtx.action) {
|
||||
this.dragCtx.action.perform(rnd.ctab);
|
||||
this.editor.update(this.dragCtx.action, true); // redraw the elements in unshifted position, lest the have different offset
|
||||
}
|
||||
this.dragCtx.action = Action.fromMultipleMove(
|
||||
rnd.ctab,
|
||||
this.editor.explicitSelected(),
|
||||
rnd.page2obj(event).sub(this.dragCtx.xy0));
|
||||
// finding & highlighting object to stick to
|
||||
if (['atoms'/* , 'bonds'*/].indexOf(this.dragCtx.item.map) >= 0) {
|
||||
// TODO add bond-to-bond fusing
|
||||
var ci = this.editor.findItem(event, [this.dragCtx.item.map], this.dragCtx.item);
|
||||
this.editor.hover((ci && ci.map == this.dragCtx.item.map) ? ci : null);
|
||||
}
|
||||
this.editor.update(this.dragCtx.action, true);
|
||||
} else if (this.lassoHelper.running()) {
|
||||
var sel = this.lassoHelper.addPoint(event);
|
||||
this.editor.selection(!event.shiftKey ? sel :
|
||||
selMerge(sel, this.editor.selection()));
|
||||
} else {
|
||||
this.editor.hover(
|
||||
this.editor.findItem(event,
|
||||
(this.lassoHelper.fragment || event.ctrlKey) ?
|
||||
['frags', 'sgroups', 'sgroupData', 'rgroups', 'rxnArrows', 'rxnPluses', 'chiralFlags'] :
|
||||
['atoms', 'bonds', 'sgroups', 'sgroupData', 'rgroups', 'rxnArrows', 'rxnPluses', 'chiralFlags']
|
||||
)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
SelectTool.prototype.mouseup = function (event) { // eslint-disable-line max-statements
|
||||
if (this.dragCtx && this.dragCtx.stopTapping)
|
||||
this.dragCtx.stopTapping();
|
||||
|
||||
if (this.dragCtx && this.dragCtx.item) {
|
||||
if (['atoms'/* , 'bonds'*/].indexOf(this.dragCtx.item.map) >= 0) {
|
||||
// TODO add bond-to-bond fusing
|
||||
var ci = this.editor.findItem(event, [this.dragCtx.item.map], this.dragCtx.item);
|
||||
if (ci && ci.map == this.dragCtx.item.map) {
|
||||
var restruct = this.editor.render.ctab;
|
||||
this.editor.hover(null);
|
||||
this.editor.selection(null);
|
||||
this.dragCtx.action = this.dragCtx.action ?
|
||||
Action.fromAtomMerge(restruct, this.dragCtx.item.id, ci.id).mergeWith(this.dragCtx.action) :
|
||||
Action.fromAtomMerge(restruct, this.dragCtx.item.id, ci.id);
|
||||
}
|
||||
}
|
||||
if (this.dragCtx.action)
|
||||
this.editor.update(this.dragCtx.action);
|
||||
delete this.dragCtx;
|
||||
} else if (this.lassoHelper.running()) { // TODO it catches more events than needed, to be re-factored
|
||||
var sel = this.lassoHelper.end();
|
||||
this.editor.selection(!event.shiftKey ? sel :
|
||||
selMerge(sel, this.editor.selection()));
|
||||
} else if (this.lassoHelper.fragment) {
|
||||
this.editor.selection(null);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
SelectTool.prototype.dblclick = function (event) { // eslint-disable-line max-statements
|
||||
var editor = this.editor;
|
||||
var rnd = this.editor.render;
|
||||
var ci = this.editor.findItem(event, ['atoms', 'bonds', 'sgroups', 'sgroupData']);
|
||||
if (!ci) return;
|
||||
|
||||
var struct = rnd.ctab.molecule;
|
||||
if (ci.map === 'atoms') {
|
||||
this.editor.selection(closestToSel(ci));
|
||||
var atom = struct.atoms.get(ci.id);
|
||||
var ra = editor.event.elementEdit.dispatch(atom);
|
||||
Promise.resolve(ra).then(function (newatom) {
|
||||
// TODO: deep compare to not produce dummy, e.g.
|
||||
// atom.label != attrs.label || !atom.atomList.equals(attrs.atomList)
|
||||
editor.update(Action.fromAtomsAttrs(rnd.ctab, ci.id, newatom));
|
||||
});
|
||||
} else if (ci.map === 'bonds') {
|
||||
this.editor.selection(closestToSel(ci));
|
||||
var bond = rnd.ctab.bonds.get(ci.id).b;
|
||||
var rb = editor.event.bondEdit.dispatch(bond);
|
||||
Promise.resolve(rb).then(function (newbond) {
|
||||
editor.update(Action.fromBondAttrs(rnd.ctab, ci.id, newbond));
|
||||
});
|
||||
} else if (ci.map === 'sgroups' || ci.map === 'sgroupData') {
|
||||
this.editor.selection(closestToSel(ci));
|
||||
SGroup.dialog(this.editor, ci.id);
|
||||
// } else if (ci.map == 'sgroupData') {
|
||||
// SGroup.dialog(this.editor, ci.sgid);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
SelectTool.prototype.cancel = SelectTool.prototype.mouseleave = function () {
|
||||
if (this.dragCtx && this.dragCtx.stopTapping)
|
||||
this.dragCtx.stopTapping();
|
||||
|
||||
if (this.dragCtx && this.dragCtx.action) {
|
||||
var action = this.dragCtx.action;
|
||||
this.editor.update(action);
|
||||
}
|
||||
if (this.lassoHelper.running())
|
||||
this.editor.selection(this.lassoHelper.end());
|
||||
|
||||
delete this.dragCtx;
|
||||
|
||||
this.editor.hover(null);
|
||||
};
|
||||
|
||||
function closestToSel(ci) {
|
||||
var res = {};
|
||||
res[ci.map] = [ci.id];
|
||||
return res;
|
||||
}
|
||||
|
||||
// TODO: deep-merge?
|
||||
function selMerge(selection, add) {
|
||||
if (add) {
|
||||
for (var item in add) {
|
||||
if (add.hasOwnProperty(item)) {
|
||||
if (!selection[item]) {
|
||||
selection[item] = add[item].slice();
|
||||
} else {
|
||||
selection[item] = uniqArray(selection[item],
|
||||
add[item]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
|
||||
function uniqArray(dest, add) {
|
||||
for (var i = 0; i < add.length; i++) {
|
||||
if (dest.indexOf(add[i]) < 0)
|
||||
dest.push(add[i]);
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
function isSelected(render, selection, item) {
|
||||
if (!selection)
|
||||
return false;
|
||||
var ctab = render.ctab;
|
||||
if (item.map === 'frags' || item.map === 'rgroups') {
|
||||
var atoms = item.map === 'frags' ?
|
||||
ctab.frags.get(item.id).fragGetAtoms(render, item.id) :
|
||||
ctab.rgroups.get(item.id).getAtoms(render);
|
||||
|
||||
return !!selection['atoms'] &&
|
||||
Set.subset(Set.fromList(atoms), Set.fromList(selection['atoms']));
|
||||
}
|
||||
|
||||
return !!selection[item.map] &&
|
||||
selection[item.map].indexOf(item.id) > -1;
|
||||
}
|
||||
|
||||
module.exports = SelectTool;
|
||||
346
static/js/ketcher2/script/editor/tool/sgroup.js
Normal file
346
static/js/ketcher2/script/editor/tool/sgroup.js
Normal file
@ -0,0 +1,346 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
const isEqual = require('lodash/fp/isEqual');
|
||||
const uniq = require('lodash/fp/uniq');
|
||||
const LassoHelper = require('./helper/lasso');
|
||||
const Action = require('../action');
|
||||
const Struct = require('../../chem/struct');
|
||||
const Set = require('../../util/set');
|
||||
const Contexts = require('../../util/constants').SgContexts;
|
||||
|
||||
const searchMaps = ['atoms', 'bonds', 'sgroups', 'sgroupData'];
|
||||
|
||||
function SGroupTool(editor, type) {
|
||||
if (!(this instanceof SGroupTool)) {
|
||||
var selection = editor.selection() || {};
|
||||
if (!selection.atoms && !selection.bonds)
|
||||
return new SGroupTool(editor, type);
|
||||
|
||||
var sgroups = editor.render.ctab.molecule.sgroups;
|
||||
var selectedAtoms = editor.selection().atoms;
|
||||
|
||||
var id = sgroups.find(function (_, sgroup) {
|
||||
return isEqual(sgroup.atoms, selectedAtoms);
|
||||
});
|
||||
|
||||
propsDialog(editor, id !== undefined ? id : null, type);
|
||||
editor.selection(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
this.editor = editor;
|
||||
this.type = type;
|
||||
|
||||
this.lassoHelper = new LassoHelper(1, editor);
|
||||
this.editor.selection(null);
|
||||
}
|
||||
|
||||
SGroupTool.prototype.mousedown = function (event) {
|
||||
var ci = this.editor.findItem(event, searchMaps);
|
||||
if (!ci) // ci.type == 'Canvas'
|
||||
this.lassoHelper.begin(event);
|
||||
};
|
||||
|
||||
SGroupTool.prototype.mousemove = function (event) {
|
||||
if (this.lassoHelper.running(event))
|
||||
this.editor.selection(this.lassoHelper.addPoint(event));
|
||||
else
|
||||
this.editor.hover(this.editor.findItem(event, searchMaps));
|
||||
};
|
||||
|
||||
SGroupTool.prototype.mouseleave = function (event) {
|
||||
if (this.lassoHelper.running(event))
|
||||
this.lassoHelper.end(event);
|
||||
};
|
||||
|
||||
SGroupTool.prototype.mouseup = function (event) {
|
||||
var id = null; // id of an existing group, if we're editing one
|
||||
var selection = null; // atoms to include in a newly created group
|
||||
if (this.lassoHelper.running(event)) { // TODO it catches more events than needed, to be re-factored
|
||||
selection = this.lassoHelper.end(event);
|
||||
} else {
|
||||
var ci = this.editor.findItem(event, searchMaps);
|
||||
if (!ci) // ci.type == 'Canvas'
|
||||
return;
|
||||
this.editor.hover(null);
|
||||
|
||||
if (ci.map === 'atoms') {
|
||||
// if we click the SGroup tool on a single atom or bond, make a group out of those
|
||||
selection = { atoms: [ci.id] };
|
||||
} else if (ci.map === 'bonds') {
|
||||
var bond = this.editor.render.ctab.bonds.get(ci.id);
|
||||
selection = { atoms: [bond.b.begin, bond.b.end] };
|
||||
} else if (ci.map === 'sgroups' || ci.map === 'sgroupData') {
|
||||
id = ci.id;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle click on an existing group?
|
||||
if (id !== null || (selection && selection.atoms))
|
||||
propsDialog(this.editor, id, this.type);
|
||||
};
|
||||
|
||||
function propsDialog(editor, id, defaultType) {
|
||||
const restruct = editor.render.ctab;
|
||||
const struct = restruct.molecule;
|
||||
const selection = editor.selection() || {};
|
||||
const sg = id !== null ? struct.sgroups.get(id) : null;
|
||||
const type = sg ? sg.type : defaultType;
|
||||
const eventName = type === 'DAT' ? 'sdataEdit' : 'sgroupEdit';
|
||||
|
||||
if (!selection.atoms && !selection.bonds && !sg) {
|
||||
console.info('There is no selection or sgroup');
|
||||
return;
|
||||
}
|
||||
|
||||
let attrs = null;
|
||||
if (sg) {
|
||||
attrs = sg.getAttrs();
|
||||
attrs.context = getContextBySgroup(restruct, sg.atoms);
|
||||
} else {
|
||||
attrs = {
|
||||
context: getContextBySelection(restruct, selection)
|
||||
};
|
||||
}
|
||||
|
||||
const res = editor.event[eventName].dispatch({
|
||||
type: type,
|
||||
attrs: attrs
|
||||
});
|
||||
|
||||
Promise.resolve(res).then(newSg => {
|
||||
// TODO: check before signal
|
||||
if (newSg.type !== 'DAT' && // when data s-group separates
|
||||
checkOverlapping(struct, selection.atoms || [])) {
|
||||
editor.event.message.dispatch({
|
||||
error: 'Partial S-group overlapping is not allowed.'
|
||||
});
|
||||
} else {
|
||||
if (!sg && newSg.type !== 'DAT' && (!selection.atoms || selection.atoms.length === 0))
|
||||
return;
|
||||
|
||||
const isDataSg = sg && sg.getAttrs().context === newSg.attrs.context;
|
||||
|
||||
if (isDataSg) {
|
||||
const action = Action.fromSeveralSgroupAddition(restruct, newSg.type, sg.atoms, newSg.attrs)
|
||||
.mergeWith(Action.fromSgroupDeletion(restruct, id));
|
||||
|
||||
editor.update(action);
|
||||
editor.selection(selection);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = fromContextType(id, editor, newSg, selection);
|
||||
editor.update(result.action);
|
||||
editor.selection(result.selection);
|
||||
}
|
||||
}).catch(result => {
|
||||
console.info('rejected', result);
|
||||
});
|
||||
}
|
||||
|
||||
function getContextBySgroup(restruct, sgAtoms) {
|
||||
const struct = restruct.molecule;
|
||||
|
||||
if (sgAtoms.length === 1)
|
||||
return Contexts.Atom;
|
||||
|
||||
if (manyComponentsSelected(restruct, sgAtoms))
|
||||
return Contexts.Multifragment;
|
||||
|
||||
if (singleComponentSelected(restruct, sgAtoms))
|
||||
return Contexts.Fragment;
|
||||
|
||||
const atomMap = sgAtoms.reduce((acc, aid) => {
|
||||
acc[aid] = true;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const sgBonds = struct.bonds
|
||||
.values()
|
||||
.filter(bond => atomMap[bond.begin] && atomMap[bond.end]);
|
||||
|
||||
return anyChainedBonds(sgBonds) ? Contexts.Group : Contexts.Bond;
|
||||
}
|
||||
|
||||
function getContextBySelection(restruct, selection) {
|
||||
const struct = restruct.molecule;
|
||||
|
||||
if (selection.atoms && !selection.bonds)
|
||||
return Contexts.Atom;
|
||||
|
||||
const bonds = selection.bonds.map(bondid => struct.bonds.get(bondid));
|
||||
|
||||
if (!anyChainedBonds(bonds))
|
||||
return Contexts.Bond;
|
||||
|
||||
selection.atoms = selection.atoms || [];
|
||||
|
||||
const atomSelectMap = atomMap(selection.atoms);
|
||||
|
||||
const allBondsSelected = bonds.every(bond =>
|
||||
atomSelectMap[bond.begin] !== undefined && atomSelectMap[bond.end] !== undefined
|
||||
);
|
||||
|
||||
if (singleComponentSelected(restruct, selection.atoms) && allBondsSelected)
|
||||
return Contexts.Fragment;
|
||||
|
||||
return manyComponentsSelected(restruct, selection.atoms) ? Contexts.Multifragment : Contexts.Group;
|
||||
}
|
||||
|
||||
function fromContextType(id, editor, newSg, currSelection) {
|
||||
const restruct = editor.render.ctab;
|
||||
const sg = restruct.molecule.sgroups.get(id);
|
||||
const sourceAtoms = (sg && sg.atoms) || currSelection.atoms || [];
|
||||
const context = newSg.attrs.context;
|
||||
|
||||
const result = getActionForContext(context, restruct, newSg, sourceAtoms, currSelection);
|
||||
|
||||
result.selection = result.selection || currSelection;
|
||||
|
||||
if (id !== null && id !== undefined)
|
||||
result.action = result.action.mergeWith(Action.fromSgroupDeletion(restruct, id));
|
||||
|
||||
editor.selection(result.selection);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getActionForContext(context, restruct, newSg, sourceAtoms, selection) {
|
||||
if (context === Contexts.Bond)
|
||||
return Action.fromBondAction(restruct, newSg, sourceAtoms, selection);
|
||||
|
||||
const atomsFromBonds = getAtomsFromBonds(restruct.molecule, selection.bonds);
|
||||
const newSourceAtoms = uniq(sourceAtoms.concat(atomsFromBonds));
|
||||
|
||||
if (context === Contexts.Fragment)
|
||||
return Action.fromGroupAction(restruct, newSg, newSourceAtoms, restruct.atoms.keys());
|
||||
|
||||
if (context === Contexts.Multifragment)
|
||||
return Action.fromMultiFragmentAction(restruct, newSg, newSourceAtoms);
|
||||
|
||||
if (context === Contexts.Group)
|
||||
return Action.fromGroupAction(restruct, newSg, newSourceAtoms, newSourceAtoms);
|
||||
|
||||
if (context === Contexts.Atom)
|
||||
return Action.fromAtomAction(restruct, newSg, newSourceAtoms);
|
||||
|
||||
return {
|
||||
action: Action.fromSeveralSgroupAddition(restruct, newSg.type, sourceAtoms, newSg.attrs)
|
||||
};
|
||||
}
|
||||
|
||||
// tools
|
||||
function atomMap(atoms) {
|
||||
atoms = atoms || [];
|
||||
|
||||
return atoms.reduce((acc, atomid) => {
|
||||
acc[atomid] = atomid;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function anyChainedBonds(bonds) {
|
||||
if (bonds.length === 0)
|
||||
return true;
|
||||
|
||||
for (let i = 0; i < bonds.length; ++i) {
|
||||
const fixedBond = bonds[i];
|
||||
for (let j = 0; j < bonds.length; ++j) {
|
||||
if (i === j)
|
||||
continue;
|
||||
|
||||
const bond = bonds[j];
|
||||
|
||||
if (fixedBond.end === bond.begin || fixedBond.end === bond.end)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function singleComponentSelected(restruct, atoms) {
|
||||
return countOfSelectedComponents(restruct, atoms) === 1;
|
||||
}
|
||||
|
||||
function manyComponentsSelected(restruct, atoms) {
|
||||
return countOfSelectedComponents(restruct, atoms) > 1;
|
||||
}
|
||||
|
||||
function countOfSelectedComponents(restruct, atoms) {
|
||||
const atomSelectMap = atomMap(atoms);
|
||||
|
||||
return restruct.connectedComponents.values()
|
||||
.reduce((acc, component) => {
|
||||
const componentAtoms = Object.keys(component);
|
||||
|
||||
const count = componentAtoms
|
||||
.reduce((acc, atom) => acc + (atomSelectMap[atom] === undefined), 0);
|
||||
|
||||
return acc + (count === 0 ? 1 : 0);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function getAtomsFromBonds(struct, bonds) {
|
||||
bonds = bonds || [];
|
||||
return bonds.reduce((acc, bondid) => {
|
||||
const bond = struct.bonds.get(bondid);
|
||||
acc = acc.concat([bond.begin, bond.end]);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
|
||||
function checkOverlapping(struct, atoms) {
|
||||
var verified = {};
|
||||
var atomsHash = {};
|
||||
|
||||
atoms.forEach(function (id) {
|
||||
atomsHash[id] = true;
|
||||
});
|
||||
|
||||
return 0 <= atoms.findIndex(function (id) {
|
||||
var atom = struct.atoms.get(id);
|
||||
var sgroups = Set.list(atom.sgs);
|
||||
|
||||
return 0 <= sgroups.findIndex(function (sid) {
|
||||
var sg = struct.sgroups.get(sid);
|
||||
if (sg.type === 'DAT' || sid in verified)
|
||||
return false;
|
||||
|
||||
var sgAtoms = Struct.SGroup.getAtoms(struct, sg);
|
||||
|
||||
if (sgAtoms.length < atoms.length) {
|
||||
var ind = sgAtoms.findIndex(function (aid) {
|
||||
return !(aid in atomsHash);
|
||||
});
|
||||
if (0 <= ind) return true;
|
||||
}
|
||||
|
||||
return 0 <= atoms.findIndex(function (aid) {
|
||||
return (sgAtoms.indexOf(aid) === -1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = Object.assign(SGroupTool, {
|
||||
dialog: propsDialog
|
||||
});
|
||||
258
static/js/ketcher2/script/editor/tool/template.js
Normal file
258
static/js/ketcher2/script/editor/tool/template.js
Normal file
@ -0,0 +1,258 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Set = require('../../util/set');
|
||||
var Vec2 = require('../../util/vec2');
|
||||
var Action = require('../action');
|
||||
var utils = require('./utils');
|
||||
|
||||
function TemplateTool(editor, tmpl) {
|
||||
if (!(this instanceof TemplateTool))
|
||||
return new TemplateTool(editor, tmpl);
|
||||
|
||||
this.editor = editor;
|
||||
this.editor.selection(null);
|
||||
this.template = {
|
||||
aid: parseInt(tmpl.aid) || 0,
|
||||
bid: parseInt(tmpl.bid) || 0
|
||||
};
|
||||
|
||||
var frag = tmpl.struct;
|
||||
frag.rescale();
|
||||
|
||||
var xy0 = new Vec2();
|
||||
frag.atoms.each(function (aid, atom) {
|
||||
xy0.add_(atom.pp); // eslint-disable-line no-underscore-dangle
|
||||
});
|
||||
|
||||
this.template.molecule = frag; // preloaded struct
|
||||
this.findItems = [];
|
||||
this.template.xy0 = xy0.scaled(1 / (frag.atoms.count() || 1)); // template center
|
||||
|
||||
var atom = frag.atoms.get(this.template.aid);
|
||||
if (atom) {
|
||||
this.template.angle0 = utils.calcAngle(atom.pp, this.template.xy0); // center tilt
|
||||
this.findItems.push('atoms');
|
||||
}
|
||||
|
||||
var bond = frag.bonds.get(this.template.bid);
|
||||
if (bond) {
|
||||
this.template.sign = getSign(frag, bond, this.template.xy0); // template location sign against attachment bond
|
||||
this.findItems.push('bonds');
|
||||
}
|
||||
}
|
||||
|
||||
TemplateTool.prototype.mousedown = function (event) { // eslint-disable-line max-statements
|
||||
var editor = this.editor;
|
||||
var rnd = editor.render;
|
||||
this.editor.hover(null);
|
||||
this.dragCtx = {
|
||||
xy0: rnd.page2obj(event),
|
||||
item: editor.findItem(event, this.findItems)
|
||||
};
|
||||
var dragCtx = this.dragCtx;
|
||||
var ci = dragCtx.item;
|
||||
if (!ci) { // ci.type == 'Canvas'
|
||||
delete dragCtx.item;
|
||||
} else if (ci.map === 'bonds') {
|
||||
// calculate fragment center
|
||||
var molecule = rnd.ctab.molecule;
|
||||
var xy0 = new Vec2();
|
||||
var bond = molecule.bonds.get(ci.id);
|
||||
var frid = molecule.atoms.get(bond.begin).fragment;
|
||||
var frIds = molecule.getFragmentIds(frid);
|
||||
var count = 0;
|
||||
|
||||
var loop = molecule.halfBonds.get(bond.hb1).loop;
|
||||
|
||||
if (loop < 0)
|
||||
loop = molecule.halfBonds.get(bond.hb2).loop;
|
||||
|
||||
if (loop >= 0) {
|
||||
var loopHbs = molecule.loops.get(loop).hbs;
|
||||
loopHbs.forEach(function (hb) {
|
||||
xy0.add_(molecule.atoms.get(molecule.halfBonds.get(hb).begin).pp); // eslint-disable-line no-underscore-dangle
|
||||
count++;
|
||||
});
|
||||
} else {
|
||||
Set.each(frIds, function (id) {
|
||||
xy0.add_(molecule.atoms.get(id).pp); // eslint-disable-line no-underscore-dangle
|
||||
count++;
|
||||
});
|
||||
}
|
||||
|
||||
dragCtx.v0 = xy0.scaled(1 / count);
|
||||
|
||||
var sign = getSign(molecule, bond, dragCtx.v0);
|
||||
|
||||
// calculate default template flip
|
||||
dragCtx.sign1 = sign || 1;
|
||||
dragCtx.sign2 = this.template.sign;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
TemplateTool.prototype.mousemove = function (event) { // eslint-disable-line max-statements
|
||||
var editor = this.editor;
|
||||
var rnd = editor.render;
|
||||
if (this.dragCtx) {
|
||||
var dragCtx = this.dragCtx;
|
||||
var ci = dragCtx.item;
|
||||
var pos0;
|
||||
var pos1 = rnd.page2obj(event);
|
||||
var angle;
|
||||
var extraBond;
|
||||
|
||||
dragCtx.mouse_moved = true;
|
||||
|
||||
var struct = rnd.ctab.molecule;
|
||||
// calc initial pos and is extra bond needed
|
||||
if (!ci) { // ci.type == 'Canvas'
|
||||
pos0 = dragCtx.xy0;
|
||||
} else if (ci.map === 'atoms') {
|
||||
pos0 = struct.atoms.get(ci.id).pp;
|
||||
extraBond = Vec2.dist(pos0, pos1) > 1;
|
||||
} else if (ci.map === 'bonds') {
|
||||
var bond = struct.bonds.get(ci.id);
|
||||
var sign = getSign(struct, bond, pos1);
|
||||
|
||||
if (dragCtx.sign1 * this.template.sign > 0)
|
||||
sign = -sign;
|
||||
if (sign != dragCtx.sign2 || !dragCtx.action) {
|
||||
// undo previous action
|
||||
if ('action' in dragCtx)
|
||||
dragCtx.action.perform(rnd.ctab);
|
||||
dragCtx.sign2 = sign;
|
||||
dragCtx.action = Action.fromTemplateOnBond(rnd.ctab, ci.id, this.template, dragCtx.sign1 * dragCtx.sign2 > 0);
|
||||
this.editor.update(dragCtx.action, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
angle = utils.calcAngle(pos0, pos1);
|
||||
if (!event.ctrlKey)
|
||||
angle = utils.fracAngle(angle);
|
||||
|
||||
var degrees = utils.degrees(angle);
|
||||
// check if anything changed since last time
|
||||
if (dragCtx.hasOwnProperty('angle') && dragCtx.angle === degrees) {
|
||||
if (!dragCtx.hasOwnProperty('extra_bond') || dragCtx.extra_bond === extraBond)
|
||||
return true;
|
||||
}
|
||||
// undo previous action
|
||||
if (dragCtx.action)
|
||||
dragCtx.action.perform(rnd.ctab);
|
||||
// create new action
|
||||
dragCtx.angle = degrees;
|
||||
if (!ci) { // ci.type == 'Canvas'
|
||||
dragCtx.action = Action.fromTemplateOnCanvas(
|
||||
rnd.ctab,
|
||||
pos0,
|
||||
angle,
|
||||
this.template
|
||||
);
|
||||
} else if (ci.map === 'atoms') {
|
||||
dragCtx.action = Action.fromTemplateOnAtom(
|
||||
rnd.ctab,
|
||||
ci.id,
|
||||
angle,
|
||||
extraBond,
|
||||
this.template
|
||||
);
|
||||
dragCtx.extra_bond = extraBond;
|
||||
}
|
||||
this.editor.update(dragCtx.action, true);
|
||||
return true;
|
||||
}
|
||||
this.editor.hover(this.editor.findItem(event, this.findItems));
|
||||
return true;
|
||||
};
|
||||
|
||||
TemplateTool.prototype.mouseup = function (event) { // eslint-disable-line max-statements
|
||||
var editor = this.editor;
|
||||
var rnd = editor.render;
|
||||
|
||||
if (this.dragCtx) {
|
||||
var dragCtx = this.dragCtx;
|
||||
var ci = dragCtx.item;
|
||||
var restruct = rnd.ctab;
|
||||
var struct = restruct.molecule;
|
||||
|
||||
if (!dragCtx.action) {
|
||||
if (!ci) { // ci.type == 'Canvas'
|
||||
dragCtx.action = Action.fromTemplateOnCanvas(rnd.ctab, dragCtx.xy0, 0, this.template);
|
||||
} else if (ci.map === 'atoms') {
|
||||
var degree = restruct.atoms.get(ci.id).a.neighbors.length;
|
||||
|
||||
if (degree > 1) { // common case
|
||||
dragCtx.action = Action.fromTemplateOnAtom(
|
||||
restruct,
|
||||
ci.id,
|
||||
null,
|
||||
true,
|
||||
this.template
|
||||
);
|
||||
} else if (degree == 1) { // on chain end
|
||||
var neiId = struct.halfBonds.get(struct.atoms.get(ci.id).neighbors[0]).end;
|
||||
var atom = struct.atoms.get(ci.id);
|
||||
var nei = struct.atoms.get(neiId);
|
||||
var angle = utils.calcAngle(nei.pp, atom.pp);
|
||||
|
||||
dragCtx.action = Action.fromTemplateOnAtom(
|
||||
restruct,
|
||||
ci.id,
|
||||
event.ctrlKey ? angle : utils.fracAngle(angle),
|
||||
false,
|
||||
this.template
|
||||
);
|
||||
} else { // on single atom
|
||||
dragCtx.action = Action.fromTemplateOnAtom(
|
||||
restruct,
|
||||
ci.id,
|
||||
0,
|
||||
false,
|
||||
this.template
|
||||
);
|
||||
}
|
||||
} else if (ci.map === 'bonds') {
|
||||
dragCtx.action = Action.fromTemplateOnBond(restruct, ci.id, this.template, dragCtx.sign1 * dragCtx.sign2 > 0);
|
||||
}
|
||||
|
||||
this.editor.update(dragCtx.action, true);
|
||||
}
|
||||
var action = this.dragCtx.action;
|
||||
delete this.dragCtx;
|
||||
|
||||
if (action && !action.isDummy())
|
||||
this.editor.update(action);
|
||||
}
|
||||
};
|
||||
|
||||
TemplateTool.prototype.cancel = TemplateTool.prototype.mouseleave =
|
||||
TemplateTool.prototype.mouseup;
|
||||
|
||||
function getSign(molecule, bond, v) {
|
||||
var begin = molecule.atoms.get(bond.begin).pp;
|
||||
var end = molecule.atoms.get(bond.end).pp;
|
||||
|
||||
var sign = Vec2.cross(Vec2.diff(begin, end), Vec2.diff(v, end));
|
||||
|
||||
if (sign > 0) return 1;
|
||||
if (sign < 0) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
module.exports = TemplateTool;
|
||||
52
static/js/ketcher2/script/editor/tool/utils.js
Normal file
52
static/js/ketcher2/script/editor/tool/utils.js
Normal file
@ -0,0 +1,52 @@
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
***************************************************************************/
|
||||
|
||||
var Vec2 = require('../../util/vec2');
|
||||
|
||||
var FRAC = Math.PI / 12; // '15º'
|
||||
|
||||
function setFracAngle(angle) {
|
||||
FRAC = Math.PI / 180 * angle;
|
||||
}
|
||||
|
||||
function calcAngle(pos0, pos1) {
|
||||
var v = Vec2.diff(pos1, pos0);
|
||||
return Math.atan2(v.y, v.x);
|
||||
}
|
||||
|
||||
function fracAngle(angle) {
|
||||
if (arguments.length > 1)
|
||||
angle = calcAngle(arguments[0], arguments[1]);
|
||||
return Math.round(angle / FRAC) * FRAC;
|
||||
}
|
||||
|
||||
function calcNewAtomPos(pos0, pos1) {
|
||||
var v = new Vec2(1, 0).rotate(fracAngle(pos0, pos1));
|
||||
v.add_(pos0); // eslint-disable-line no-underscore-dangle
|
||||
return v;
|
||||
}
|
||||
|
||||
function degrees(angle) {
|
||||
return Math.round(angle / Math.PI * 180);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
calcAngle: calcAngle,
|
||||
fracAngle: fracAngle,
|
||||
calcNewAtomPos: calcNewAtomPos,
|
||||
degrees: degrees,
|
||||
setFracAngle: setFracAngle
|
||||
};
|
||||
Reference in New Issue
Block a user