Skip to content

Commit

Permalink
#1242 add highlighting (#1252)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
karen-sarkisyan authored Feb 15, 2022
1 parent 622b0bb commit 759a4c0
Show file tree
Hide file tree
Showing 26 changed files with 831 additions and 165 deletions.
96 changes: 96 additions & 0 deletions packages/ketcher-core/src/application/editor/actions/highlight.ts
Original file line number Diff line number Diff line change
@@ -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
}
*/
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export * from './simpleobject'
export * from './template'
export * from './text'
export * from './utils'
export * from './highlight'
Original file line number Diff line number Diff line change
Expand Up @@ -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'
})
224 changes: 224 additions & 0 deletions packages/ketcher-core/src/application/editor/operations/highlight.ts
Original file line number Diff line number Diff line change
@@ -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<number>
bonds: Array<number>
color: string
highlightId?: number
}

export class HighlightAdd extends BaseOperation {
data: Data

constructor(
atoms: Array<number>,
bonds: Array<number>,
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<number>,
bonds?: Array<number>,
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<number>,
bonds: Array<number>,
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)
}
})
}
}
4 changes: 2 additions & 2 deletions packages/ketcher-core/src/application/render/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function defaultOptions(opt) {
fill: '#7f7',
stroke: 'none'
},
highlightStyle: {
hoverStyle: {
stroke: '#0c0',
'stroke-width': (0.6 * scaleFactor) / 20
},
Expand All @@ -86,7 +86,7 @@ function defaultOptions(opt) {
stroke: 'gray',
'stroke-width': '1px'
},
highlightStyleSimpleObject: {
hoverStyleSimpleObject: {
stroke: '#0c0',
'stroke-width': scaleFactor / 4,
'stroke-linecap': 'round',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
export enum LayerMap {
background = 'background',
selectionPlate = 'selectionPlate',
highlighting = 'highlighting',
hovering = 'hovering',
warnings = 'warnings',
data = 'data',
indices = 'indices'
Expand Down
Loading

0 comments on commit 759a4c0

Please sign in to comment.