From 72e422ac0344ed3ca9c3f3e0b3807878b72ab02c Mon Sep 17 00:00:00 2001 From: Kim Rutherford Date: Wed, 30 Oct 2024 14:54:14 +1300 Subject: [PATCH] Implement a manual residue selection box Refs pombase/website#2266 --- .../transcript-sequence-select.component.css | 14 ++++ .../transcript-sequence-select.component.html | 13 +++- .../transcript-sequence-select.component.ts | 70 ++++++++++++++++++- 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/app/transcript-sequence-select/transcript-sequence-select.component.css b/src/app/transcript-sequence-select/transcript-sequence-select.component.css index e3050cc4..e1b17c75 100644 --- a/src/app/transcript-sequence-select/transcript-sequence-select.component.css +++ b/src/app/transcript-sequence-select/transcript-sequence-select.component.css @@ -42,6 +42,7 @@ .selected-range { font-size: 120%; padding: 0 0.5em; + display: flex; } .seq-title { @@ -183,3 +184,16 @@ .dropdown-item a { text-decoration: none; } + +.manual-selection { + margin-left: auto; + font-size: 90% +} + +.manual-selection input { + width: 16em; +} + +.manual-selection-error { + color: red; +} diff --git a/src/app/transcript-sequence-select/transcript-sequence-select.component.html b/src/app/transcript-sequence-select/transcript-sequence-select.component.html index 47d238e6..08af3858 100644 --- a/src/app/transcript-sequence-select/transcript-sequence-select.component.html +++ b/src/app/transcript-sequence-select/transcript-sequence-select.component.html @@ -17,7 +17,7 @@ -
+
@@ -192,7 +192,17 @@ {{selectedResidueMessage()}}
 
+ +
+ Manually select residues +
+ Select range: + error: {{manualSelectionErrorMessage}} +
+
+
@@ -202,6 +212,7 @@ title="{{partTitle(part)}}" class="{{partClass(part)}}" #partSpan (mouseenter)="mouseenter(part)" (mouseleave)="mouseleave()" + (mousedown)="clearManualSelection()" [attr.data-part-id]="part.partId">{{part.residues}}
diff --git a/src/app/transcript-sequence-select/transcript-sequence-select.component.ts b/src/app/transcript-sequence-select/transcript-sequence-select.component.ts index ca40ea9c..2ae4de6b 100644 --- a/src/app/transcript-sequence-select/transcript-sequence-select.component.ts +++ b/src/app/transcript-sequence-select/transcript-sequence-select.component.ts @@ -1,4 +1,4 @@ -import { Component, OnChanges, Input, Inject } from '@angular/core'; +import { Component, OnChanges, Input, Inject, ElementRef, ViewChild } from '@angular/core'; import { saveAs } from 'file-saver'; @@ -6,6 +6,7 @@ import { GeneDetails, ProteinDetails, PombaseAPIService, Strand } from '../pomba import { Util } from '../shared/util'; import { DisplaySequence, DisplaySequencePart, ResidueRange } from '../display-sequence'; import { getAppConfig, replaceFieldsInUrl } from '../config'; +import { DeployConfigService } from '../deploy-config.service'; @Component({ selector: 'app-transcript-sequence-select', @@ -43,6 +44,12 @@ export class TranscriptSequenceSelectComponent implements OnChanges { upstreamBases = 0; downstreamBases = 0; + manualSelection?: string; + manualSelectionErrorMessage?: string; + manualSelectionVisible = false; + + @ViewChild('partSpan') partSpanElement: ElementRef; + linksInNewWindow = true; hoverPart?: DisplaySequencePart; @@ -57,6 +64,7 @@ export class TranscriptSequenceSelectComponent implements OnChanges { downloadWithFeaturesURL = getAppConfig().seq_and_features_download_url; constructor(private apiService: PombaseAPIService, + public deployConfigService: DeployConfigService, @Inject('Window') private window: Window) { } updateHeader(sequenceLength: number) { @@ -132,6 +140,11 @@ export class TranscriptSequenceSelectComponent implements OnChanges { return retVal; } + clearManualSelection(): void { + this.manualSelection = undefined; + this.manualSelectionErrorMessage = undefined; + } + mouseenter(part: DisplaySequencePart): void { if (this.showNucSequence) { this.hoverPart = part; @@ -142,7 +155,7 @@ export class TranscriptSequenceSelectComponent implements OnChanges { this.hoverPart = undefined; } - mousemove(): void { + checkSelection(): void { this.selectedResidueRange = undefined; const selection = this.window.getSelection(); @@ -169,6 +182,10 @@ export class TranscriptSequenceSelectComponent implements OnChanges { this.selectedResidueRange = displaySequence.rangeFromParts(+startPartId, startOffset, +endPartId, endOffset - 1); + if (this.selectedResidueRange) { + this.manualSelectionVisible = false; + this.manualSelection = undefined; + } } } } @@ -426,6 +443,55 @@ export class TranscriptSequenceSelectComponent implements OnChanges { this.updateSequence(); } + manualSelectionChanged(): void { + this.manualSelectionErrorMessage = undefined; + const selection = this.window.getSelection(); + if (selection) { + selection.removeAllRanges(); + } + + if (this.manualSelection && this.manualSelection.trim().length >= 0) { + const parts = /^(\d+)(?:(?:\.\.|--?)(\d+))?$/.exec(this.manualSelection.trim()); + + if (parts) { + let start = parseInt(parts[1], 10); + let end = undefined; + + const endMatch = parts[2]; + + if (endMatch) { + end = parseInt(endMatch, 10); + if (end < start) { + this.manualSelectionErrorMessage = 'end position < start'; + return; + } + } else { + end = start; + } + + if (start <= end && selection) { + const range = document.createRange(); + const residuesText = this.partSpanElement.nativeElement.firstChild; + if (end >= residuesText.length) { + this.manualSelectionErrorMessage = 'end > sequence length'; + return; + } + try { + range.setStart(residuesText, start-1); + range.setEnd(residuesText, end); + selection.addRange(range); + this.checkSelection(); + } + catch (e) { + console.log(e); + } + } + } else { + this.manualSelectionErrorMessage = 'invalid range - example "10..20"'; + } + } + } + selectedResidueMessage(): string { if (!this.selectedResidueRange) { return '';