Skip to content

Commit

Permalink
Merge branch 'master' of github.com:epam/ketcher into #1276-template-…
Browse files Browse the repository at this point in the history
…with-simple-objects-are-incorrect-after-refresh
  • Loading branch information
neiloxx committed Feb 22, 2022
2 parents 354672a + f70adbe commit f5311c0
Show file tree
Hide file tree
Showing 36 changed files with 848 additions and 170 deletions.
11 changes: 10 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,16 @@ const App = () => {
/>
{polymerEditor && <PolymerToggler toggle={setShowPolymerEditor} />}
{hasError && (
<ErrorModal message={errorMessage} close={() => setHasError(false)} />
<ErrorModal
message={errorMessage}
close={() => {
setHasError(false)

// Focus on editor after modal is closed
const cliparea: HTMLElement = document.querySelector('.cliparea')!
cliparea?.focus()
}}
/>
)}
</>
)
Expand Down
2 changes: 1 addition & 1 deletion packages/ketcher-core/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
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'
14 changes: 14 additions & 0 deletions packages/ketcher-core/src/application/editor/editor.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,22 @@ export interface LoadOptions {
fragment: boolean
}

interface Selection {
atoms?: Array<number>
bonds?: Array<number>
enhancedFlags?: Array<number>
rxnPluses?: Array<number>
rxnArrows?: Array<number>
}

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
}
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)
}
})
}
}
Loading

0 comments on commit f5311c0

Please sign in to comment.