Skip to content

Commit

Permalink
#4878 – Add/Replace monomer in sequence mode from library
Browse files Browse the repository at this point in the history
  • Loading branch information
svvald committed Jul 31, 2024
1 parent 6a4af1a commit 05c1f77
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 37 deletions.
16 changes: 12 additions & 4 deletions packages/ketcher-core/src/application/editor/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,14 +271,22 @@ export class CoreEditor {
}

private onSelectMonomer(monomer: MonomerItemType) {
this.selectTool('monomer', monomer);
if (this.mode instanceof SequenceMode) {
this.mode.insertMonomerFromLibrary(monomer);
} else {
this.selectTool('monomer', monomer);
}
}

private onSelectRNAPreset(preset: IRnaPreset) {
if (preset) {
this.selectTool('preset', preset);
if (this.mode instanceof SequenceMode) {
this.mode.insertPresetFromLibrary(preset);
} else {
this.tool = undefined;
if (preset) {
this.selectTool('preset', preset);
} else {
this.tool = undefined;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export function resetEditorEvents() {
mousedownBetweenSequenceItems: new Subscription(),
mouseDownOnSequenceItem: new Subscription(),
doubleClickOnSequenceItem: new Subscription(),
openConfirmationDialog: new Subscription(),
};
}
resetEditorEvents();
Expand Down
185 changes: 182 additions & 3 deletions packages/ketcher-core/src/application/editor/modes/SequenceMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ import { BaseMode } from 'application/editor/modes/BaseMode';
import ZoomTool from 'application/editor/tools/Zoom';
import { BaseSequenceItemRenderer } from 'application/render/renderers/sequence/BaseSequenceItemRenderer';
import {
NodeSelection,
NodesSelection,
SequenceRenderer,
} from 'application/render/renderers/sequence/SequenceRenderer';
import { AttachmentPointName } from 'domain/types';
import { AttachmentPointName, MonomerItemType } from 'domain/types';
import { Command } from 'domain/entities/Command';
import { BaseMonomer, Phosphate, SequenceType, Vec2 } from 'domain/entities';
import {
BaseMonomer,
LinkerSequenceNode,
Phosphate,
RNABase,
SequenceType,
Sugar,
Vec2,
} from 'domain/entities';
import { BaseRenderer } from 'application/render/renderers/internal';
import { EmptySequenceNode } from 'domain/entities/EmptySequenceNode';
import { Nucleoside } from 'domain/entities/Nucleoside';
Expand All @@ -34,7 +43,10 @@ import { ChainsCollection } from 'domain/entities/monomer-chains/ChainsCollectio
import { DrawingEntitiesManager } from 'domain/entities/DrawingEntitiesManager';
import { Chain } from 'domain/entities/monomer-chains/Chain';
import { MonomerSequenceNode } from 'domain/entities/MonomerSequenceNode';
import { LabeledNodesWithPositionInSequence } from 'application/editor/tools/Tool';
import {
IRnaPreset,
LabeledNodesWithPositionInSequence,
} from 'application/editor/tools/Tool';
import { NewSequenceButton } from 'application/render/renderers/sequence/ui-controls/NewSequenceButton';

const naturalAnalogues = uniq([
Expand Down Expand Up @@ -1008,6 +1020,173 @@ export class SequenceMode extends BaseMode {
return modelChanges;
}

private replaceSelectionWithMonomer(
monomerItem: MonomerItemType,
selection: NodeSelection,
modelChanges: Command,
) {
const editor = CoreEditor.provideEditorInstance();
const previousNode = SequenceRenderer.getPreviousNode(selection.node);
const nextNode = SequenceRenderer.getNextNode(selection.node);
const position = selection.node.monomer.position;

selection.node.monomers.forEach((monomer) => {
modelChanges.merge(editor.drawingEntitiesManager.deleteMonomer(monomer));
});

const monomerAddCommand = editor.drawingEntitiesManager.addMonomer(
monomerItem,
position,
);
const newMonomer = monomerAddCommand.operations[0].monomer as BaseMonomer;
const newMonomerSequenceNode = new MonomerSequenceNode(newMonomer);

modelChanges.merge(monomerAddCommand);
modelChanges.merge(this.insertNewSequenceFragment(newMonomerSequenceNode));

if (previousNode) {
modelChanges.merge(
this.tryToCreatePolymerBond(previousNode.lastMonomerInNode, newMonomer),
);
}
if (nextNode) {
modelChanges.merge(
this.tryToCreatePolymerBond(newMonomer, nextNode.firstMonomerInNode),
);
}
}

public insertMonomerFromLibrary(monomerItem: MonomerItemType) {
const editor = CoreEditor.provideEditorInstance();
const history = new EditorHistory(editor);
const modelChanges = new Command();
const selections = SequenceRenderer.selections;

if (selections.length > 0) {
selections.forEach((selectionRange) => {
selectionRange.forEach((selection) => {
if (selection.node instanceof LinkerSequenceNode) {
editor.events.openConfirmationDialog.dispatch({
confirmationText:
'Symbol @ can represent multiple monomers, all of them are going to be deleted. Do you want to proceed?',
onConfirm: () =>
this.replaceSelectionWithMonomer(
monomerItem,
selection,
modelChanges,
),
});
} else {
this.replaceSelectionWithMonomer(
monomerItem,
selection,
modelChanges,
);
}
});
});
} else {
const newNodePosition = this.getNewNodePosition();

const monomerAddCommand = editor.drawingEntitiesManager.addMonomer(
monomerItem,
newNodePosition,
);
const newMonomer = monomerAddCommand.operations[0].monomer as BaseMonomer;
const newMonomerSequenceNode = new MonomerSequenceNode(newMonomer);

modelChanges.merge(monomerAddCommand);
modelChanges.merge(
this.insertNewSequenceFragment(newMonomerSequenceNode),
);
}

modelChanges.addOperation(new ReinitializeModeOperation());
editor.renderersContainer.update(modelChanges);
SequenceRenderer.moveCaretForward();
history.update(modelChanges);
}

public insertPresetFromLibrary(preset: IRnaPreset) {
const editor = CoreEditor.provideEditorInstance();
const history = new EditorHistory(editor);
const modelChanges = new Command();
const currentNode = SequenceRenderer.currentEdittingNode;
const newNodePosition = this.getNewNodePosition();

const { base: rnaBase, sugar, phosphate } = preset;

let newPresetNode: Nucleotide | Nucleoside;
if (
currentNode instanceof Nucleotide ||
currentNode instanceof Nucleoside
) {
if (!rnaBase || !sugar || !phosphate) {
return;
}

const { command: presetAddCommand, monomers } =
editor.drawingEntitiesManager.addRnaPreset({
sugar,
sugarPosition: newNodePosition,
rnaBase,
rnaBasePosition: newNodePosition,
phosphate,
phosphatePosition: newNodePosition,
});

const sugarMonomer = monomers.find(
(monomer) => monomer instanceof Sugar,
) as Sugar;
const rnaBaseMonomer = monomers.find(
(monomer) => monomer instanceof RNABase,
) as RNABase;
const phosphateMonomer = monomers.find(
(monomer) => monomer instanceof Phosphate,
) as Phosphate;

modelChanges.merge(presetAddCommand);

newPresetNode = new Nucleotide(
sugarMonomer,
rnaBaseMonomer,
phosphateMonomer,
);
} else {
if (!rnaBase || !sugar) {
return;
}

modelChanges.merge(
editor.drawingEntitiesManager.addMonomer(sugar, newNodePosition),
);
modelChanges.merge(
editor.drawingEntitiesManager.addMonomer(rnaBase, newNodePosition),
);

const sugarMonomer = modelChanges.operations[0].monomer as Sugar;
const rnaBaseMonomer = modelChanges.operations[1].monomer as RNABase;

modelChanges.merge(
editor.drawingEntitiesManager.createPolymerBond(
sugarMonomer,
rnaBaseMonomer,
AttachmentPointName.R3,
AttachmentPointName.R1,
),
);

newPresetNode = new Nucleoside(sugarMonomer, rnaBaseMonomer);
}

modelChanges.merge(this.insertNewSequenceFragment(newPresetNode));

modelChanges.addOperation(new ReinitializeModeOperation());
editor.renderersContainer.update(modelChanges);
SequenceRenderer.moveCaretForward();
history.update(modelChanges);
}

private insertNewSequenceItem(editor: CoreEditor, enteredSymbol: string) {
const currentNode = SequenceRenderer.currentEdittingNode;
const newNodePosition = this.getNewNodePosition();
Expand Down
18 changes: 14 additions & 4 deletions packages/ketcher-macromolecules/src/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ import {
openModal,
selectErrorTooltipText,
} from 'state/modal';
import { ModalContainer } from 'components/modal/modalContainer';
import {
ConfirmationDialogOnlyProps,
ModalContainer,
} from 'components/modal/modalContainer';
import { DeepPartial } from './types';
import { EditorClassName } from 'ketcher-react';
import { Snackbar } from '@mui/material';
Expand Down Expand Up @@ -204,6 +207,15 @@ function Editor({ theme, togglerComponent }: EditorProps) {
}),
),
);
editor.events.openConfirmationDialog.add(
(additionalProps: ConfirmationDialogOnlyProps) =>
dispatch(
openModal({
name: 'confirmationDialog',
additionalProps,
}),
),
);
editor.events.selectTool.add(handler);
}

Expand Down Expand Up @@ -384,9 +396,7 @@ function Editor({ theme, togglerComponent }: EditorProps) {
</Layout.Main>

<Layout.Right hide={isMonomerLibraryHidden}>
<MonomerLibrary
isSequenceEditInRNABuilderMode={isSequenceEditInRNABuilderMode}
/>
<MonomerLibrary />
</Layout.Right>
</Layout>
<MonomerLibraryToggle
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import styled from '@emotion/styled';

export const ConfirmationText = styled.p`
padding: 12px;
font-size: 12px;
color: #000;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ConfirmationDialogProps } from 'components/modal/modalContainer';
import { Modal } from 'components/shared/modal';
import { ActionButton } from 'components/shared/actionButton';
import { ConfirmationText } from './ConfirmationDialog.styles';

export const ConfirmationDialog = ({
confirmationText,
onConfirm,
isModalOpen,
onClose,
}: ConfirmationDialogProps) => {
return (
<Modal isOpen={isModalOpen} title="Confirm your action" onClose={onClose}>
<Modal.Content>
<ConfirmationText>{confirmationText}</ConfirmationText>
</Modal.Content>
<Modal.Footer>
<ActionButton label="Cancel" clickHandler={onClose} />
<ActionButton label="Yes" clickHandler={onConfirm!} />
</Modal.Footer>
</Modal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ConfirmationDialog';
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,22 @@ import { Save } from 'components/modal/save';
import { Delete } from 'components/modal/Delete';
import { UpdateSequenceInRNABuilder } from 'components/modal/UpdateSequenceInRNABuilder';
import { MonomerConnection } from 'components/modal/monomerConnection';
import { ConfirmationDialog } from 'components/modal/ConfirmationDialog';
import { ModalName } from 'state/modal';

export const modalComponentList: Record<
string,
ModalName,
| typeof Open
| typeof Save
| typeof Delete
| typeof UpdateSequenceInRNABuilder
| typeof MonomerConnection
| typeof ConfirmationDialog
> = {
open: Open,
save: Save,
delete: Delete,
updateSequenceInRNABuilder: UpdateSequenceInRNABuilder,
monomerConnection: MonomerConnection,
confirmationDialog: ConfirmationDialog,
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@ export interface MonomerConnectionOnlyProps {
secondMonomer?: BaseMonomer;
}

export interface ConfirmationDialogOnlyProps {
confirmationText?: string;
onConfirm?: () => void;
}

export interface RequiredModalProps {
onClose: () => void;
isModalOpen: boolean;
}
export type MonomerConnectionProps = MonomerConnectionOnlyProps &
RequiredModalProps;

export type AdditionalModalProps = Partial<MonomerConnectionProps>;
export type ConfirmationDialogProps = ConfirmationDialogOnlyProps &
RequiredModalProps;

export type AdditionalModalProps = Partial<
MonomerConnectionProps & ConfirmationDialogProps
>;
Loading

0 comments on commit 05c1f77

Please sign in to comment.