Skip to content

Commit

Permalink
#4388 - Macro: Allow Nucleoside of sequence edit in RNA builder
Browse files Browse the repository at this point in the history
  • Loading branch information
ilya-asiyuk-epam committed Apr 18, 2024
1 parent ca3b83f commit 65ac94b
Show file tree
Hide file tree
Showing 55 changed files with 651 additions and 196 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,66 @@ test.describe('Sequence mode edit in RNA Builder', () => {
await takePageScreenshot(page);
});

test('Select nucleoside and phosphate and modify sugar and phosphate as it one nucleotide', async ({
page,
}) => {
// Coordinates for rectangle selection
const startX = 300;
const startY = 100;
const endX = 400;
const endY = 200;
await selectRectangleArea(page, startX, startY, endX, endY);
await clickOnSequenceSymbol(page, 'T', { button: 'right', nthNumber: 2 });
// should see correct context menu title and available 'modify_in_rna_builder' button
await takeEditorScreenshot(page);
await page.getByTestId('modify_in_rna_builder').click();
// should see uploaded nucleotide (nucleoside + phosphate) data to RNA Builder and disabled "Update" button
await takeRNABuilderScreenshot(page);
// Update Sugar
await page.getByTestId(SUGAR).click();
await page.getByTestId('25R___2,5-Ribose').click();
await moveMouseAway(page);
// Update Phosphate
await page.getByTestId(PHOSPHATE).click();
await page.getByTestId('bP___Boranophosphate').click();
await moveMouseAway(page);
// should see updated sugar and phosphate, updated title of preset and nondisabled "Update" button
await takeRNABuilderScreenshot(page);
await page.getByTestId('save-btn').click();
await takePageScreenshot(page);
});

test('Select nucleotide, nucleoside and modify sugar and phosphate. nucleoside should become nucleotide', async ({
page,
}) => {
// Coordinates for rectangle selection
const startX = 280;
const startY = 100;
const endX = 320;
const endY = 200;
await selectRectangleArea(page, startX, startY, endX, endY);
await clickOnSequenceSymbol(page, 'T', { button: 'right', nthNumber: 2 });
// should see correct context menu title and available 'modify_in_rna_builder' button
await takeEditorScreenshot(page);
await page.getByTestId('modify_in_rna_builder').click();
// should see uploaded data to RNA Builder and disabled "Update" button
await takeRNABuilderScreenshot(page);
// Update Sugar
await page.getByTestId(SUGAR).click();
await page.getByTestId('25R___2,5-Ribose').click();
await moveMouseAway(page);
// Update Phosphate
await page.getByTestId(PHOSPHATE).click();
await page.getByTestId('bP___Boranophosphate').click();
await moveMouseAway(page);
// should see updated sugar and phosphate of preset and nondisabled "Update" button
await takeRNABuilderScreenshot(page);
await page.getByTestId('save-btn').click();
// Click 'Yes' in modal
await page.getByText('Yes').click();
await takePageScreenshot(page);
});

test('Select one nucleotide and cancel modification', async ({ page }) => {
await clickOnSequenceSymbol(page, 'T');
await clickOnSequenceSymbol(page, 'T', { button: 'right' });
Expand Down Expand Up @@ -93,7 +153,7 @@ test.describe('Sequence mode edit in RNA Builder', () => {
await takeEditorScreenshot(page);
});

test('Select entire chain and see disabled modify_in_rna_builder button', async ({
test('Select entire chain and see enabled modify_in_rna_builder button', async ({
page,
}) => {
await page.keyboard.down('Control');
Expand All @@ -102,7 +162,7 @@ test.describe('Sequence mode edit in RNA Builder', () => {
// should see the whole chain selected
await takeEditorScreenshot(page);
await clickOnSequenceSymbol(page, 'T', { button: 'right' });
// should see correct context menu title and disabled 'modify_in_rna_builder' button
// should see correct context menu title and enabled 'modify_in_rna_builder' button
await takeEditorScreenshot(page);
});
});
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions ketcher-autotests/tests/utils/macromolecules/sequence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { Page } from '@playwright/test';
export async function clickOnSequenceSymbol(
page: Page,
symbolText: string,
clickOptions?: { button: 'right' | 'left' },
clickOptions?: { button?: 'right' | 'left'; nthNumber?: number },
) {
const symbolLocator = await getSequenceSymbolLocator(page, symbolText);
const symbolLocator = getSequenceSymbolLocator(
page,
symbolText,
clickOptions?.nthNumber,
);
await symbolLocator.click(clickOptions);
}

Expand Down
6 changes: 3 additions & 3 deletions packages/ketcher-core/src/application/editor/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { SequenceType, Struct, Vec2 } from 'domain/entities';
import {
BaseTool,
IRnaPreset,
LabeledNucleotideWithPositionInSequence,
LabeledNodesWithPositionInSequence,
isBaseTool,
Tool,
ToolConstructorInterface,
Expand Down Expand Up @@ -197,7 +197,7 @@ export class CoreEditor {
this.onTurnOffSequenceEditInRNABuilderMode(),
);
this.events.modifySequenceInRnaBuilder.add(
(updatedSelection: LabeledNucleotideWithPositionInSequence[]) =>
(updatedSelection: LabeledNodesWithPositionInSequence[]) =>
this.onModifySequenceInRnaBuilder(updatedSelection),
);
this.events.changeSequenceTypeEnterMode.add((mode: SequenceType) =>
Expand Down Expand Up @@ -238,7 +238,7 @@ export class CoreEditor {
}

private onModifySequenceInRnaBuilder(
updatedSelection: LabeledNucleotideWithPositionInSequence[],
updatedSelection: LabeledNodesWithPositionInSequence[],
) {
if (!(this.mode instanceof SequenceMode)) {
return;
Expand Down
146 changes: 109 additions & 37 deletions packages/ketcher-core/src/application/editor/modes/SequenceMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
NodesSelection,
SequenceRenderer,
} from 'application/render/renderers/sequence/SequenceRenderer';
import { AttachmentPointName, MonomerItemType } from 'domain/types';
import { AttachmentPointName } from 'domain/types';
import { Command } from 'domain/entities/Command';
import { BaseMonomer, SequenceType, Vec2 } from 'domain/entities';
import { BaseMonomer, Phosphate, SequenceType, 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 +34,7 @@ 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 { LabeledNucleotideWithPositionInSequence } from 'application/editor/tools/Tool';
import { LabeledNodesWithPositionInSequence } from 'application/editor/tools/Tool';

const naturalAnalogues = uniq([
...rnaDnaNaturalAnalogues,
Expand Down Expand Up @@ -154,53 +154,125 @@ export class SequenceMode extends BaseMode {
}

public modifySequenceInRnaBuilder(
updatedSelection: LabeledNucleotideWithPositionInSequence[],
updatedSelection: LabeledNodesWithPositionInSequence[],
) {
const editor = CoreEditor.provideEditorInstance();
const history = new EditorHistory(editor);
const modelChanges = new Command();

// Update Nucleotides one by one
for (const labeledNucleotide of updatedSelection) {
const nodeIndexOverall = labeledNucleotide.nodeIndexOverall;
for (const labeledNucleoelement of updatedSelection) {
const nodeIndexOverall = labeledNucleoelement.nodeIndexOverall;

if (nodeIndexOverall === undefined) return;

// Create monomerItem(s) based on label
const sugarMonomerItem = getRnaPartLibraryItem(
editor,
labeledNucleotide.sugarLabel,
);
const baseMonomerItem = getRnaPartLibraryItem(
editor,
labeledNucleotide.baseLabel,
);
const phosphateMonomerItem = getRnaPartLibraryItem(
editor,
labeledNucleotide.phosphateLabel,
);
let sugarMonomerItem;
let baseMonomerItem;
let phosphateMonomerItem;
if (labeledNucleoelement.sugarLabel) {
sugarMonomerItem = getRnaPartLibraryItem(
editor,
labeledNucleoelement.sugarLabel,
);
}
if (labeledNucleoelement.baseLabel) {
baseMonomerItem = getRnaPartLibraryItem(
editor,
labeledNucleoelement.baseLabel,
);
}
if (labeledNucleoelement.phosphateLabel) {
phosphateMonomerItem = getRnaPartLibraryItem(
editor,
labeledNucleoelement.phosphateLabel,
);
}

const currentNode = SequenceRenderer.getNodeByPointer(nodeIndexOverall);

// Update monomerItem objects
modelChanges.merge(
editor.drawingEntitiesManager.modifyMonomerItem(
currentNode.sugar,
sugarMonomerItem as MonomerItemType,
),
);
modelChanges.merge(
editor.drawingEntitiesManager.modifyMonomerItem(
currentNode.rnaBase,
baseMonomerItem as MonomerItemType,
),
);
modelChanges.merge(
editor.drawingEntitiesManager.modifyMonomerItem(
currentNode.phosphate,
phosphateMonomerItem as MonomerItemType,
),
);
// Update Sugar monomerItem object
if (currentNode.sugar && sugarMonomerItem) {
modelChanges.merge(
editor.drawingEntitiesManager.modifyMonomerItem(
currentNode.sugar,
sugarMonomerItem,
),
);
}
// Update Base monomerItem object
if (currentNode.rnaBase && baseMonomerItem) {
modelChanges.merge(
editor.drawingEntitiesManager.modifyMonomerItem(
currentNode.rnaBase,
baseMonomerItem,
),
);
}

// Update monomerItem object or add Phosphate
if (phosphateMonomerItem) {
// Update Phosphate monomerItem object for Nucleotide
if (currentNode instanceof Nucleotide) {
modelChanges.merge(
editor.drawingEntitiesManager.modifyMonomerItem(
currentNode.phosphate,
phosphateMonomerItem,
),
);
// Add Phosphate to Nucleoside
} else if (currentNode instanceof Nucleoside) {
const sugarR2 = currentNode.sugar.attachmentPointsToBonds.R2;
const nextMonomerInSameChain = sugarR2?.secondMonomer;
const newPhosphateCreationCommand =
editor.drawingEntitiesManager.addMonomer(
phosphateMonomerItem,
new Vec2(0, 0),
);
const newPhosphate =
newPhosphateCreationCommand.operations[0].monomer;

if (!newPhosphate) continue;

modelChanges.merge(newPhosphateCreationCommand);
// Remove existing bond connection between Nucleoside Sugar and next node in case of any
if (sugarR2) {
modelChanges.merge(
editor.drawingEntitiesManager.deletePolymerBond(sugarR2),
);
}
// Connect Nucleoside Sugar with new Phosphate
modelChanges.merge(
editor.drawingEntitiesManager.createPolymerBond(
currentNode.sugar,
newPhosphate,
'R2',
'R1',
),
);
if (nextMonomerInSameChain) {
if (nextMonomerInSameChain) {
// Connect new Phosphate with next node in case of any
modelChanges.merge(
editor.drawingEntitiesManager.createPolymerBond(
newPhosphate,
nextMonomerInSameChain,
'R2',
'R1',
),
);
}
}
// Update Phosphate monomerItem object
} else if (currentNode.monomer instanceof Phosphate) {
modelChanges.merge(
editor.drawingEntitiesManager.modifyMonomerItem(
currentNode.monomer,
phosphateMonomerItem,
),
);
}
}
}

// Refresh UI
Expand Down
11 changes: 7 additions & 4 deletions packages/ketcher-core/src/application/editor/tools/Tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,14 @@ export interface IRnaPreset {
presetInList?: IRnaPreset;
}

export type LabeledNucleotideWithPositionInSequence = {
baseLabel: string;
sugarLabel: string;
phosphateLabel: string;
export type LabeledNodesWithPositionInSequence = {
type: 'Phosphate' | 'Nucleotide' | 'Nucleoside';
nodeIndexOverall: number;
baseLabel?: string;
sugarLabel?: string;
phosphateLabel?: string;
isNucleosideConnectedAndSelectedWithPhosphate?: boolean;
hasR1Connection?: boolean;
};

export interface Tool extends ToolEventHandler {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { ChainsCollection } from 'domain/entities/monomer-chains/ChainsCollection';
import { SequenceNodeRendererFactory } from 'application/render/renderers/sequence/SequenceNodeRendererFactory';
import { BaseMonomer, RNABase, Sugar, Vec2 } from 'domain/entities';
import {
BaseMonomer,
Nucleotide,
Phosphate,
RNABase,
Sugar,
Vec2,
} from 'domain/entities';
import { AttachmentPointName } from 'domain/types';
import { PolymerBondSequenceRenderer } from 'application/render/renderers/sequence/PolymerBondSequenceRenderer';
import {
getNextMonomerInChain,
getRnaBaseFromSugar,
getSugarFromRnaBase,
} from 'domain/helpers/monomers';
import { Nucleoside } from 'domain/entities/Nucleoside';
import { BackBoneBondSequenceRenderer } from 'application/render/renderers/sequence/BackBoneBondSequenceRenderer';
import { PolymerBond } from 'domain/entities/PolymerBond';
import { BaseSequenceItemRenderer } from 'application/render/renderers/sequence/BaseSequenceItemRenderer';
Expand All @@ -28,6 +37,8 @@ export type SequencePointer = number;
export type NodeSelection = {
node: SubChainNode;
nodeIndexOverall: number;
isNucleosideConnectedAndSelectedWithPhosphate?: boolean;
hasR1Connection?: boolean;
};

export type NodesSelection = NodeSelection[][];
Expand Down Expand Up @@ -634,16 +645,38 @@ export class SequenceRenderer {
}

public static get selections() {
const editor = CoreEditor.provideEditorInstance();
const selections: NodesSelection = [];
let lastSelectionRangeIndex = -1;
let previousNode;

SequenceRenderer.forEachNode(({ node, nodeIndexOverall }) => {
if (node.monomer.selected) {
const selection: Partial<NodeSelection> = {};

// Add field 'isNucleosideConnectedAndSelectedWithPhosphate' to the Nucleoside elements
if (node instanceof Nucleoside) {
const nextMonomer = getNextMonomerInChain(node.sugar);

selection.isNucleosideConnectedAndSelectedWithPhosphate =
nextMonomer instanceof Phosphate &&
nextMonomer.selected &&
editor.drawingEntitiesManager.isNucleosideAndPhosphateConnectedAsNucleotide(
node,
nextMonomer,
);
}

// Add field 'hasR1Connection' to the Nucleotide/Nucleoside elements
if (node instanceof Nucleotide || node instanceof Nucleoside) {
selection.hasR1Connection = !!node.sugar.attachmentPointsToBonds.R1;
}

if (!previousNode?.monomer.selected) {
lastSelectionRangeIndex = selections.push([]) - 1;
}
selections[lastSelectionRangeIndex].push({
...selection,
node,
nodeIndexOverall,
});
Expand Down
Loading

0 comments on commit 65ac94b

Please sign in to comment.