From 759a4c016cd6e9a9e5425747b73bc3fce5984972 Mon Sep 17 00:00:00 2001 From: Karen Sarkisyan Date: Tue, 15 Feb 2022 14:28:30 +0300 Subject: [PATCH 1/6] #1242 add highlighting (#1252) * Create new highlight entity in struct. In editor, replace previous highlight method with a setHover function * Create highlight operations and actions * Modify draw methods for atoms and bonds to display highlight * Rename highlight to hover across ketcher-core to prevent confusion * Add highlight update operation and update and append methods for highlighter * Make create method accept multiple highlights in one call, refactior rendering of highlight * Hide update and append methods and actions. Rename highlight to highlights, refactor getAll * Fix code styling --- .../application/editor/actions/highlight.ts | 96 ++++++++ .../src/application/editor/actions/index.ts | 1 + .../editor/operations/OperationType.ts | 5 +- .../editor/operations/highlight.ts | 224 ++++++++++++++++++ .../src/application/render/options.js | 4 +- .../render/restruct/generalEnumTypes.ts | 2 +- .../src/application/render/restruct/reatom.ts | 59 +++-- .../src/application/render/restruct/rebond.ts | 62 ++++- .../render/restruct/redatasgroupdata.js | 18 +- .../render/restruct/reenhancedFlag.ts | 18 +- .../src/application/render/restruct/refrag.js | 13 +- .../application/render/restruct/reobject.ts | 37 ++- .../application/render/restruct/rergroup.js | 9 +- .../application/render/restruct/rerxnarrow.ts | 8 +- .../application/render/restruct/rerxnplus.js | 18 +- .../application/render/restruct/resgroup.js | 27 ++- .../render/restruct/resimpleObject.ts | 13 +- .../application/render/restruct/restruct.ts | 2 +- .../src/application/render/restruct/retext.ts | 10 +- .../src/domain/entities/highlight.ts | 34 +++ .../ketcher-core/src/domain/entities/index.ts | 1 + .../src/domain/entities/sgroup.ts | 8 +- .../src/domain/entities/struct.ts | 3 + .../ketcher-react/src/script/editor/Editor.ts | 104 ++++---- .../src/script/editor/highlighter.ts | 216 +++++++++++++++++ .../ui/dialog/template/template-attach.jsx | 4 +- 26 files changed, 831 insertions(+), 165 deletions(-) create mode 100644 packages/ketcher-core/src/application/editor/actions/highlight.ts create mode 100644 packages/ketcher-core/src/application/editor/operations/highlight.ts create mode 100644 packages/ketcher-core/src/domain/entities/highlight.ts create mode 100644 packages/ketcher-react/src/script/editor/highlighter.ts diff --git a/packages/ketcher-core/src/application/editor/actions/highlight.ts b/packages/ketcher-core/src/application/editor/actions/highlight.ts new file mode 100644 index 0000000000..7f9d5c614c --- /dev/null +++ b/packages/ketcher-core/src/application/editor/actions/highlight.ts @@ -0,0 +1,96 @@ +/**************************************************************************** + * Copyright 2021 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +import { ReStruct } from '../../render' + +import { HighlightAdd, HighlightDelete } from '../operations/highlight' + +import { Action } from './action' + +type HighlightType = { + atoms: number[] + bonds: number[] + color: string +} + +export function fromHighlightCreate( + restruct: ReStruct, + highlights: HighlightType[] +): Action { + const action = new Action() + + highlights.forEach(highlight => { + const { atoms, bonds, color } = highlight + + action.addOp(new HighlightAdd(atoms, bonds, color)) + }) + return action.perform(restruct) +} + +export function fromHighlightClear(restruct: ReStruct): Action { + const action = new Action() + + const highlights = restruct.molecule.highlights + + highlights.forEach((_, key) => { + action.addOp(new HighlightDelete(key)) + }) + + return action.perform(restruct) +} + +/* +// Update highlight by placing new one on the given id +export function fromHighlightUpdate( + highlightId: number, + restruct: ReStruct, + atoms: number[], + bonds: number[], + color: string +): Action { + const action = new Action() + + const highlights = restruct.molecule.highlights + + const selectedHighlight = highlights.get(highlightId) + if (!selectedHighlight) { + return action + } + + const updateOperation = new HighlightUpdate(highlightId, atoms, bonds, color) + action.addOp(updateOperation) + + return action.perform(restruct) +} +*/ + +/* +// Delete single highlight by id +export function fromHighlightDelete( + restruct: ReStruct, + highlightId: number +): Action { + const action = new Action() + + const highlights = restruct.molecule.highlights + if (highlights.has(highlightId)) { + action.addOp(new HighlightDelete(highlightId)) + + return action.perform(restruct) + } + return action +} +*/ diff --git a/packages/ketcher-core/src/application/editor/actions/index.ts b/packages/ketcher-core/src/application/editor/actions/index.ts index 36a0b16ec7..d59659c3ba 100644 --- a/packages/ketcher-core/src/application/editor/actions/index.ts +++ b/packages/ketcher-core/src/application/editor/actions/index.ts @@ -16,3 +16,4 @@ export * from './simpleobject' export * from './template' export * from './text' export * from './utils' +export * from './highlight' diff --git a/packages/ketcher-core/src/application/editor/operations/OperationType.ts b/packages/ketcher-core/src/application/editor/operations/OperationType.ts index c920b96080..9270527299 100644 --- a/packages/ketcher-core/src/application/editor/operations/OperationType.ts +++ b/packages/ketcher-core/src/application/editor/operations/OperationType.ts @@ -60,5 +60,8 @@ export const OperationType = Object.freeze({ TEXT_CREATE: 'Add text', TEXT_UPDATE: 'Edit text', TEXT_DELETE: 'Delete text', - TEXT_MOVE: 'Move text' + TEXT_MOVE: 'Move text', + ADD_HIGHLIGHT: 'Highlight', + UPDATE_HIGHLIGHT: 'Update highlight', + REMOVE_HIGHLIGHT: 'Remove highlight' }) diff --git a/packages/ketcher-core/src/application/editor/operations/highlight.ts b/packages/ketcher-core/src/application/editor/operations/highlight.ts new file mode 100644 index 0000000000..791f72ab7c --- /dev/null +++ b/packages/ketcher-core/src/application/editor/operations/highlight.ts @@ -0,0 +1,224 @@ +/**************************************************************************** + * Copyright 2021 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +import { Highlight } from 'domain/entities' +import { ReStruct } from '../../render' + +import { BaseOperation } from './base' +import { OperationType } from './OperationType' + +type Data = { + atoms: Array + bonds: Array + color: string + highlightId?: number +} + +export class HighlightAdd extends BaseOperation { + data: Data + + constructor( + atoms: Array, + bonds: Array, + color: string, + highlightId?: number + ) { + super(OperationType.ADD_HIGHLIGHT) + this.data = { + atoms: atoms, + bonds: bonds, + color: color, + highlightId: highlightId + } + } + + execute(restruct: ReStruct) { + const { atoms, bonds, color } = this.data + + if (!color) { + return + } + + const struct = restruct.molecule + const highlight = new Highlight({ + atoms, + bonds, + color + }) + + if (typeof this.data.highlightId !== 'number') { + this.data.highlightId = struct.highlights.add(highlight) + } else { + struct.highlights.set(this.data.highlightId, highlight) + } + + notifyChanged(restruct, atoms, bonds) + } + + invert() { + const { atoms, bonds, color, highlightId } = this.data + const inverted = new HighlightDelete(highlightId, atoms, bonds, color) + return inverted + } +} + +export class HighlightDelete extends BaseOperation { + data: Data + + constructor( + highlightId?: number, + atoms?: Array, + bonds?: Array, + color?: string + ) { + super(OperationType.REMOVE_HIGHLIGHT, 5) + this.data = { + highlightId: highlightId, + atoms: atoms || [], + bonds: bonds || [], + color: color || 'white' + } + } + + execute(restruct: ReStruct) { + if (typeof this.data.highlightId === 'number') { + const struct = restruct.molecule + + const highlightToRemove = struct.highlights.get(this.data.highlightId) + if (typeof highlightToRemove === 'undefined') { + return + } + + const { atoms, bonds, color } = highlightToRemove + + this.data.atoms = atoms + this.data.bonds = bonds + this.data.color = color + + struct.highlights.delete(this.data.highlightId) + notifyChanged(restruct, atoms, bonds) + } + } + + invert() { + const { atoms, bonds, color, highlightId } = this.data + const inverted = new HighlightAdd(atoms, bonds, color, highlightId) + inverted.data = this.data + return inverted + } +} + +export class HighlightUpdate extends BaseOperation { + // making sure highlightId is not optional + newData: Data & { highlightId: number } + oldData: Data & { highlightId: number } + + constructor( + highlightId: number, + atoms: Array, + bonds: Array, + color: string + ) { + super(OperationType.UPDATE_HIGHLIGHT) + this.newData = { + atoms: atoms, + bonds: bonds, + color: color, + highlightId: highlightId + } + + // pre-filling with new data. Upon execution this will be replaced + this.oldData = { + atoms: atoms, + bonds: bonds, + color: color, + highlightId: highlightId + } + } + + execute(restruct: ReStruct) { + const { atoms, bonds, color } = this.newData + if (!color) { + return + } + + const highlightId = this.newData.highlightId + const struct = restruct.molecule + + const highlightToUpdate = struct.highlights.get(highlightId) + + if (highlightToUpdate) { + // saving data of existing highlight + const { + atoms: oldAtoms, + bonds: oldBonds, + color: oldColor + } = highlightToUpdate + this.oldData = { + atoms: oldAtoms, + bonds: oldBonds, + color: oldColor, + highlightId + } + + // creating new highlight with new data + const updatedHighlight = new Highlight({ + atoms, + bonds, + color + }) + + // setting the new highlight + struct.highlights.set(this.newData.highlightId, updatedHighlight) + + // notify atoms from both collections that repaint is needed + notifyChanged(restruct, [...atoms, ...oldAtoms], [...bonds, ...oldBonds]) + } + } + + invert() { + const { atoms, bonds, color } = this.oldData + const inverted = new HighlightUpdate( + this.newData.highlightId, + atoms, + bonds, + color + ) + return inverted + } +} + +function notifyChanged(restruct: ReStruct, atoms?: number[], bonds?: number[]) { + // Notifying ReStruct that repaint needed + const reAtoms = restruct.atoms + const reBonds = restruct.bonds + + if (atoms) { + atoms.forEach(atomId => { + if (typeof reAtoms.get(atomId) !== 'undefined') { + restruct.markAtom(atomId, 1) + } + }) + } + + if (bonds) { + bonds.forEach(bondId => { + if (typeof reBonds.get(bondId) !== 'undefined') { + restruct.markBond(bondId, 1) + } + }) + } +} diff --git a/packages/ketcher-core/src/application/render/options.js b/packages/ketcher-core/src/application/render/options.js index 3c27e300fd..899be3806d 100644 --- a/packages/ketcher-core/src/application/render/options.js +++ b/packages/ketcher-core/src/application/render/options.js @@ -74,7 +74,7 @@ function defaultOptions(opt) { fill: '#7f7', stroke: 'none' }, - highlightStyle: { + hoverStyle: { stroke: '#0c0', 'stroke-width': (0.6 * scaleFactor) / 20 }, @@ -86,7 +86,7 @@ function defaultOptions(opt) { stroke: 'gray', 'stroke-width': '1px' }, - highlightStyleSimpleObject: { + hoverStyleSimpleObject: { stroke: '#0c0', 'stroke-width': scaleFactor / 4, 'stroke-linecap': 'round', diff --git a/packages/ketcher-core/src/application/render/restruct/generalEnumTypes.ts b/packages/ketcher-core/src/application/render/restruct/generalEnumTypes.ts index 629a73933c..5d1dae37fe 100644 --- a/packages/ketcher-core/src/application/render/restruct/generalEnumTypes.ts +++ b/packages/ketcher-core/src/application/render/restruct/generalEnumTypes.ts @@ -17,7 +17,7 @@ export enum LayerMap { background = 'background', selectionPlate = 'selectionPlate', - highlighting = 'highlighting', + hovering = 'hovering', warnings = 'warnings', data = 'data', indices = 'indices' diff --git a/packages/ketcher-core/src/application/render/restruct/reatom.ts b/packages/ketcher-core/src/application/render/restruct/reatom.ts index 8766867db5..00d009e1aa 100644 --- a/packages/ketcher-core/src/application/render/restruct/reatom.ts +++ b/packages/ketcher-core/src/application/render/restruct/reatom.ts @@ -80,12 +80,14 @@ class ReAtom extends ReObject { return ReObject.prototype.getVBoxObj.call(this, render) return new Box2Abs(this.a.pp, this.a.pp) } - drawHighlight(render: Render) { - const ret = this.makeHighlightPlate(render) - render.ctab.addReObjectPath(LayerMap.highlighting, this.visel, ret) + + drawHover(render: Render) { + const ret = this.makeHoverPlate(render) + render.ctab.addReObjectPath(LayerMap.hovering, this.visel, ret) return ret } - makeHighlightPlate(render: Render) { + + makeHoverPlate(render: Render) { const paper = render.paper const options = render.options const ps = Scale.obj2scaled(this.a.pp, options) @@ -103,7 +105,7 @@ class ReAtom extends ReObject { return null return paper .circle(ps.x, ps.y, options.atomSelectionPlateRadius) - .attr(options.highlightStyle) + .attr(options.hoverStyle) } makeSelectionPlate(restruct: ReStruct, paper: any, styles: any) { const atom = this.a @@ -176,19 +178,18 @@ class ReAtom extends ReObject { implh = Math.floor(this.a.implicitH) isHydrogen = label.text === 'H' restruct.addReObjectPath(LayerMap.data, this.visel, label.path, ps, true) - if (options.showAtomIds) { - index = {} - index.text = aid.toString() - index.path = render.paper.text(ps.x, ps.y, index.text).attr({ - font: options.font, - 'font-size': options.fontszsub, - fill: '#070' - }) - index.rbb = util.relBox(index.path.getBBox()) - draw.recenterText(index.path, index.rbb) - restruct.addReObjectPath(LayerMap.indices, this.visel, index.path, ps) + this.setHover(this.hover, render) + } + if (options.showAtomIds) { + index = {} + index.text = aid.toString() + let idPos = this.hydrogenOnTheLeft + ? Vec2.lc(ps, 1, new Vec2({ x: -2, y: 0, z: 0 }), 6) + : Vec2.lc(ps, 1, new Vec2({ x: 2, y: 0, z: 0 }), 6) + if (this.showLabel) { + idPos = Vec2.lc(idPos, 1, new Vec2({ x: 1, y: -3, z: 0 }), 6) } - this.setHighlight(this.highlight, render) + this.setHover(this.hover, render) } if (this.showLabel && !this.a.pseudo) { @@ -359,6 +360,30 @@ class ReAtom extends ReObject { pathAndRBoxTranslate(aamPath, aamBox, dir.x, dir.y) restruct.addReObjectPath(LayerMap.data, this.visel, aamPath, ps, true) } + + // Checking whether atom is highlighted and what's the last color + const highlights = restruct.molecule.highlights + let isHighlighted = false + let highlightColor = '' + highlights.forEach(highlight => { + const hasCurrentHighlight = highlight.atoms?.includes(aid) + isHighlighted = isHighlighted || hasCurrentHighlight + if (hasCurrentHighlight) { + highlightColor = highlight.color + } + }) + + // Drawing highlight + if (isHighlighted) { + const style = { fill: highlightColor, stroke: 'none' } + + const ps = Scale.obj2scaled(this.a.pp, restruct.render.options) + const path = render.paper + .circle(ps.x, ps.y, options.atomSelectionPlateRadius * 0.8) + .attr(style) + + restruct.addReObjectPath(LayerMap.hovering, this.visel, path) + } } } diff --git a/packages/ketcher-core/src/application/render/restruct/rebond.ts b/packages/ketcher-core/src/application/render/restruct/rebond.ts index 39aa5264ff..ee3762e798 100644 --- a/packages/ketcher-core/src/application/render/restruct/rebond.ts +++ b/packages/ketcher-core/src/application/render/restruct/rebond.ts @@ -49,12 +49,14 @@ class ReBond extends ReObject { static isSelectable() { return true } - drawHighlight(render: Render) { - const ret = this.makeHighlightPlate(render) - render.ctab.addReObjectPath(LayerMap.highlighting, this.visel, ret) + + drawHover(render: Render) { + const ret = this.makeHoverPlate(render) + render.ctab.addReObjectPath(LayerMap.hovering, this.visel, ret) return ret } - makeHighlightPlate(render: Render) { + + makeHoverPlate(render: Render) { const options = render.options bondRecalc(this, render.ctab, options) const bond = this.b @@ -73,7 +75,7 @@ class ReBond extends ReObject { const c = Scale.obj2scaled(this.b.center, options) return render.paper .circle(c.x, c.y, 0.8 * options.atomSelectionPlateRadius) - .attr(options.highlightStyle) + .attr(options.hoverStyle) } makeSelectionPlate(restruct: ReStruct, paper: any, options: any) { bondRecalc(this, restruct, options) @@ -149,7 +151,7 @@ class ReBond extends ReObject { true ) } - this.setHighlight(this.highlight, render) + this.setHover(this.hover, render) let ipath = null const bondIdxOff = options.subFontSize * 0.6 @@ -205,9 +207,57 @@ class ReBond extends ReObject { ) restruct.addReObjectPath(LayerMap.indices, this.visel, ipath) } + + // Checking whether bond is highlighted and what is the last color + const highlights = restruct.molecule.highlights + let isHighlighted = false + let highlightColor = '' + highlights.forEach(highlight => { + const hasCurrentHighlight = highlight.bonds?.includes(bid) + isHighlighted = isHighlighted || hasCurrentHighlight + if (hasCurrentHighlight) { + highlightColor = highlight.color + } + }) + + // Drawing highlight + if (isHighlighted) { + const style = { + fill: highlightColor, + stroke: highlightColor, + 'stroke-width': options.lineattr['stroke-width'] * 7, + 'stroke-linecap': 'round' + } + + const c = Scale.obj2scaled(this.b.center, restruct.render.options) + + const highlightPath = getHighlightPath(restruct, hb1, hb2) + highlightPath.attr(style) + + restruct.addReObjectPath( + LayerMap.hovering, + this.visel, + highlightPath, + c, + true + ) + } } } +function getHighlightPath(restruct: ReStruct, hb1: HalfBond, hb2: HalfBond) { + const beginning = { x: hb1.p.x, y: hb1.p.y } + const end = { x: hb2.p.x, y: hb2.p.y } + + const paper = restruct.render.paper + + const pathString = `M${beginning.x},${beginning.y} L${end.x},${end.y}` + + const path = paper.path(pathString) + + return path +} + function findIncomingStereoUpBond( atom: Atom, bid0: number, diff --git a/packages/ketcher-core/src/application/render/restruct/redatasgroupdata.js b/packages/ketcher-core/src/application/render/restruct/redatasgroupdata.js index 1d816a787a..91dc38755e 100644 --- a/packages/ketcher-core/src/application/render/restruct/redatasgroupdata.js +++ b/packages/ketcher-core/src/application/render/restruct/redatasgroupdata.js @@ -26,20 +26,22 @@ class ReDataSGroupData extends ReObject { static isSelectable() { return true } - highlightPath(render) { - var box = this.sgroup.dataArea - var p0 = Scale.obj2scaled(box.p0, render.options) - var sz = Scale.obj2scaled(box.p1, render.options).sub(p0) + + hoverPath(render) { + const box = this.sgroup.dataArea + const p0 = Scale.obj2scaled(box.p0, render.options) + const sz = Scale.obj2scaled(box.p1, render.options).sub(p0) return render.paper.rect(p0.x, p0.y, sz.x, sz.y) } - drawHighlight(render) { - var ret = this.highlightPath(render).attr(render.options.highlightStyle) - render.ctab.addReObjectPath(LayerMap.highlighting, this.visel, ret) + + drawHover(render) { + const ret = this.hoverPath(render).attr(render.options.hoverStyle) + render.ctab.addReObjectPath(LayerMap.hovering, this.visel, ret) return ret } makeSelectionPlate(restruct, paper, styles) { // TODO [MK] review parameters - return this.highlightPath(restruct.render).attr(styles.selectionStyle) + return this.hoverPath(restruct.render).attr(styles.selectionStyle) } } diff --git a/packages/ketcher-core/src/application/render/restruct/reenhancedFlag.ts b/packages/ketcher-core/src/application/render/restruct/reenhancedFlag.ts index fa3438f163..41a3abc39e 100644 --- a/packages/ketcher-core/src/application/render/restruct/reenhancedFlag.ts +++ b/packages/ketcher-core/src/application/render/restruct/reenhancedFlag.ts @@ -32,24 +32,26 @@ class ReEnhancedFlag extends ReObject { static isSelectable() { return true } - highlightPath(render: Render): any { - var box = Box2Abs.fromRelBox(this.#path.getBBox()) - var sz = box.p1.sub(box.p0) - var p0 = box.p0.sub(render.options.offset) + + hoverPath(render: Render): any { + const box = Box2Abs.fromRelBox(this.#path.getBBox()) + const sz = box.p1.sub(box.p0) + const p0 = box.p0.sub(render.options.offset) return render.paper.rect(p0.x, p0.y, sz.x, sz.y) } - drawHighlight(render: Render): any { + + drawHover(render: Render): any { // TODO: after the enhanced flag stops being displayed, need to remove the reEnhancedflag object from ctab if (!this.#path?.attrs) return null - var ret = this.highlightPath(render).attr(render.options.highlightStyle) - render.ctab.addReObjectPath(LayerMap.highlighting, this.visel, ret) + const ret = this.hoverPath(render).attr(render.options.hoverStyle) + render.ctab.addReObjectPath(LayerMap.hovering, this.visel, ret) return ret } // @ts-ignore makeSelectionPlate(restruct: ReStruct, paper: any, options: any): any { // TODO: after the enhanced flag stops being displayed, need to remove the reEnhancedflag object from ctab if (!this.#path?.attrs) return null - return this.highlightPath(restruct.render).attr(options.selectionStyle) + return this.hoverPath(restruct.render).attr(options.selectionStyle) } show(restruct: ReStruct, fragmentId: number, options: any): void { diff --git a/packages/ketcher-core/src/application/render/restruct/refrag.js b/packages/ketcher-core/src/application/render/restruct/refrag.js index d960c90b75..a7078487ab 100644 --- a/packages/ketcher-core/src/application/render/restruct/refrag.js +++ b/packages/ketcher-core/src/application/render/restruct/refrag.js @@ -85,11 +85,13 @@ class ReFrag extends ReObject { // eslint-disable-line no-unused-vars return null // this._draw(render, fid, { 'stroke' : 'lightgray' }); // [RB] for debugging only } - drawHighlight(render) { + + drawHover(render) { // eslint-disable-line no-unused-vars // Do nothing. This method shouldn't actually be called. } - setHighlight(highLight, render) { + + setHover(hover, render) { let fid = render.ctab.frags.keyOf(this) if (!fid && fid !== 0) { @@ -100,12 +102,13 @@ class ReFrag extends ReObject { fid = parseInt(fid, 10) render.ctab.atoms.forEach(atom => { - if (atom.a.fragment === fid) atom.setHighlight(highLight, render) + if (atom.a.fragment === fid) atom.setHover(hover, render) }) render.ctab.bonds.forEach(bond => { - if (render.ctab.atoms.get(bond.b.begin).a.fragment === fid) - bond.setHighlight(highLight, render) + if (render.ctab.atoms.get(bond.b.begin).a.fragment === fid) { + bond.setHover(hover, render) + } }) } } diff --git a/packages/ketcher-core/src/application/render/restruct/reobject.ts b/packages/ketcher-core/src/application/render/restruct/reobject.ts index 7d65638e36..f993794ee1 100644 --- a/packages/ketcher-core/src/application/render/restruct/reobject.ts +++ b/packages/ketcher-core/src/application/render/restruct/reobject.ts @@ -22,9 +22,9 @@ import Visel from './visel' class ReObject { public visel: Visel - public highlight: boolean = false - public highlighting: any = null - public selected: boolean = false + public hover = false + public hovering: any = null + public selected = false public selectionPlate: any = null constructor(viselType: string) { @@ -39,35 +39,34 @@ class ReObject { return vbox.transform(Scale.scaled2obj, render.options) } - setHighlight(highLight: boolean, render: Render): void { + setHover(hover: boolean, render: Render): void { // TODO render should be field - if (highLight) { - let noredraw = 'highlighting' in this && this.highlighting !== null // && !this.highlighting.removed; + if (hover) { + let noredraw = 'hovering' in this && this.hovering !== null // && !this.highlighting.removed; if (noredraw) { - if (this.highlighting.type === 'set') { - if (!this.highlighting[0]) return - noredraw = !this.highlighting[0].removed + if (this.hovering.type === 'set') { + if (!this.hovering[0]) return + noredraw = !this.hovering[0].removed } else { - noredraw = !this.highlighting.removed + noredraw = !this.hovering.removed } } if (noredraw) { - this.highlighting.show() + this.hovering.show() } else { render.paper.setStart() - this.drawHighlight(render) - this.highlighting = render.paper.setFinish() + this.drawHover(render) + this.hovering = render.paper.setFinish() } - } else if (this.highlighting) { - this.highlighting.hide() + } else if (this.hovering) { + this.hovering.hide() } - this.highlight = highLight + this.hover = hover } - // @ts-ignore - drawHighlight(render: Render): any { - throw new Error('ReObject.drawHighlight is not overridden.') + drawHover(_render: Render): any { + throw new Error('ReObject.drawHover is not overridden.') } // @ts-ignore diff --git a/packages/ketcher-core/src/application/render/restruct/rergroup.js b/packages/ketcher-core/src/application/render/restruct/rergroup.js index bf32a526b0..574ba640e1 100644 --- a/packages/ketcher-core/src/application/render/restruct/rergroup.js +++ b/packages/ketcher-core/src/application/render/restruct/rergroup.js @@ -137,7 +137,8 @@ class ReRGroup extends ReObject { .rect(p0.x, p0.y, p1.x - p0.x, p1.y - p0.y, 0) .attr(attrs) } - drawHighlight(render) { + + drawHover(render) { const rgid = render.ctab.rgroups.keyOf(this) if (!rgid) { @@ -150,12 +151,12 @@ class ReRGroup extends ReObject { const ret = this._draw( render, rgid, - render.options.highlightStyle /* { 'fill' : 'red' }*/ + render.options.hoverStyle /* { 'fill' : 'red' } */ ) // eslint-disable-line no-underscore-dangle - render.ctab.addReObjectPath(LayerMap.highlighting, this.visel, ret) + render.ctab.addReObjectPath(LayerMap.hovering, this.visel, ret) this.item.frags.forEach((fnum, fid) => { - render.ctab.frags.get(fid).drawHighlight(render) + render.ctab.frags.get(fid).drawHover(render) }) return ret diff --git a/packages/ketcher-core/src/application/render/restruct/rerxnarrow.ts b/packages/ketcher-core/src/application/render/restruct/rerxnarrow.ts index 2ede4fd5b6..50bc7185f7 100644 --- a/packages/ketcher-core/src/application/render/restruct/rerxnarrow.ts +++ b/packages/ketcher-core/src/application/render/restruct/rerxnarrow.ts @@ -84,15 +84,15 @@ class ReRxnArrow extends ReObject { return minDist } - highlightPath(render: Render) { + hoverPath(render: Render) { const path = this.generatePath(render, render.options, 'selection') return render.paper.path(path) } - drawHighlight(render: Render) { - const ret = this.highlightPath(render).attr(render.options.highlightStyle) - render.ctab.addReObjectPath(LayerMap.highlighting, this.visel, ret) + drawHover(render: Render) { + const ret = this.hoverPath(render).attr(render.options.hoverStyle) + render.ctab.addReObjectPath(LayerMap.hovering, this.visel, ret) return ret } diff --git a/packages/ketcher-core/src/application/render/restruct/rerxnplus.js b/packages/ketcher-core/src/application/render/restruct/rerxnplus.js index 39e6b53d4d..557cf1f4a0 100644 --- a/packages/ketcher-core/src/application/render/restruct/rerxnplus.js +++ b/packages/ketcher-core/src/application/render/restruct/rerxnplus.js @@ -29,21 +29,23 @@ class ReRxnPlus extends ReObject { static isSelectable() { return true } - highlightPath(render) { - var p = Scale.obj2scaled(this.item.pp, render.options) - var s = render.options.scale - /* eslint-disable no-mixed-operators*/ + + hoverPath(render) { + const p = Scale.obj2scaled(this.item.pp, render.options) + const s = render.options.scale + /* eslint-disable no-mixed-operators */ return render.paper.rect(p.x - s / 4, p.y - s / 4, s / 2, s / 2, s / 8) /* eslint-enable no-mixed-operators*/ } - drawHighlight(render) { - var ret = this.highlightPath(render).attr(render.options.highlightStyle) - render.ctab.addReObjectPath(LayerMap.highlighting, this.visel, ret) + + drawHover(render) { + const ret = this.hoverPath(render).attr(render.options.hoverStyle) + render.ctab.addReObjectPath(LayerMap.hovering, this.visel, ret) return ret } makeSelectionPlate(restruct, paper, styles) { // TODO [MK] review parameters - return this.highlightPath(restruct.render).attr(styles.selectionStyle) + return this.hoverPath(restruct.render).attr(styles.selectionStyle) } show(restruct, id, options) { var render = restruct.render diff --git a/packages/ketcher-core/src/application/render/restruct/resgroup.js b/packages/ketcher-core/src/application/render/restruct/resgroup.js index e1111883b0..d78d0456b0 100644 --- a/packages/ketcher-core/src/application/render/restruct/resgroup.js +++ b/packages/ketcher-core/src/application/render/restruct/resgroup.js @@ -122,7 +122,8 @@ class ReSGroup extends ReObject { return paper.rect(startX, startY, size, size).attr(options.selectionStyle) } } - drawHighlight(render) { + + drawHover(render) { // eslint-disable-line max-statements var options = render.options var paper = render.paper @@ -138,11 +139,11 @@ class ReSGroup extends ReObject { ) ) { const { startX, startY, size } = getHighlighPathInfo(sGroupItem, options) - sGroupItem.highlighting = paper + sGroupItem.hovering = paper .rect(startX, startY, size, size) - .attr(options.highlightStyle) + .attr(options.hoverStyle) } else { - sGroupItem.highlighting = paper + sGroupItem.hovering = paper .path( 'M{0},{1}L{2},{3}L{4},{5}L{6},{7}L{0},{1}', tfx(a0.x), @@ -154,17 +155,17 @@ class ReSGroup extends ReObject { tfx(b0.x), tfx(b0.y) ) - .attr(options.highlightStyle) + .attr(options.hoverStyle) } - set.push(sGroupItem.highlighting) + set.push(sGroupItem.hovering) SGroup.getAtoms(render.ctab.molecule, sGroupItem).forEach(aid => { - set.push(render.ctab.atoms.get(aid).makeHighlightPlate(render)) + set.push(render.ctab.atoms.get(aid).makeHoverPlate(render)) }, this) SGroup.getBonds(render.ctab.molecule, sGroupItem).forEach(bid => { - set.push(render.ctab.bonds.get(bid).makeHighlightPlate(render)) + set.push(render.ctab.bonds.get(bid).makeHoverPlate(render)) }, this) - render.ctab.addReObjectPath(LayerMap.highlighting, this.visel, set) + render.ctab.addReObjectPath(LayerMap.hovering, this.visel, set) } show(restruct) { var render = restruct.render @@ -173,7 +174,7 @@ class ReSGroup extends ReObject { var remol = render.ctab var path = this.draw(remol, sgroup) restruct.addReObjectPath(LayerMap.data, this.visel, path, null, true) - this.setHighlight(this.highlight, render) // TODO: fix this + this.setHover(this.hover, render) // TODO: fix this } } } @@ -447,9 +448,9 @@ function getHighlighPathInfo(sgroup, options) { let startY = (a1.y + a0.y) / 2 - size / 2 if (sgroup.firstSgroupAtom) { const shift = new Vec2(size / 2, size / 2, 0) - const highlightPp = Vec2.diff(sgroup.firstSgroupAtom.pp.scaled(40), shift) - startX = highlightPp.x - startY = highlightPp.y + const hoverPp = Vec2.diff(sgroup.firstSgroupAtom.pp.scaled(40), shift) + startX = hoverPp.x + startY = hoverPp.y } return { a0, diff --git a/packages/ketcher-core/src/application/render/restruct/resimpleObject.ts b/packages/ketcher-core/src/application/render/restruct/resimpleObject.ts index 15e337473a..6f4545b90d 100644 --- a/packages/ketcher-core/src/application/render/restruct/resimpleObject.ts +++ b/packages/ketcher-core/src/application/render/restruct/resimpleObject.ts @@ -184,7 +184,8 @@ class ReSimpleObject extends ReObject { } return refPoints } - highlightPath(render: Render): Array { + + hoverPath(render: Render): Array { const point: Array = [] this.item.pos.forEach((p, index) => { @@ -329,15 +330,15 @@ class ReSimpleObject extends ReObject { return enhPaths } - drawHighlight(render: Render): Array { - const paths: Array = this.highlightPath(render).map(enhPath => { + drawHover(render: Render): Array { + const paths: Array = this.hoverPath(render).map(enhPath => { if (!enhPath.stylesApplied) { - return enhPath.path.attr(render.options.highlightStyle) + return enhPath.path.attr(render.options.hoverStyle) } return enhPath.path }) - render.ctab.addReObjectPath(LayerMap.highlighting, this.visel, paths) + render.ctab.addReObjectPath(LayerMap.hovering, this.visel, paths) return paths } @@ -351,7 +352,7 @@ class ReSimpleObject extends ReObject { var selectionSet = restruct.render.paper.set() selectionSet.push( generatePath(this.item.mode, paper, pos).attr( - styles.highlightStyleSimpleObject + styles.hoverStyleSimpleObject ) ) refPoints.forEach(rp => { diff --git a/packages/ketcher-core/src/application/render/restruct/restruct.ts b/packages/ketcher-core/src/application/render/restruct/restruct.ts index ad19df765f..6b40e6f123 100644 --- a/packages/ketcher-core/src/application/render/restruct/restruct.ts +++ b/packages/ketcher-core/src/application/render/restruct/restruct.ts @@ -387,7 +387,7 @@ class ReStruct { // TODO: when to update sgroup? this.sgroups.forEach(sgroup => { this.clearVisel(sgroup.visel) - sgroup.highlighting = null + sgroup.hovering = null sgroup.selectionPlate = null }) diff --git a/packages/ketcher-core/src/application/render/restruct/retext.ts b/packages/ketcher-core/src/application/render/restruct/retext.ts index 4c742f2cbd..8d40a6e091 100644 --- a/packages/ketcher-core/src/application/render/restruct/retext.ts +++ b/packages/ketcher-core/src/application/render/restruct/retext.ts @@ -59,7 +59,7 @@ class ReText extends ReObject { return refPoints } - highlightPath(render: any): any { + hoverPath(render: any): any { const { p0, p1 } = this.getRelBox(this.paths) const topLeft = p0.sub(render.options.offset) const { x: width, y: height } = p1.sub(p0) @@ -103,16 +103,16 @@ class ReText extends ReObject { }, 0) } - drawHighlight(render: any): any { + drawHover(render: any): any { if (!this.paths.length) return null - const ret = this.highlightPath(render).attr(render.options.highlightStyle) - render.ctab.addReObjectPath(LayerMap.highlighting, this.visel, ret) + const ret = this.hoverPath(render).attr(render.options.hoverStyle) + render.ctab.addReObjectPath(LayerMap.hovering, this.visel, ret) return ret } makeSelectionPlate(restruct: ReStruct, paper: any, options: any): any { if (!this.paths.length || !paper) return null - return this.highlightPath(restruct.render).attr(options.selectionStyle) + return this.hoverPath(restruct.render).attr(options.selectionStyle) } show(restruct: ReStruct, _id: number, options: any): void { diff --git a/packages/ketcher-core/src/domain/entities/highlight.ts b/packages/ketcher-core/src/domain/entities/highlight.ts new file mode 100644 index 0000000000..1037bb21ec --- /dev/null +++ b/packages/ketcher-core/src/domain/entities/highlight.ts @@ -0,0 +1,34 @@ +/**************************************************************************** + * Copyright 2021 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. + ***************************************************************************/ + +export interface HighlightAttributes { + atoms: Array + bonds: Array + color: string +} + +export class Highlight { + atoms: Array + bonds: Array + color: string + + constructor(attributes: HighlightAttributes) { + const { atoms, bonds, color } = attributes + this.color = color + this.atoms = atoms + this.bonds = bonds + } +} diff --git a/packages/ketcher-core/src/domain/entities/index.ts b/packages/ketcher-core/src/domain/entities/index.ts index 699a55d257..989caf00d6 100644 --- a/packages/ketcher-core/src/domain/entities/index.ts +++ b/packages/ketcher-core/src/domain/entities/index.ts @@ -33,3 +33,4 @@ export * from './pile' export * from './vec2' export * from './box2Abs' export * from './pool' +export * from './highlight' diff --git a/packages/ketcher-core/src/domain/entities/sgroup.ts b/packages/ketcher-core/src/domain/entities/sgroup.ts index 161ac33e11..5426f3b6d9 100644 --- a/packages/ketcher-core/src/domain/entities/sgroup.ts +++ b/packages/ketcher-core/src/domain/entities/sgroup.ts @@ -62,8 +62,8 @@ export class SGroup { bracketBox: any bracketDir: Vec2 areas: any - highlight: boolean - highlighting: any + hover: boolean + hovering: any selected: boolean selectionPlate: any atoms: any @@ -85,8 +85,8 @@ export class SGroup { this.bracketDir = new Vec2(1, 0) this.areas = [] - this.highlight = false - this.highlighting = null + this.hover = false + this.hovering = null this.selected = false this.selectionPlate = null diff --git a/packages/ketcher-core/src/domain/entities/struct.ts b/packages/ketcher-core/src/domain/entities/struct.ts index caa74017ec..96a4c1baff 100644 --- a/packages/ketcher-core/src/domain/entities/struct.ts +++ b/packages/ketcher-core/src/domain/entities/struct.ts @@ -33,6 +33,7 @@ import { SGroupForest } from './sgroupForest' import { SimpleObject } from './simpleObject' import { Text } from './text' import { Vec2 } from './vec2' +import { Highlight } from './highlight' export type Neighbor = { aid: number @@ -63,6 +64,7 @@ export class Struct { simpleObjects: Pool texts: Pool functionalGroups: Pool + highlights: Pool constructor() { this.atoms = new Pool() @@ -80,6 +82,7 @@ export class Struct { this.simpleObjects = new Pool() this.texts = new Pool() this.functionalGroups = new Pool() + this.highlights = new Pool() } hasRxnProps(): boolean { diff --git a/packages/ketcher-react/src/script/editor/Editor.ts b/packages/ketcher-react/src/script/editor/Editor.ts index 81e99a6b2c..8cd9781fc5 100644 --- a/packages/ketcher-react/src/script/editor/Editor.ts +++ b/packages/ketcher-react/src/script/editor/Editor.ts @@ -34,6 +34,7 @@ import closest from './shared/closest' import { customOnChangeHandler } from './utils' import { isEqual } from 'lodash/fp' import toolMap from './tool' +import { Highlighter } from './highlighter' const SCALE = 40 const HISTORY_SIZE = 32 // put me to options @@ -106,6 +107,7 @@ class Editor implements KetcherEditor { historyStack: any historyPtr: any errorHandler: ((message: string) => void) | null + highlights: Highlighter event: { message: Subscription elementEdit: PipelineSubscription @@ -140,6 +142,7 @@ class Editor implements KetcherEditor { this.historyStack = [] this.historyPtr = 0 this.errorHandler = null + this.highlights = new Highlighter(this) this.event = { message: new Subscription(), @@ -316,60 +319,11 @@ class Editor implements KetcherEditor { 'ci' in tool && (!ci || tool.ci.map !== ci.map || tool.ci.id !== ci.id) ) { - this.highlight(tool.ci, false) + setHover(tool.ci, false, this.render) delete tool.ci } - if (ci && this.highlight(ci, true)) tool.ci = ci - } - - highlight(ci: any, visible: any) { - if (highlightTargets.indexOf(ci.map) === -1) { - return false - } - - const render = this.render - let item: any = null - - if (ci.map === 'merge') { - Object.keys(ci.items).forEach(mp => { - ci.items[mp].forEach(dstId => { - item = render.ctab[mp].get(dstId)! - - if (item) { - item.setHighlight(visible, render) - } - }) - }) - - return true - } - - if (ci.map === 'functionalGroups') ci.map = 'sgroups' // TODO: Refactor object - - item = (render.ctab[ci.map] as Map).get(ci.id) - if (!item) { - return true // TODO: fix, attempt to highlight a deleted item - } - - if ( - (ci.map === 'sgroups' && item.item.type === 'DAT') || - ci.map === 'sgroupData' - ) { - // set highlight for both the group and the data item - const item1 = render.ctab.sgroups.get(ci.id) - if (item1) { - item1.setHighlight(visible, render) - } - - const item2 = render.ctab.sgroupData.get(ci.id) - if (item2) { - item2.setHighlight(visible, render) - } - } else { - item.setHighlight(visible, render) - } - return true + if (ci && setHover(ci, true, this.render)) tool.ci = ci } update(action: Action | true, ignoreHistory?) { @@ -611,3 +565,51 @@ function getStructCenter(ReStruct, selection?) { export { Editor } export default Editor + +function setHover(ci: any, visible: any, render: any) { + if (highlightTargets.indexOf(ci.map) === -1) { + return false + } + + let item: any = null + + if (ci.map === 'merge') { + Object.keys(ci.items).forEach(mp => { + ci.items[mp].forEach(dstId => { + item = render.ctab[mp].get(dstId)! + + if (item) { + item.setHover(visible, render) + } + }) + }) + + return true + } + + if (ci.map === 'functionalGroups') ci.map = 'sgroups' // TODO: Refactor object + + item = (render.ctab[ci.map] as Map).get(ci.id) + if (!item) { + return true // TODO: fix, attempt to highlight a deleted item + } + + if ( + (ci.map === 'sgroups' && item.item.type === 'DAT') || + ci.map === 'sgroupData' + ) { + // set highlight for both the group and the data item + const item1 = render.ctab.sgroups.get(ci.id) + if (item1) { + item1.setHover(visible, render) + } + + const item2 = render.ctab.sgroupData.get(ci.id) + if (item2) { + item2.setHover(visible, render) + } + } else { + item.setHover(visible, render) + } + return true +} diff --git a/packages/ketcher-react/src/script/editor/highlighter.ts b/packages/ketcher-react/src/script/editor/highlighter.ts new file mode 100644 index 0000000000..9ebc63633b --- /dev/null +++ b/packages/ketcher-react/src/script/editor/highlighter.ts @@ -0,0 +1,216 @@ +/**************************************************************************** + * Copyright 2021 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +import { fromHighlightCreate, fromHighlightClear } from 'ketcher-core' +import type { Struct } from 'ketcher-core' + +import type { Editor } from './Editor' + +type HighlightAttributes = { + atoms: number[] + bonds: number[] + color: string +} + +export class Highlighter { + editor: Editor + + constructor(editor: Editor) { + this.editor = editor + } + + getAll() { + const highlightsMap = this.editor.render.ctab.molecule.highlights + const highlightsArray = [...highlightsMap].map(([id, highlight]) => ({ + id, + highlight + })) + return highlightsArray + } + + create(...args: HighlightAttributes[]) { + const createdHighlights: HighlightAttributes[] = [] + + args.forEach(arg => { + const { atoms, bonds, color } = arg + if (typeof color !== 'string') { + return + } + + if (!atoms && !bonds) { + return + } + + const restruct = this.editor.render.ctab + + const { validAtoms, validBonds } = getValidInputOnly( + restruct.molecule, + atoms, + bonds + ) + + if (validAtoms.length === 0 && validBonds.length === 0) { + return + } + + createdHighlights.push({ atoms: validAtoms, bonds: validBonds, color }) + }) + const action = fromHighlightCreate( + this.editor.render.ctab, + createdHighlights + ) + this.editor.update(action) + } + + clear() { + const action = fromHighlightClear(this.editor.render.ctab) + this.editor.update(action) + } + + /* + // Update by ID + update( + id: number, + { + atoms, + bonds, + color + }: { + atoms?: number[] + bonds?: number[] + color: string + } + ) { + if (typeof color !== 'string' || typeof id !== 'number') { + return + } + if (!atoms && !bonds) { + return + } + + const restruct = this.editor.render.ctab + + const { validAtoms, validBonds } = getValidInputOnly( + restruct.molecule, + atoms, + bonds + ) + + if (validAtoms.length === 0 && validBonds.length === 0) { + return + } + + const action = fromHighlightUpdate( + id, + this.editor.render.ctab, + validAtoms, + validBonds, + color + ) + this.editor.update(action) + } + + // Add items to highlight by id + append( + id: number, + { + atoms, + bonds, + color + }: { + atoms?: number[] + bonds?: number[] + color?: string + } + ) { + if (!atoms && !bonds && !color) { + return + } + const restruct = this.editor.render.ctab + + const { validAtoms, validBonds } = getValidInputOnly( + restruct.molecule, + atoms, + bonds + ) + + if (validAtoms.length === 0 && validBonds.length === 0 && !color) { + return + } + + const currentHighlight = restruct.molecule.highlights.get(id) + + if (currentHighlight) { + const mergedHighlight = { + atoms: union(currentHighlight.atoms, validAtoms), + bonds: union(currentHighlight.bonds, validBonds), + color: color || currentHighlight.color + } + + const action = fromHighlightUpdate( + id, + restruct, + mergedHighlight.atoms, + mergedHighlight.bonds, + mergedHighlight.color + ) + this.editor.update(action) + } + } + + // Delete by ID + delete(id: number) { + if (typeof id !== 'number') { + return + } + + const action = fromHighlightDelete(this.editor.render.ctab, id) + this.editor.update(action) + } + */ +} + +type ValidInput = { + validAtoms: number[] + validBonds: number[] +} + +function getValidInputOnly(struct: Struct, atoms, bonds): ValidInput { + if (!Array.isArray(atoms)) { + atoms = [] + } + + if (!Array.isArray(bonds)) { + bonds = [] + } + + const { atoms: structAtoms, bonds: structBonds } = struct + + // Filter out atom ids that are not in struct + if (atoms.length > 0) { + atoms = atoms.filter(aid => structAtoms.has(aid)) + } + + // Filter out bond ids that are not in struct + if (bonds.length > 0) { + bonds = bonds.filter(bid => structBonds.has(bid)) + } + + return { + validAtoms: atoms, + validBonds: bonds + } +} diff --git a/packages/ketcher-react/src/script/ui/dialog/template/template-attach.jsx b/packages/ketcher-react/src/script/ui/dialog/template/template-attach.jsx index 9292ababf9..7535a17f94 100644 --- a/packages/ketcher-react/src/script/ui/dialog/template/template-attach.jsx +++ b/packages/ketcher-react/src/script/ui/dialog/template/template-attach.jsx @@ -26,8 +26,8 @@ import { attachSchema } from '../../data/schema/struct-schema' const EDITOR_STYLES = { selectionStyle: { fill: '#47b3ec', stroke: 'none' }, - highlightStyle: { stroke: '#1a7090', 'stroke-width': 1.2 }, - highlightStyleSimpleObject: { 'stroke-opacity': 0.3 } + hoverStyle: { stroke: '#1a7090', 'stroke-width': 1.2 }, + hoverStyleSimpleObject: { 'stroke-opacity': 0.3 } } class Attach extends Component { From 8ab5b03fc44e0fefd6fc52c15153592c69c4043d Mon Sep 17 00:00:00 2001 From: Karen Sarkisyan Date: Thu, 17 Feb 2022 11:00:59 +0300 Subject: [PATCH 2/6] Return atom label rendering code (#1266) --- .../src/application/render/restruct/reatom.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/ketcher-core/src/application/render/restruct/reatom.ts b/packages/ketcher-core/src/application/render/restruct/reatom.ts index 00d009e1aa..fb939feee8 100644 --- a/packages/ketcher-core/src/application/render/restruct/reatom.ts +++ b/packages/ketcher-core/src/application/render/restruct/reatom.ts @@ -178,16 +178,18 @@ class ReAtom extends ReObject { implh = Math.floor(this.a.implicitH) isHydrogen = label.text === 'H' restruct.addReObjectPath(LayerMap.data, this.visel, label.path, ps, true) - this.setHover(this.hover, render) - } - if (options.showAtomIds) { - index = {} - index.text = aid.toString() - let idPos = this.hydrogenOnTheLeft - ? Vec2.lc(ps, 1, new Vec2({ x: -2, y: 0, z: 0 }), 6) - : Vec2.lc(ps, 1, new Vec2({ x: 2, y: 0, z: 0 }), 6) - if (this.showLabel) { - idPos = Vec2.lc(idPos, 1, new Vec2({ x: 1, y: -3, z: 0 }), 6) + + if (options.showAtomIds) { + index = {} + index.text = aid.toString() + index.path = render.paper.text(ps.x, ps.y, index.text).attr({ + font: options.font, + 'font-size': options.fontszsub, + fill: '#070' + }) + index.rbb = util.relBox(index.path.getBBox()) + draw.recenterText(index.path, index.rbb) + restruct.addReObjectPath(LayerMap.indices, this.visel, index.path, ps) } this.setHover(this.hover, render) } From 551c294fb3354179468a5c63cf0183c854bffff2 Mon Sep 17 00:00:00 2001 From: Karen Sarkisyan Date: Thu, 17 Feb 2022 15:38:47 +0300 Subject: [PATCH 3/6] Extend Editor interface with new methods (#1262) --- .../src/application/editor/editor.types.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/ketcher-core/src/application/editor/editor.types.ts b/packages/ketcher-core/src/application/editor/editor.types.ts index e9ef302614..c2a003011a 100644 --- a/packages/ketcher-core/src/application/editor/editor.types.ts +++ b/packages/ketcher-core/src/application/editor/editor.types.ts @@ -29,8 +29,22 @@ export interface LoadOptions { fragment: boolean } +interface Selection { + atoms?: Array + bonds?: Array + enhancedFlags?: Array + rxnPluses?: Array + rxnArrows?: Array +} + export interface Editor { isDitrty: () => boolean setOrigin: () => void struct: (struct?: Struct) => Struct + subscribe: (eventName: string, handler: (data?: any) => any) => any + unsubscribe: (eventName: string, subscriber: any) => void + selection: (arg?: Selection | 'all' | null) => Selection | null + undo: () => void + redo: () => void + clear: () => void } From 54d1fa924bca2991d036f1e638999a474c79a4ae Mon Sep 17 00:00:00 2001 From: Karen Sarkisyan Date: Fri, 18 Feb 2022 12:06:36 +0300 Subject: [PATCH 4/6] Set focus to editor cliparea element after error modal is closed (#1270) --- example/src/App.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index fba20f066b..5568c7fc0b 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -52,7 +52,16 @@ const App = () => { }} /> {hasError && ( - setHasError(false)} /> + { + setHasError(false) + + // Focus on editor after modal is closed + const cliparea: HTMLElement = document.querySelector('.cliparea')! + cliparea?.focus() + }} + /> )} ) From e16fcb41e0a2e2f539b78365bb0a01ac78531575 Mon Sep 17 00:00:00 2001 From: Andrei Mazol Date: Mon, 21 Feb 2022 15:49:10 +0300 Subject: [PATCH 5/6] update package versions --- packages/ketcher-core/package.json | 2 +- packages/ketcher-react/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ketcher-core/package.json b/packages/ketcher-core/package.json index cfb28e5cdf..1b1594d7c9 100644 --- a/packages/ketcher-core/package.json +++ b/packages/ketcher-core/package.json @@ -1,6 +1,6 @@ { "name": "ketcher-core", - "version": "1.2.1", + "version": "1.3.0", "description": "Web-based molecule sketcher", "license": "Apache-2.0", "homepage": "http://lifescience.opensource.epam.com/ketcher", diff --git a/packages/ketcher-react/package.json b/packages/ketcher-react/package.json index c91c702688..067cdff53d 100644 --- a/packages/ketcher-react/package.json +++ b/packages/ketcher-react/package.json @@ -1,6 +1,6 @@ { "name": "ketcher-react", - "version": "2.4.1", + "version": "2.4.2", "description": "Web-based molecule sketcher", "license": "Apache-2.0", "homepage": "http://lifescience.opensource.epam.com/ketcher", From f70adbec9a76ad739125e772f8a09c4fa2896cb0 Mon Sep 17 00:00:00 2001 From: Aleksandr Gabrielov <94836496+Alex-Work-Account@users.noreply.github.com> Date: Tue, 22 Feb 2022 10:41:37 +0300 Subject: [PATCH 6/6] Add styles for firefox scrollbar (#1277) * Add styles for firefox scrollbar * Rewrite mixin with css function and teplate string since typescript doesn't know scrollbar width, update snapshots * Update snapshots --- .../src/__snapshots__/Editor.test.tsx.snap | 8 ++--- .../Open/__snapshots__/Open.test.tsx.snap | 2 +- .../save/__snapshots__/Save.test.tsx.snap | 4 +-- .../__snapshots__/notationInput.test.tsx.snap | 2 +- .../modal/__snapshots__/Modal.test.tsx.snap | 2 +- .../src/theming/mixins/mixins.ts | 35 ++++++++++--------- 6 files changed, 28 insertions(+), 25 deletions(-) diff --git a/packages/ketcher-polymer-editor-react/src/__snapshots__/Editor.test.tsx.snap b/packages/ketcher-polymer-editor-react/src/__snapshots__/Editor.test.tsx.snap index b0332121fe..45e27f8e30 100644 --- a/packages/ketcher-polymer-editor-react/src/__snapshots__/Editor.test.tsx.snap +++ b/packages/ketcher-polymer-editor-react/src/__snapshots__/Editor.test.tsx.snap @@ -309,7 +309,7 @@ Object { class="css-1il5ut8" >