From b393f7dfcf5a7d81fc73b949dfca11d826f8da20 Mon Sep 17 00:00:00 2001 From: aproskurnov Date: Wed, 22 Jan 2025 09:13:38 +0100 Subject: [PATCH] #6088 disable create antisense strand option if antisensless base present in chain selection (#6328) * disable create antisense strand option * added disabling menu for selection sugar * added mixed type and base strand calculation --- .../src/domain/entities/AmbiguousMonomer.ts | 2 + .../ketcher-core/src/domain/entities/types.ts | 6 +- .../SelectedMonomersContextMenu.tsx | 91 +++++++++++++++++-- 3 files changed, 91 insertions(+), 8 deletions(-) diff --git a/packages/ketcher-core/src/domain/entities/AmbiguousMonomer.ts b/packages/ketcher-core/src/domain/entities/AmbiguousMonomer.ts index d96d771783..cf112426bf 100644 --- a/packages/ketcher-core/src/domain/entities/AmbiguousMonomer.ts +++ b/packages/ketcher-core/src/domain/entities/AmbiguousMonomer.ts @@ -19,6 +19,7 @@ export const DEFAULT_VARIANT_MONOMER_LABEL = '%'; export class AmbiguousMonomer extends BaseMonomer implements IVariantMonomer { public monomers: BaseMonomer[]; public monomerClass: KetMonomerClass; + public subtype: KetAmbiguousMonomerTemplateSubType; constructor( public variantMonomerItem: AmbiguousMonomerType, position?: Vec2, @@ -52,6 +53,7 @@ export class AmbiguousMonomer extends BaseMonomer implements IVariantMonomer { this.monomerClass = AmbiguousMonomer.getMonomerClass( variantMonomerItem.monomers, ); + this.subtype = variantMonomerItem.subtype; } public static getMonomerClass(monomers: BaseMonomer[]) { diff --git a/packages/ketcher-core/src/domain/entities/types.ts b/packages/ketcher-core/src/domain/entities/types.ts index c16dc6e65c..13f9a7e7c1 100644 --- a/packages/ketcher-core/src/domain/entities/types.ts +++ b/packages/ketcher-core/src/domain/entities/types.ts @@ -1,7 +1,11 @@ import { BaseMonomer } from 'domain/entities/BaseMonomer'; -import { KetMonomerClass } from 'application/formatters'; +import { + KetAmbiguousMonomerTemplateSubType, + KetMonomerClass, +} from 'application/formatters'; export interface IVariantMonomer { monomers: BaseMonomer[]; monomerClass: KetMonomerClass; + subtype: KetAmbiguousMonomerTemplateSubType; } diff --git a/packages/ketcher-macromolecules/src/components/contextMenu/SelectedMonomersContextMenu/SelectedMonomersContextMenu.tsx b/packages/ketcher-macromolecules/src/components/contextMenu/SelectedMonomersContextMenu/SelectedMonomersContextMenu.tsx index e81da1a5d7..e2be83a045 100644 --- a/packages/ketcher-macromolecules/src/components/contextMenu/SelectedMonomersContextMenu/SelectedMonomersContextMenu.tsx +++ b/packages/ketcher-macromolecules/src/components/contextMenu/SelectedMonomersContextMenu/SelectedMonomersContextMenu.tsx @@ -5,9 +5,12 @@ import { KETCHER_MACROMOLECULES_ROOT_NODE_SELECTOR } from 'ketcher-react'; import { useAppSelector } from 'hooks'; import { selectEditor } from 'state/common'; import { + AmbiguousMonomer, BaseMonomer, getRnaBaseFromSugar, getSugarFromRnaBase, + isRnaBaseOrAmbiguousRnaBase, + KetAmbiguousMonomerTemplateSubType, RNABase, Sugar, } from 'ketcher-core'; @@ -17,21 +20,95 @@ type SelectedMonomersContextMenuType = { selectedMonomers: BaseMonomer[]; }; +const getMonomersCode = (monomers: BaseMonomer[]) => { + return monomers + .map((monomer) => monomer.monomerItem.props.MonomerNaturalAnalogCode) + .sort() + .join(''); +}; +const isSenseBase = (monomer: BaseMonomer | AmbiguousMonomer) => { + const { monomerItem } = monomer; + const isNaturalAnalogue = + monomerItem.props.MonomerNaturalAnalogCode === 'A' || + monomerItem.props.MonomerNaturalAnalogCode === 'C' || + monomerItem.props.MonomerNaturalAnalogCode === 'G' || + monomerItem.props.MonomerNaturalAnalogCode === 'T' || + monomerItem.props.MonomerNaturalAnalogCode === 'U'; + if (isNaturalAnalogue) { + return true; + } + if (!monomer.monomerItem.isAmbiguous) { + return false; + } + + if ( + (monomer as AmbiguousMonomer).subtype === + KetAmbiguousMonomerTemplateSubType.MIXTURE + ) { + return false; + } + + const N1 = 'ACGT'; + const N2 = 'ACGU'; + const B1 = 'CGT'; + const B2 = 'CGU'; + const D1 = 'AGT'; + const D2 = 'AGU'; + const H1 = 'ACT'; + const H2 = 'ACU'; + const K1 = 'GT'; + const K2 = 'GU'; + const W1 = 'AT'; + const W2 = 'AU'; + const Y1 = 'CT'; + const Y2 = 'CU'; + const M = 'AC'; + const R = 'AG'; + const S = 'CG'; + const V = 'ACG'; + const ambigues = [ + N1, + N2, + B1, + B2, + D1, + D2, + H1, + H2, + K1, + K2, + W1, + W2, + Y1, + Y2, + M, + R, + S, + V, + ]; + const code = getMonomersCode((monomer as AmbiguousMonomer).monomers); + return ambigues.some((v) => v === code); +}; + export const SelectedMonomersContextMenu = ({ selectedMonomers, }: SelectedMonomersContextMenuType) => { const editor = useAppSelector(selectEditor); const isAntisenseCreationDisabled = selectedMonomers?.some( - (selectedMonomer) => { + (selectedMonomer: BaseMonomer) => { + const rnaBaseForSugar = + selectedMonomer instanceof Sugar && + getRnaBaseFromSugar(selectedMonomer); + return ( (selectedMonomer instanceof RNABase && - (selectedMonomer.monomerItem.props.MonomerNaturalAnalogCode === '' || - selectedMonomer.monomerItem.props.MonomerNaturalAnalogCode === - 'X' || - selectedMonomer.hydrogenBonds.length > 0 || + (selectedMonomer.hydrogenBonds.length > 0 || selectedMonomer.covalentBonds.length > 1)) || - (selectedMonomer instanceof Sugar && - (getRnaBaseFromSugar(selectedMonomer)?.hydrogenBonds.length || 0) > 0) + (isRnaBaseOrAmbiguousRnaBase(selectedMonomer) && + !isSenseBase(selectedMonomer)) || + (rnaBaseForSugar && + (rnaBaseForSugar.hydrogenBonds.length > 0 || + !isSenseBase(rnaBaseForSugar))) ); }, );