|
@@ -6,16 +6,12 @@
|
|
|
*/
|
|
|
|
|
|
import * as React from 'react'
|
|
|
-import { Structure, StructureSequence, Queries, StructureSelection, StructureProperties as SP, StructureQuery, StructureElement, Unit } from '../../mol-model/structure';
|
|
|
-import { PluginUIComponent, PurePluginUIComponent } from './base';
|
|
|
+import { PluginUIComponent } from './base';
|
|
|
import { StateTreeSpine } from '../../mol-state/tree/spine';
|
|
|
import { PluginStateObject as SO } from '../state/objects';
|
|
|
-import { Interactivity } from '../util/interactivity';
|
|
|
-import { OrderedSet, Interval } from '../../mol-data/int';
|
|
|
-import { Loci } from '../../mol-model/loci';
|
|
|
-import { applyMarkerAction, MarkerAction } from '../../mol-util/marker-action';
|
|
|
-import { ButtonsType, ModifiersKeys, getButtons, getModifiers } from '../../mol-util/input/input-observer';
|
|
|
-import { ValueBox } from '../../mol-util';
|
|
|
+import { MarkerAction } from '../../mol-util/marker-action';
|
|
|
+import { PolymerSequence } from './sequence/polymer';
|
|
|
+import { StructureSeq, markResidue } from './sequence/util';
|
|
|
|
|
|
function getStructureSeqKey(structureSeq: StructureSeq) {
|
|
|
const { structure, seq } = structureSeq
|
|
@@ -73,206 +69,8 @@ export class SequenceView extends PluginUIComponent<{ }, { }> {
|
|
|
{seqs.map((seq, i) => {
|
|
|
const structureSeq = { structure, seq }
|
|
|
const markerArray = this.getMarkerArray(structureSeq)
|
|
|
- return <EntitySequence key={i} structureSeq={structureSeq} markerArray={markerArray} />
|
|
|
+ return <PolymerSequence key={i} structureSeq={structureSeq} markerArray={markerArray} />
|
|
|
})}
|
|
|
</div>;
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-function createQuery(entityId: string, label_seq_id: number) {
|
|
|
- return Queries.generators.atoms({
|
|
|
- entityTest: ctx => {
|
|
|
- return SP.entity.id(ctx.element) === entityId
|
|
|
- },
|
|
|
- residueTest: ctx => {
|
|
|
- if (ctx.element.unit.kind === Unit.Kind.Atomic) {
|
|
|
- return SP.residue.label_seq_id(ctx.element) === label_seq_id
|
|
|
- } else {
|
|
|
- return (
|
|
|
- SP.coarse.seq_id_begin(ctx.element) <= label_seq_id &&
|
|
|
- SP.coarse.seq_id_end(ctx.element) >= label_seq_id
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
-/** Zero-indexed */
|
|
|
-function getSeqIdInterval(location: StructureElement): Interval {
|
|
|
- const { unit, element } = location
|
|
|
- const { model } = unit
|
|
|
- switch (unit.kind) {
|
|
|
- case Unit.Kind.Atomic:
|
|
|
- const residueIndex = model.atomicHierarchy.residueAtomSegments.index[element]
|
|
|
- const seqId = model.atomicHierarchy.residues.label_seq_id.value(residueIndex)
|
|
|
- return Interval.ofSingleton(seqId - 1)
|
|
|
- case Unit.Kind.Spheres:
|
|
|
- return Interval.ofRange(
|
|
|
- model.coarseHierarchy.spheres.seq_id_begin.value(element) - 1,
|
|
|
- model.coarseHierarchy.spheres.seq_id_end.value(element) - 1
|
|
|
- )
|
|
|
- case Unit.Kind.Gaussians:
|
|
|
- return Interval.ofRange(
|
|
|
- model.coarseHierarchy.gaussians.seq_id_begin.value(element) - 1,
|
|
|
- model.coarseHierarchy.gaussians.seq_id_end.value(element) - 1
|
|
|
- )
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-type StructureSeq = { structure: Structure, seq: StructureSequence.Entity }
|
|
|
-
|
|
|
-function eachResidue(loci: Loci, structureSeq: StructureSeq, apply: (interval: Interval) => boolean) {
|
|
|
- let changed = false
|
|
|
- const { structure, seq } = structureSeq
|
|
|
- if (!StructureElement.isLoci(loci)) return false
|
|
|
- if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
|
|
- const l = StructureElement.create()
|
|
|
- for (const e of loci.elements) {
|
|
|
- l.unit = e.unit
|
|
|
- OrderedSet.forEach(e.indices, v => {
|
|
|
- l.element = e.unit.elements[v]
|
|
|
- const entityId = SP.entity.id(l)
|
|
|
- if (entityId === seq.entityId) {
|
|
|
- if (apply(getSeqIdInterval(l))) changed = true
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
- return changed
|
|
|
-}
|
|
|
-
|
|
|
-function markResidue(loci: Loci, structureSeq: StructureSeq, array: Uint8Array, action: MarkerAction) {
|
|
|
- const { structure, seq } = structureSeq
|
|
|
- return eachResidue(loci, { structure , seq }, (i: Interval) => {
|
|
|
- return applyMarkerAction(array, i, action)
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-type EntitySequenceProps = { structureSeq: StructureSeq, markerArray: Uint8Array }
|
|
|
-type EntitySequenceState = { markerData: ValueBox<Uint8Array> }
|
|
|
-
|
|
|
-// TODO: this is really inefficient and should be done using a canvas.
|
|
|
-class EntitySequence extends PluginUIComponent<EntitySequenceProps, EntitySequenceState> {
|
|
|
- state = {
|
|
|
- markerData: ValueBox.create(new Uint8Array(this.props.markerArray))
|
|
|
- }
|
|
|
-
|
|
|
- private lociHighlightProvider = (loci: Interactivity.Loci, action: MarkerAction) => {
|
|
|
- const { markerData } = this.state;
|
|
|
- const changed = markResidue(loci.loci, this.props.structureSeq, markerData.value, action)
|
|
|
- if (changed) this.setState({ markerData: ValueBox.withValue(markerData, markerData.value) })
|
|
|
- }
|
|
|
-
|
|
|
- private lociSelectionProvider = (loci: Interactivity.Loci, action: MarkerAction) => {
|
|
|
- const { markerData } = this.state;
|
|
|
- const changed = markResidue(loci.loci, this.props.structureSeq, markerData.value, action)
|
|
|
- if (changed) this.setState({ markerData: ValueBox.withValue(markerData, markerData.value) })
|
|
|
- }
|
|
|
-
|
|
|
- static getDerivedStateFromProps(nextProps: EntitySequenceProps, prevState: EntitySequenceState): EntitySequenceState | null {
|
|
|
- if (prevState.markerData.value !== nextProps.markerArray) {
|
|
|
- return { markerData: ValueBox.create(nextProps.markerArray) }
|
|
|
- }
|
|
|
- return null
|
|
|
- }
|
|
|
-
|
|
|
- componentDidMount() {
|
|
|
- this.plugin.interactivity.lociHighlights.addProvider(this.lociHighlightProvider)
|
|
|
- this.plugin.interactivity.lociSelections.addProvider(this.lociSelectionProvider)
|
|
|
- }
|
|
|
-
|
|
|
- componentWillUnmount() {
|
|
|
- this.plugin.interactivity.lociHighlights.removeProvider(this.lociHighlightProvider)
|
|
|
- this.plugin.interactivity.lociSelections.removeProvider(this.lociSelectionProvider)
|
|
|
- }
|
|
|
-
|
|
|
- getLoci(seqId: number) {
|
|
|
- const { structure, seq } = this.props.structureSeq
|
|
|
- const query = createQuery(seq.entityId, seqId);
|
|
|
- return StructureSelection.toLoci2(StructureQuery.run(query, structure));
|
|
|
- }
|
|
|
-
|
|
|
- highlight(seqId?: number, modifiers?: ModifiersKeys) {
|
|
|
- const ev = { current: Interactivity.Loci.Empty, modifiers }
|
|
|
- if (seqId !== undefined) {
|
|
|
- const loci = this.getLoci(seqId);
|
|
|
- if (loci.elements.length > 0) ev.current = { loci };
|
|
|
- }
|
|
|
- this.plugin.behaviors.interaction.highlight.next(ev)
|
|
|
- }
|
|
|
-
|
|
|
- click(seqId: number | undefined, buttons: ButtonsType, modifiers: ModifiersKeys) {
|
|
|
- const ev = { current: Interactivity.Loci.Empty, buttons, modifiers }
|
|
|
- if (seqId !== undefined) {
|
|
|
- const loci = this.getLoci(seqId);
|
|
|
- if (loci.elements.length > 0) ev.current = { loci };
|
|
|
- }
|
|
|
- this.plugin.behaviors.interaction.click.next(ev)
|
|
|
- }
|
|
|
-
|
|
|
- contextMenu = (e: React.MouseEvent) => {
|
|
|
- e.preventDefault()
|
|
|
- }
|
|
|
-
|
|
|
- mouseDown = (e: React.MouseEvent) => {
|
|
|
- const buttons = getButtons(e.nativeEvent)
|
|
|
- const modifiers = getModifiers(e.nativeEvent)
|
|
|
- this.click(undefined, buttons, modifiers);
|
|
|
- }
|
|
|
-
|
|
|
- render() {
|
|
|
- const { markerData } = this.state;
|
|
|
- const { seq } = this.props.structureSeq;
|
|
|
- const { offset, sequence } = seq.sequence;
|
|
|
-
|
|
|
- const elems: JSX.Element[] = [];
|
|
|
- for (let i = 0, _i = sequence.length; i < _i; i++) {
|
|
|
- elems[elems.length] = <Residue seqId={offset + i + 1} letter={sequence[i]} parent={this} marker={markerData.value[i]} key={i} />;
|
|
|
- }
|
|
|
-
|
|
|
- return <div
|
|
|
- className='msp-sequence-entity'
|
|
|
- onContextMenu={this.contextMenu}
|
|
|
- onMouseDown={this.mouseDown}
|
|
|
- >
|
|
|
- <span style={{ fontWeight: 'bold' }}>{seq.entityId}:{offset} </span>
|
|
|
- {elems}
|
|
|
- </div>;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-class Residue extends PurePluginUIComponent<{ seqId: number, letter: string, parent: EntitySequence, marker: number }> {
|
|
|
-
|
|
|
- mouseEnter = (e: React.MouseEvent) => {
|
|
|
- const modifiers = getModifiers(e.nativeEvent)
|
|
|
- this.props.parent.highlight(this.props.seqId, modifiers);
|
|
|
- }
|
|
|
-
|
|
|
- mouseLeave = () => {
|
|
|
- this.props.parent.highlight();
|
|
|
- }
|
|
|
-
|
|
|
- mouseDown = (e: React.MouseEvent) => {
|
|
|
- const buttons = getButtons(e.nativeEvent)
|
|
|
- const modifiers = getModifiers(e.nativeEvent)
|
|
|
- this.props.parent.click(this.props.seqId, buttons, modifiers);
|
|
|
- e.stopPropagation() // so that `parent.mouseDown` is not called
|
|
|
- }
|
|
|
-
|
|
|
- getBackgroundColor() {
|
|
|
- // TODO make marker color configurable
|
|
|
- if (this.props.marker === 0) return ''
|
|
|
- if (this.props.marker % 2 === 0) return 'rgb(51, 255, 25)' // selected
|
|
|
- if (this.props.marker === undefined) console.error('unexpected marker value')
|
|
|
- return 'rgb(255, 102, 153)' // highlighted
|
|
|
- }
|
|
|
-
|
|
|
- render() {
|
|
|
- return <span
|
|
|
- onMouseEnter={this.mouseEnter}
|
|
|
- onMouseLeave={this.mouseLeave}
|
|
|
- onMouseDown={this.mouseDown}
|
|
|
- style={{ backgroundColor: this.getBackgroundColor() }}>
|
|
|
- {this.props.letter}
|
|
|
- </span>;
|
|
|
- }
|
|
|
}
|