diff --git a/packages/ketcher-core/src/application/editor/operations/monomer/monomerFactory.ts b/packages/ketcher-core/src/application/editor/operations/monomer/monomerFactory.ts index b286bf8efc..f283cad059 100644 --- a/packages/ketcher-core/src/application/editor/operations/monomer/monomerFactory.ts +++ b/packages/ketcher-core/src/application/editor/operations/monomer/monomerFactory.ts @@ -5,13 +5,17 @@ import { RNABaseRenderer, SugarRenderer, PhosphateRenderer, + UnresolvedMonomerRenderer, } from 'application/render/renderers'; import { MonomerItemType } from 'domain/types'; -import { Peptide } from 'domain/entities/Peptide'; -import { Chem } from 'domain/entities/Chem'; -import { Sugar } from 'domain/entities/Sugar'; -import { Phosphate } from 'domain/entities/Phosphate'; -import { RNABase } from 'domain/entities/RNABase'; +import { + Peptide, + Chem, + Sugar, + Phosphate, + RNABase, + UnresolvedMonomer, +} from 'domain/entities'; import { KetMonomerClass } from 'application/formatters/types/ket'; type DerivedClass = new (...args: unknown[]) => T; @@ -47,7 +51,11 @@ export const monomerFactory = ( let MonomerRenderer; let ketMonomerClass: KetMonomerClass; - if ( + if (monomer.props.unresolved) { + Monomer = UnresolvedMonomer; + MonomerRenderer = UnresolvedMonomerRenderer; + ketMonomerClass = KetMonomerClass.CHEM; + } else if ( monomer.props.MonomerType === MONOMER_CONST.CHEM || (monomer.props.MonomerType === MONOMER_CONST.RNA && (monomer.props.MonomerClass === MONOMER_CONST.MODDNA || diff --git a/packages/ketcher-core/src/application/formatters/types/ket.ts b/packages/ketcher-core/src/application/formatters/types/ket.ts index 6233a67a77..a0cfbbf236 100644 --- a/packages/ketcher-core/src/application/formatters/types/ket.ts +++ b/packages/ketcher-core/src/application/formatters/types/ket.ts @@ -122,6 +122,7 @@ export interface IKetMonomerTemplate { classHELM?: string; name?: string; idtAliases?: IKetIdtAliases; + unresolved?: boolean; } export interface IKetMonomerTemplateRef { diff --git a/packages/ketcher-core/src/application/render/renderers/UnresolvedMonomerRenderer.ts b/packages/ketcher-core/src/application/render/renderers/UnresolvedMonomerRenderer.ts new file mode 100644 index 0000000000..df06a420b4 --- /dev/null +++ b/packages/ketcher-core/src/application/render/renderers/UnresolvedMonomerRenderer.ts @@ -0,0 +1,44 @@ +import { BaseMonomerRenderer } from 'application/render/renderers/BaseMonomerRenderer'; +import { UnresolvedMonomer } from 'domain/entities'; +import { Selection } from 'd3'; + +const UNRESOLVED_MONOMER_SELECTED_ELEMENT_ID = '#unresolved-monomer-selection'; +const UNRESOLVED_MONOMER_HOVERED_ELEMENT_ID = '#unresolved-monomer-hover'; +const UNRESOLVED_MONOMER_SYMBOL_ELEMENT_ID = '#unresolved-monomer'; + +export class UnresolvedMonomerRenderer extends BaseMonomerRenderer { + constructor(public monomer: UnresolvedMonomer, scale?: number) { + super( + monomer, + UNRESOLVED_MONOMER_SELECTED_ELEMENT_ID, + UNRESOLVED_MONOMER_HOVERED_ELEMENT_ID, + UNRESOLVED_MONOMER_SYMBOL_ELEMENT_ID, + scale, + ); + } + + public get textColor() { + return 'white'; + } + + protected appendBody( + rootElement: Selection, + ) { + return rootElement + .append('use') + .data([this]) + .attr('href', UNRESOLVED_MONOMER_SYMBOL_ELEMENT_ID); + } + + show(theme) { + super.show(theme); + } + + protected get enumerationElementPosition() { + return undefined; + } + + protected get beginningElementPosition() { + return undefined; + } +} diff --git a/packages/ketcher-core/src/application/render/renderers/index.ts b/packages/ketcher-core/src/application/render/renderers/index.ts index 8ae82188d2..2683b32229 100644 --- a/packages/ketcher-core/src/application/render/renderers/index.ts +++ b/packages/ketcher-core/src/application/render/renderers/index.ts @@ -6,4 +6,5 @@ export { RNABaseRenderer } from './RNABaseRenderer'; export { BaseRenderer } from './BaseRenderer'; export { BaseMonomerRenderer } from './BaseMonomerRenderer'; export { PolymerBondRenderer } from './PolymerBondRenderer'; +export { UnresolvedMonomerRenderer } from './UnresolvedMonomerRenderer'; export * from './sequence'; diff --git a/packages/ketcher-core/src/application/render/renderers/sequence/SequenceNodeRendererFactory.ts b/packages/ketcher-core/src/application/render/renderers/sequence/SequenceNodeRendererFactory.ts index b0550de404..af2d656df7 100644 --- a/packages/ketcher-core/src/application/render/renderers/sequence/SequenceNodeRendererFactory.ts +++ b/packages/ketcher-core/src/application/render/renderers/sequence/SequenceNodeRendererFactory.ts @@ -1,18 +1,27 @@ -import { Chem, Peptide, Phosphate, Vec2 } from 'domain/entities'; -import { PeptideSequenceItemRenderer } from 'application/render/renderers/sequence/PeptideSequenceItemRenderer'; -import { ChemSequenceItemRenderer } from 'application/render/renderers/sequence/ChemSequenceItemRenderer'; -import { PhosphateSequenceItemRenderer } from 'application/render/renderers/sequence/PhosphateSequenceItemRenderer'; -import { NucleotideSequenceItemRenderer } from 'application/render/renderers/sequence/NucleotideSequenceItemRenderer'; +import { + Chem, + Peptide, + Phosphate, + Vec2, + Nucleotide, + Nucleoside, + EmptySequenceNode, + LinkerSequenceNode, + UnresolvedMonomer, +} from 'domain/entities'; +import { + PeptideSequenceItemRenderer, + ChemSequenceItemRenderer, + PhosphateSequenceItemRenderer, + NucleotideSequenceItemRenderer, + EmptySequenceItemRenderer, + BaseMonomerRenderer, + BaseSequenceItemRenderer, + NucleosideSequenceItemRenderer, + UnresolvedMonomerSequenceItemRenderer, +} from 'application/render'; import { SubChainNode } from 'domain/entities/monomer-chains/types'; -import { Nucleotide } from 'domain/entities/Nucleotide'; -import { Nucleoside } from 'domain/entities/Nucleoside'; import { BaseSubChain } from 'domain/entities/monomer-chains/BaseSubChain'; -import { EmptySequenceNode } from 'domain/entities/EmptySequenceNode'; -import { EmptySequenceItemRenderer } from 'application/render/renderers/sequence/EmptySequenceItemRenderer'; -import { BaseMonomerRenderer } from 'application/render'; -import { BaseSequenceItemRenderer } from 'application/render/renderers/sequence/BaseSequenceItemRenderer'; -import { LinkerSequenceNode } from 'domain/entities/LinkerSequenceNode'; -import { NucleosideSequenceItemRenderer } from './NucleosideSequenceItemRenderer'; export class SequenceNodeRendererFactory { static fromNode( @@ -50,6 +59,9 @@ export class SequenceNodeRendererFactory { case Chem: RendererClass = ChemSequenceItemRenderer; break; + case UnresolvedMonomer: + RendererClass = UnresolvedMonomerSequenceItemRenderer; + break; default: RendererClass = ChemSequenceItemRenderer; break; diff --git a/packages/ketcher-core/src/application/render/renderers/sequence/UnresolvedMonomerSequenceItemRenderer.ts b/packages/ketcher-core/src/application/render/renderers/sequence/UnresolvedMonomerSequenceItemRenderer.ts new file mode 100644 index 0000000000..2c408fa139 --- /dev/null +++ b/packages/ketcher-core/src/application/render/renderers/sequence/UnresolvedMonomerSequenceItemRenderer.ts @@ -0,0 +1,11 @@ +import { BaseSequenceItemRenderer } from 'application/render/renderers/sequence/BaseSequenceItemRenderer'; + +export class UnresolvedMonomerSequenceItemRenderer extends BaseSequenceItemRenderer { + get symbolToDisplay(): string { + return '?'; + } + + protected drawModification(): void { + return undefined; + } +} diff --git a/packages/ketcher-core/src/application/render/renderers/sequence/index.ts b/packages/ketcher-core/src/application/render/renderers/sequence/index.ts index 3922aa5b05..fb3304da49 100644 --- a/packages/ketcher-core/src/application/render/renderers/sequence/index.ts +++ b/packages/ketcher-core/src/application/render/renderers/sequence/index.ts @@ -1,2 +1,14 @@ export * from './SequenceRenderer'; export * from './BaseSequenceItemRenderer'; +export * from './BackBoneBondSequenceRenderer'; +export * from './BaseSequenceRenderer'; +export * from './ChemSequenceItemRenderer'; +export * from './EmptySequenceItemRenderer'; +export * from './NucleotideSequenceItemRenderer'; +export * from './NucleosideSequenceItemRenderer'; +export * from './PeptideSequenceItemRenderer'; +export * from './PhosphateSequenceItemRenderer'; +export * from './PolymerBondSequenceRenderer'; +export * from './RNASequenceItemRenderer'; +export * from './SequenceNodeRendererFactory'; +export * from './UnresolvedMonomerSequenceItemRenderer'; diff --git a/packages/ketcher-core/src/domain/entities/UnresolvedMonomer.ts b/packages/ketcher-core/src/domain/entities/UnresolvedMonomer.ts new file mode 100644 index 0000000000..40de09becc --- /dev/null +++ b/packages/ketcher-core/src/domain/entities/UnresolvedMonomer.ts @@ -0,0 +1,24 @@ +import { BaseMonomer, Peptide } from 'domain/entities'; +import { ChemSubChain } from 'domain/entities/monomer-chains/ChemSubChain'; +import { PeptideSubChain } from 'domain/entities/monomer-chains/PeptideSubChain'; +import { SubChainNode } from 'domain/entities/monomer-chains/types'; + +export class UnresolvedMonomer extends BaseMonomer { + public getValidSourcePoint(monomer?: BaseMonomer) { + return Peptide.prototype.getValidSourcePoint.call(this, monomer); + } + + public getValidTargetPoint(monomer: BaseMonomer) { + return Peptide.prototype.getValidTargetPoint.call(this, monomer); + } + + public get SubChainConstructor() { + return ChemSubChain; + } + + public isMonomerTypeDifferentForChaining(monomerToChain: SubChainNode) { + return ![PeptideSubChain, ChemSubChain].includes( + monomerToChain.SubChainConstructor, + ); + } +} diff --git a/packages/ketcher-core/src/domain/entities/index.ts b/packages/ketcher-core/src/domain/entities/index.ts index cc618dc2cf..a9588b0c30 100644 --- a/packages/ketcher-core/src/domain/entities/index.ts +++ b/packages/ketcher-core/src/domain/entities/index.ts @@ -48,3 +48,6 @@ export * from './Nucleoside'; export * from './Nucleotide'; export * from './monomer-chains/types'; export * from './MonomerSequenceNode'; +export * from './EmptySequenceNode'; +export * from './LinkerSequenceNode'; +export * from './UnresolvedMonomer'; diff --git a/packages/ketcher-core/src/domain/entities/monomer-chains/Chain.ts b/packages/ketcher-core/src/domain/entities/monomer-chains/Chain.ts index c63b26b64c..dbc834f6ae 100644 --- a/packages/ketcher-core/src/domain/entities/monomer-chains/Chain.ts +++ b/packages/ketcher-core/src/domain/entities/monomer-chains/Chain.ts @@ -5,6 +5,7 @@ import { Phosphate, SubChainNode, Sugar, + UnresolvedMonomer, } from 'domain/entities'; import { getNextMonomerInChain, @@ -49,6 +50,11 @@ export class Chain { public add(monomer: BaseMonomer) { this.createSubChainIfNeed(monomer); + if (monomer instanceof UnresolvedMonomer) { + this.lastSubChain.add(new MonomerSequenceNode(monomer)); + return; + } + if (monomer instanceof Sugar) { if (isValidNucleoside(monomer, this.firstMonomer)) { this.lastSubChain.add(Nucleoside.fromSugar(monomer, false)); @@ -59,10 +65,12 @@ export class Chain { return; } } + if (monomer instanceof Peptide) { this.lastSubChain.add(new MonomerSequenceNode(monomer)); return; } + const nextMonomer = getNextMonomerInChain(monomer); const isNextMonomerNucleosideOrNucleotideOrPeptide = () => { const isNucleosideOrNucleotide = diff --git a/packages/ketcher-core/src/domain/entities/monomer-chains/ChainsCollection.ts b/packages/ketcher-core/src/domain/entities/monomer-chains/ChainsCollection.ts index 91eb166233..de6d7b867c 100644 --- a/packages/ketcher-core/src/domain/entities/monomer-chains/ChainsCollection.ts +++ b/packages/ketcher-core/src/domain/entities/monomer-chains/ChainsCollection.ts @@ -7,6 +7,7 @@ import { Phosphate, RNABase, Sugar, + UnresolvedMonomer, } from 'domain/entities'; import { getNextMonomerInChain, @@ -102,7 +103,8 @@ export class ChainsCollection { | typeof Phosphate | typeof Sugar | typeof RNABase - > = [Peptide, Chem, Phosphate, Sugar, RNABase], + | typeof UnresolvedMonomer + > = [Peptide, Chem, Phosphate, Sugar, RNABase, UnresolvedMonomer], ) { const monomersList = monomers.filter((monomer) => MonomerTypes.some((MonomerType) => monomer instanceof MonomerType), diff --git a/packages/ketcher-core/src/domain/serializers/ket/fromKet/monomerToDrawingEntity.ts b/packages/ketcher-core/src/domain/serializers/ket/fromKet/monomerToDrawingEntity.ts index ff62788dff..7b566df587 100644 --- a/packages/ketcher-core/src/domain/serializers/ket/fromKet/monomerToDrawingEntity.ts +++ b/packages/ketcher-core/src/domain/serializers/ket/fromKet/monomerToDrawingEntity.ts @@ -17,6 +17,7 @@ export function templateToMonomerProps(template: IKetMonomerTemplate) { MonomerClass: template.class, MonomerCaps: {}, idtAliases: template.idtAliases, + unresolved: template.unresolved, }; } diff --git a/packages/ketcher-core/src/domain/serializers/ket/ketSerializer.ts b/packages/ketcher-core/src/domain/serializers/ket/ketSerializer.ts index 3b066ad397..54be506a10 100644 --- a/packages/ketcher-core/src/domain/serializers/ket/ketSerializer.ts +++ b/packages/ketcher-core/src/domain/serializers/ket/ketSerializer.ts @@ -325,6 +325,10 @@ export class KetSerializer implements Serializer { template: IKetMonomerTemplate, monomerItem: MonomerItemType, ) { + if (monomerItem.props.unresolved) { + return; + } + const { attachmentPointsList } = BaseMonomer.getAttachmentPointDictFromMonomerDefinition( template.attachmentPoints || [], diff --git a/packages/ketcher-core/src/domain/types/monomers.ts b/packages/ketcher-core/src/domain/types/monomers.ts index 0802155b20..cc4351f91e 100644 --- a/packages/ketcher-core/src/domain/types/monomers.ts +++ b/packages/ketcher-core/src/domain/types/monomers.ts @@ -38,6 +38,7 @@ export type MonomerItemType = { MonomerClass?: string; isMicromoleculeFragment?: boolean; idtAliases?: IKetIdtAliases; + unresolved?: boolean; }; attachmentPoints?: IKetAttachmentPoint[]; seqId?: number; diff --git a/packages/ketcher-macromolecules/src/Editor.tsx b/packages/ketcher-macromolecules/src/Editor.tsx index 238d030a6f..6cabc4a9f3 100644 --- a/packages/ketcher-macromolecules/src/Editor.tsx +++ b/packages/ketcher-macromolecules/src/Editor.tsx @@ -71,6 +71,7 @@ import { SugarAvatar, PhosphateAvatar, RNABaseAvatar, + UnresolvedMonomerAvatar, } from 'components/shared/monomerOnCanvas'; import { MonomerConnectionOnlyProps } from 'components/modal/modalContainer/types'; import { ErrorModal } from 'components/modal/Error'; @@ -372,6 +373,7 @@ function Editor({ theme, togglerComponent }: EditorProps) { + diff --git a/packages/ketcher-macromolecules/src/components/shared/MonomerPreview/index.tsx b/packages/ketcher-macromolecules/src/components/shared/MonomerPreview/index.tsx index 849f65e81d..aa13f20547 100644 --- a/packages/ketcher-macromolecules/src/components/shared/MonomerPreview/index.tsx +++ b/packages/ketcher-macromolecules/src/components/shared/MonomerPreview/index.tsx @@ -20,6 +20,7 @@ import { preview } from '../../../constants'; import styled from '@emotion/styled'; import { useAppSelector } from 'hooks'; import { selectShowPreview } from 'state/common'; +import UnresolvedMonomerPreview from 'components/shared/UnresolvedMonomerPreview/UnresolvedMonomerPreview'; const MonomerPreview = ({ className }: IPreviewProps) => { const preview = useAppSelector(selectShowPreview); @@ -39,20 +40,26 @@ const MonomerPreview = ({ className }: IPreviewProps) => { data-testid="polymer-library-preview" > {preview.monomer.struct.name} - + {preview.monomer.props?.unresolved ? ( + + ) : ( + + )} ) ); }; -const StyledPreview = styled(MonomerPreview)` +const StyledPreview = styled(MonomerPreview)` z-index: 5; position: absolute; - width: ${preview.width}px; - height: ${preview.height}px; + width: ${(props) => + props.unresolvedMonomer ? 'auto' : preview.width + 'px'}; + height: ${(props) => + props.unresolvedMonomer ? 'auto' : preview.height + 'px'}; transform: translate(-50%, 0); `; diff --git a/packages/ketcher-macromolecules/src/components/shared/MonomerPreview/types.ts b/packages/ketcher-macromolecules/src/components/shared/MonomerPreview/types.ts index 83255788e1..a0b13acd69 100644 --- a/packages/ketcher-macromolecules/src/components/shared/MonomerPreview/types.ts +++ b/packages/ketcher-macromolecules/src/components/shared/MonomerPreview/types.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. ***************************************************************************/ - export interface IPreviewProps { className?: string; + unresolvedMonomer?: boolean; } diff --git a/packages/ketcher-macromolecules/src/components/shared/Preview/index.tsx b/packages/ketcher-macromolecules/src/components/shared/Preview/index.tsx index 1916fb4ee1..8c9474074d 100644 --- a/packages/ketcher-macromolecules/src/components/shared/Preview/index.tsx +++ b/packages/ketcher-macromolecules/src/components/shared/Preview/index.tsx @@ -38,5 +38,10 @@ export const Preview = () => { return ; } - return ; + return ( + + ); }; diff --git a/packages/ketcher-macromolecules/src/components/shared/UnresolvedMonomerPreview/UnresolvedMonomerPreview.tsx b/packages/ketcher-macromolecules/src/components/shared/UnresolvedMonomerPreview/UnresolvedMonomerPreview.tsx new file mode 100644 index 0000000000..28fb89396d --- /dev/null +++ b/packages/ketcher-macromolecules/src/components/shared/UnresolvedMonomerPreview/UnresolvedMonomerPreview.tsx @@ -0,0 +1,13 @@ +import { Icon } from 'ketcher-react'; +import { StyledContent } from 'components/shared/UnresolvedMonomerPreview/styles'; + +const UnresolvedMonomerPreview = () => { + return ( + + + Unknown structure + + ); +}; + +export default UnresolvedMonomerPreview; diff --git a/packages/ketcher-macromolecules/src/components/shared/UnresolvedMonomerPreview/styles.ts b/packages/ketcher-macromolecules/src/components/shared/UnresolvedMonomerPreview/styles.ts new file mode 100644 index 0000000000..6dcbaae3da --- /dev/null +++ b/packages/ketcher-macromolecules/src/components/shared/UnresolvedMonomerPreview/styles.ts @@ -0,0 +1,14 @@ +import styled from '@emotion/styled'; + +export const StyledContent = styled.div` + width: 70px; + height: 77px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 8px; + padding: 8px; + color: #7c7c7f; + border: 1px solid #cad3dd; +`; diff --git a/packages/ketcher-macromolecules/src/components/shared/monomerOnCanvas/UnresolvedMonomer.tsx b/packages/ketcher-macromolecules/src/components/shared/monomerOnCanvas/UnresolvedMonomer.tsx new file mode 100644 index 0000000000..8f3ddc8d83 --- /dev/null +++ b/packages/ketcher-macromolecules/src/components/shared/monomerOnCanvas/UnresolvedMonomer.tsx @@ -0,0 +1,66 @@ +/**************************************************************************** + * 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 const UnresolvedMonomerAvatar = () => ( + <> + + + + + + + + + + + +); diff --git a/packages/ketcher-macromolecules/src/components/shared/monomerOnCanvas/index.ts b/packages/ketcher-macromolecules/src/components/shared/monomerOnCanvas/index.ts index 669ceaccad..f791e51cb1 100644 --- a/packages/ketcher-macromolecules/src/components/shared/monomerOnCanvas/index.ts +++ b/packages/ketcher-macromolecules/src/components/shared/monomerOnCanvas/index.ts @@ -3,3 +3,4 @@ export { PeptideAvatar } from './Peptide'; export { SugarAvatar } from './Sugar'; export { PhosphateAvatar } from './Phosphate'; export { RNABaseAvatar } from './RNABase'; +export { UnresolvedMonomerAvatar } from './UnresolvedMonomer'; diff --git a/packages/ketcher-react/src/assets/icons/files/questionMark.svg b/packages/ketcher-react/src/assets/icons/files/questionMark.svg new file mode 100644 index 0000000000..5b27f84f49 --- /dev/null +++ b/packages/ketcher-react/src/assets/icons/files/questionMark.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ketcher-react/src/components/Icon/utils/iconNameToIcon.ts b/packages/ketcher-react/src/components/Icon/utils/iconNameToIcon.ts index f045539876..4341ae24f7 100644 --- a/packages/ketcher-react/src/components/Icon/utils/iconNameToIcon.ts +++ b/packages/ketcher-react/src/components/Icon/utils/iconNameToIcon.ts @@ -222,6 +222,7 @@ import ExplicitHydrogensIcon from '../../../assets/icons/files/explicit-hydrogen import FlexLayoutIcon from '../../../assets/icons/files/flex-layout-mode.svg'; import SnakeLayoutIcon from '../../../assets/icons/files/snake-layout-mode.svg'; import SequenceLayoutIcon from '../../../assets/icons/files/sequence-layout-mode.svg'; +import QuestionMark from '../../../assets/icons/files/questionMark.svg'; export const iconNameToIcon = { α, @@ -444,4 +445,5 @@ export const iconNameToIcon = { 'flex-layout-mode': FlexLayoutIcon, 'snake-layout-mode': SnakeLayoutIcon, 'sequence-layout-mode': SequenceLayoutIcon, + questionMark: QuestionMark, } as const;