diff --git a/example/src/App.tsx b/example/src/App.tsx index 34edde9461..be5c8a6662 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -67,7 +67,16 @@ const App = () => { /> {polymerEditor && } {hasError && ( - setHasError(false)} /> + { + setHasError(false) + + // Focus on editor after modal is closed + const cliparea: HTMLElement = document.querySelector('.cliparea')! + cliparea?.focus() + }} + /> )} ) diff --git a/packages/ketcher-core/package.json b/packages/ketcher-core/package.json index 3dd99a889e..bd91d809a1 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-core/src/application/editor/actions/highlight.ts b/packages/ketcher-core/src/application/editor/actions/highlight.ts new file mode 100644 index 0000000000..9b07ffca10 --- /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/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 } 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..b1edaad189 --- /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 c1b5d2d8b4..0ae11471fd 100644 --- a/packages/ketcher-core/src/application/render/restruct/reatom.ts +++ b/packages/ketcher-core/src/application/render/restruct/reatom.ts @@ -84,13 +84,13 @@ class ReAtom extends ReObject { 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) @@ -109,7 +109,7 @@ class ReAtom extends ReObject { } return paper .circle(ps.x, ps.y, options.atomSelectionPlateRadius) - .attr(options.highlightStyle) + .attr(options.hoverStyle) } makeSelectionPlate(restruct: ReStruct, paper: any, styles: any) { @@ -185,7 +185,6 @@ 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.setHighlight(this.highlight, render) } if (options.showAtomIds) { index = {} @@ -205,6 +204,7 @@ class ReAtom extends ReObject { draw.recenterText(index.path, index.rbb) restruct.addReObjectPath(LayerMap.indices, this.visel, index.path, ps) } + this.setHover(this.hover, render) if (this.showLabel && !this.a.pseudo) { let hydroIndex: any = null @@ -376,6 +376,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 d37c53f96d..dbf67940c6 100644 --- a/packages/ketcher-core/src/application/render/restruct/rebond.ts +++ b/packages/ketcher-core/src/application/render/restruct/rebond.ts @@ -51,13 +51,13 @@ class ReBond extends ReObject { 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 @@ -77,7 +77,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) { @@ -157,7 +157,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 @@ -213,9 +213,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 3460196f0b..92124f0709 100644 --- a/packages/ketcher-core/src/application/render/restruct/redatasgroupdata.js +++ b/packages/ketcher-core/src/application/render/restruct/redatasgroupdata.js @@ -28,22 +28,22 @@ class ReDataSGroupData extends ReObject { return true } - highlightPath(render) { + 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) { - const 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 78ec95506e..54ae8fe3ff 100644 --- a/packages/ketcher-core/src/application/render/restruct/reenhancedFlag.ts +++ b/packages/ketcher-core/src/application/render/restruct/reenhancedFlag.ts @@ -33,25 +33,25 @@ class ReEnhancedFlag extends ReObject { return true } - highlightPath(render: Render): any { + 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 - 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 { // 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 3ea04cad1a..8392393ef7 100644 --- a/packages/ketcher-core/src/application/render/restruct/refrag.js +++ b/packages/ketcher-core/src/application/render/restruct/refrag.js @@ -91,12 +91,12 @@ class ReFrag extends ReObject { 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) { @@ -107,12 +107,12 @@ 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) + 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 1fa7cbf9c8..330a6f6529 100644 --- a/packages/ketcher-core/src/application/render/restruct/reobject.ts +++ b/packages/ketcher-core/src/application/render/restruct/reobject.ts @@ -22,8 +22,8 @@ import Visel from './visel' class ReObject { public visel: Visel - public highlight = false - public highlighting: any = null + public hover = false + public hovering: any = null public selected = false public selectionPlate: any = null @@ -40,34 +40,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 } - drawHighlight(_render: Render): any { - throw new Error('ReObject.drawHighlight is not overridden.') + drawHover(_render: Render): any { + throw new Error('ReObject.drawHover is not overridden.') } makeSelectionPlate(_restruct: ReStruct, _paper: any, _styles: any): any { diff --git a/packages/ketcher-core/src/application/render/restruct/rergroup.js b/packages/ketcher-core/src/application/render/restruct/rergroup.js index 9cc3934fc1..4855bfad40 100644 --- a/packages/ketcher-core/src/application/render/restruct/rergroup.js +++ b/packages/ketcher-core/src/application/render/restruct/rergroup.js @@ -144,7 +144,7 @@ class ReRGroup extends ReObject { .attr(attrs) } - drawHighlight(render) { + drawHover(render) { const rgid = render.ctab.rgroups.keyOf(this) if (!rgid) { @@ -157,12 +157,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 78071e72f7..628868eb54 100644 --- a/packages/ketcher-core/src/application/render/restruct/rerxnarrow.ts +++ b/packages/ketcher-core/src/application/render/restruct/rerxnarrow.ts @@ -96,15 +96,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 46d7ae0052..f12e13ab42 100644 --- a/packages/ketcher-core/src/application/render/restruct/rerxnplus.js +++ b/packages/ketcher-core/src/application/render/restruct/rerxnplus.js @@ -31,7 +31,7 @@ class ReRxnPlus extends ReObject { return true } - highlightPath(render) { + hoverPath(render) { const p = Scale.obj2scaled(this.item.pp, render.options) const s = render.options.scale /* eslint-disable no-mixed-operators */ @@ -39,15 +39,15 @@ class ReRxnPlus extends ReObject { /* eslint-enable no-mixed-operators */ } - drawHighlight(render) { - const 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) { diff --git a/packages/ketcher-core/src/application/render/restruct/resgroup.js b/packages/ketcher-core/src/application/render/restruct/resgroup.js index abfbba5e50..1577e2aff1 100644 --- a/packages/ketcher-core/src/application/render/restruct/resgroup.js +++ b/packages/ketcher-core/src/application/render/restruct/resgroup.js @@ -130,7 +130,7 @@ class ReSGroup extends ReObject { } } - drawHighlight(render) { + drawHover(render) { // eslint-disable-line max-statements const options = render.options const paper = render.paper @@ -146,11 +146,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), @@ -162,17 +162,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) { @@ -182,7 +182,7 @@ class ReSGroup extends ReObject { const remol = render.ctab const 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 } } } @@ -458,9 +458,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 db88ac329f..a6a5c63a7b 100644 --- a/packages/ketcher-core/src/application/render/restruct/resimpleObject.ts +++ b/packages/ketcher-core/src/application/render/restruct/resimpleObject.ts @@ -189,7 +189,7 @@ class ReSimpleObject extends ReObject { return refPoints } - highlightPath(render: Render): Array { + hoverPath(render: Render): Array { const point: Array = [] this.item.pos.forEach((p, index) => { @@ -336,15 +336,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 } @@ -358,7 +358,7 @@ class ReSimpleObject extends ReObject { const 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 907e5871a7..81b964eb1c 100644 --- a/packages/ketcher-core/src/application/render/restruct/restruct.ts +++ b/packages/ketcher-core/src/application/render/restruct/restruct.ts @@ -389,7 +389,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 ade0e91f77..44f694d1bc 100644 --- a/packages/ketcher-core/src/application/render/restruct/retext.ts +++ b/packages/ketcher-core/src/application/render/restruct/retext.ts @@ -69,7 +69,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) @@ -115,16 +115,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 b7faf7bb95..b9b631b8d3 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 2141230c61..feb7159636 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-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" >