Skip to content
Snippets Groups Projects
sequence.tsx 4.07 KiB
Newer Older
/**
 * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
 *
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 * @author David Sehnal <david.sehnal@gmail.com>
 */

import * as React from 'react'
import { PluginUIComponent } from '../base';
import { Interactivity } from '../../util/interactivity';
import { MarkerAction } from '../../../mol-util/marker-action';
import { ButtonsType, ModifiersKeys, getButtons, getModifiers } from '../../../mol-util/input/input-observer';
import { ValueBox } from '../../../mol-util';
import { Residue } from './residue';
import { SequenceWrapper } from './util';
type SequenceProps = { sequenceWrapper: SequenceWrapper.Any }
type SequenceState = { markerData: ValueBox<Uint8Array> }

function getState(markerData: ValueBox<Uint8Array>) {
    return { markerData: ValueBox.withValue(markerData, markerData.value) }
}

// TODO: this is really inefficient and should be done using a canvas.
export class Sequence<P extends SequenceProps> extends PluginUIComponent<P, SequenceState> {
    state = {
        markerData: ValueBox.create(this.props.sequenceWrapper.markerArray)
    }

    private setMarkerData(markerData: ValueBox<Uint8Array>) {
        this.setState(getState(markerData))
    }

    private lociHighlightProvider = (loci: Interactivity.Loci, action: MarkerAction) => {
        const changed = this.props.sequenceWrapper.markResidue(loci.loci, action)
        if (changed) this.setMarkerData(this.state.markerData)
    }

    private lociSelectionProvider = (loci: Interactivity.Loci, action: MarkerAction) => {
        const changed = this.props.sequenceWrapper.markResidue(loci.loci, action)
        if (changed) this.setMarkerData(this.state.markerData)
    static getDerivedStateFromProps(nextProps: SequenceProps, prevState: SequenceState): SequenceState | null {
        if (prevState.markerData.value !== nextProps.sequenceWrapper.markerArray) {
            return getState(ValueBox.create(nextProps.sequenceWrapper.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)
    }

    highlight(seqId?: number, modifiers?: ModifiersKeys) {
        const ev = { current: Interactivity.Loci.Empty, modifiers }
        if (seqId !== undefined) {
            const loci = this.props.sequenceWrapper.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.props.sequenceWrapper.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 { offset, sequence } = this.props.sequenceWrapper.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-wrapper'
            onContextMenu={this.contextMenu}
            onMouseDown={this.mouseDown}
        >
            {elems}
        </div>;
    }
}