molstar
Version:
A comprehensive macromolecular library.
278 lines (277 loc) • 13.5 kB
JavaScript
import { __assign, __extends } from "tslib";
import { jsx as _jsx } from "react/jsx-runtime";
/**
* Copyright (c) 2018-2020 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 { Subject } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
import { OrderedSet } from '../../mol-data/int';
import { StructureElement, StructureProperties, Unit } from '../../mol-model/structure';
import { Representation } from '../../mol-repr/representation';
import { getButton, getButtons, getModifiers } from '../../mol-util/input/input-observer';
import { PluginUIComponent } from '../base';
/** Note, if this is changed, the CSS for `msp-sequence-number` needs adjustment too */
var MaxSequenceNumberSize = 5;
// TODO: this is somewhat inefficient and should be done using a canvas.
var Sequence = /** @class */ (function (_super) {
__extends(Sequence, _super);
function Sequence() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.parentDiv = React.createRef();
_this.lastMouseOverSeqIdx = -1;
_this.highlightQueue = new Subject();
_this.lociHighlightProvider = function (loci, action) {
var changed = _this.props.sequenceWrapper.markResidue(loci.loci, action);
if (changed)
_this.updateMarker();
};
_this.lociSelectionProvider = function (loci, action) {
var changed = _this.props.sequenceWrapper.markResidue(loci.loci, action);
if (changed)
_this.updateMarker();
};
_this.contextMenu = function (e) {
e.preventDefault();
};
_this.mouseDownLoci = undefined;
_this.mouseDown = function (e) {
e.stopPropagation();
var seqIdx = _this.getSeqIdx(e);
var loci = _this.getLoci(seqIdx);
var buttons = getButtons(e.nativeEvent);
var button = getButton(e.nativeEvent);
var modifiers = getModifiers(e.nativeEvent);
_this.click(loci, buttons, button, modifiers);
_this.mouseDownLoci = loci;
};
_this.mouseUp = function (e) {
e.stopPropagation();
// ignore mouse-up events without a bound loci
if (_this.mouseDownLoci === undefined)
return;
var seqIdx = _this.getSeqIdx(e);
var loci = _this.getLoci(seqIdx);
if (loci && !StructureElement.Loci.areEqual(_this.mouseDownLoci, loci)) {
var buttons = getButtons(e.nativeEvent);
var button = getButton(e.nativeEvent);
var modifiers = getModifiers(e.nativeEvent);
var ref = _this.mouseDownLoci.elements[0];
var ext = loci.elements[0];
var min = Math.min(OrderedSet.min(ref.indices), OrderedSet.min(ext.indices));
var max = Math.max(OrderedSet.max(ref.indices), OrderedSet.max(ext.indices));
var range = StructureElement.Loci(loci.structure, [{
unit: ref.unit,
indices: OrderedSet.ofRange(min, max)
}]);
_this.click(StructureElement.Loci.subtract(range, _this.mouseDownLoci), buttons, button, modifiers);
}
_this.mouseDownLoci = undefined;
};
_this.location = StructureElement.Location.create(void 0);
_this.mouseMove = function (e) {
e.stopPropagation();
var buttons = getButtons(e.nativeEvent);
var button = getButton(e.nativeEvent);
var modifiers = getModifiers(e.nativeEvent);
var el = e.target;
if (!el || !el.getAttribute) {
if (_this.lastMouseOverSeqIdx === -1)
return;
_this.lastMouseOverSeqIdx = -1;
_this.highlightQueue.next({ seqIdx: -1, buttons: buttons, button: button, modifiers: modifiers });
return;
}
var seqIdx = el.hasAttribute('data-seqid') ? +el.getAttribute('data-seqid') : -1;
if (_this.lastMouseOverSeqIdx === seqIdx) {
return;
}
else {
_this.lastMouseOverSeqIdx = seqIdx;
if (_this.mouseDownLoci !== undefined) {
var loci = _this.getLoci(seqIdx);
_this.hover(loci, 0 /* ButtonsType.Flag.None */, 0 /* ButtonsType.Flag.None */, __assign(__assign({}, modifiers), { shift: true }));
}
else {
_this.highlightQueue.next({ seqIdx: seqIdx, buttons: buttons, button: button, modifiers: modifiers });
}
}
};
_this.mouseLeave = function (e) {
e.stopPropagation();
_this.mouseDownLoci = undefined;
if (_this.lastMouseOverSeqIdx === -1)
return;
_this.lastMouseOverSeqIdx = -1;
var buttons = getButtons(e.nativeEvent);
var button = getButton(e.nativeEvent);
var modifiers = getModifiers(e.nativeEvent);
_this.highlightQueue.next({ seqIdx: -1, buttons: buttons, button: button, modifiers: modifiers });
};
return _this;
}
Object.defineProperty(Sequence.prototype, "sequenceNumberPeriod", {
get: function () {
if (this.props.sequenceNumberPeriod !== undefined) {
return this.props.sequenceNumberPeriod;
}
if (this.props.sequenceWrapper.length > 10)
return 10;
var lastSeqNum = this.getSequenceNumber(this.props.sequenceWrapper.length - 1);
if (lastSeqNum.length > 1)
return 5;
return 1;
},
enumerable: false,
configurable: true
});
Sequence.prototype.componentDidMount = function () {
var _this = this;
this.plugin.managers.interactivity.lociHighlights.addProvider(this.lociHighlightProvider);
this.plugin.managers.interactivity.lociSelects.addProvider(this.lociSelectionProvider);
this.subscribe(this.highlightQueue.pipe(throttleTime(3 * 16.666, void 0, { leading: true, trailing: true })), function (e) {
var loci = _this.getLoci(e.seqIdx < 0 ? void 0 : e.seqIdx);
_this.hover(loci, e.buttons, e.button, e.modifiers);
});
};
Sequence.prototype.componentWillUnmount = function () {
_super.prototype.componentWillUnmount.call(this);
this.plugin.managers.interactivity.lociHighlights.removeProvider(this.lociHighlightProvider);
this.plugin.managers.interactivity.lociSelects.removeProvider(this.lociSelectionProvider);
};
Sequence.prototype.getLoci = function (seqIdx) {
if (seqIdx !== undefined) {
var loci = this.props.sequenceWrapper.getLoci(seqIdx);
if (!StructureElement.Loci.isEmpty(loci))
return loci;
}
};
Sequence.prototype.getSeqIdx = function (e) {
var seqIdx = undefined;
var el = e.target;
if (el && el.getAttribute) {
seqIdx = el.hasAttribute('data-seqid') ? +el.getAttribute('data-seqid') : undefined;
}
return seqIdx;
};
Sequence.prototype.hover = function (loci, buttons, button, modifiers) {
var ev = { current: Representation.Loci.Empty, buttons: buttons, button: button, modifiers: modifiers };
if (loci !== undefined && !StructureElement.Loci.isEmpty(loci)) {
ev.current = { loci: loci };
}
this.plugin.behaviors.interaction.hover.next(ev);
};
Sequence.prototype.click = function (loci, buttons, button, modifiers) {
var ev = { current: Representation.Loci.Empty, buttons: buttons, button: button, modifiers: modifiers };
if (loci !== undefined && !StructureElement.Loci.isEmpty(loci)) {
ev.current = { loci: loci };
}
this.plugin.behaviors.interaction.click.next(ev);
};
Sequence.prototype.getBackgroundColor = function (marker) {
// TODO: make marker color configurable
if (typeof marker === 'undefined')
console.error('unexpected marker value');
return marker === 0
? ''
: marker % 2 === 0
? 'rgb(51, 255, 25)' // selected
: 'rgb(255, 102, 153)'; // highlighted
};
Sequence.prototype.getResidueClass = function (seqIdx, label) {
return label.length > 1
? this.props.sequenceWrapper.residueClass(seqIdx) + (seqIdx === 0 ? ' msp-sequence-residue-long-begin' : ' msp-sequence-residue-long')
: this.props.sequenceWrapper.residueClass(seqIdx);
};
Sequence.prototype.residue = function (seqIdx, label, marker) {
return _jsx("span", __assign({ "data-seqid": seqIdx, style: { backgroundColor: this.getBackgroundColor(marker) }, className: this.getResidueClass(seqIdx, label) }, { children: "\u200B".concat(label, "\u200B") }), seqIdx);
};
Sequence.prototype.getSequenceNumberClass = function (seqIdx, seqNum, label) {
var classList = ['msp-sequence-number'];
if (seqNum.startsWith('-')) {
if (label.length > 1 && seqIdx > 0)
classList.push('msp-sequence-number-long-negative');
else
classList.push('msp-sequence-number-negative');
}
else {
if (label.length > 1 && seqIdx > 0)
classList.push('msp-sequence-number-long');
}
return classList.join(' ');
};
Sequence.prototype.getSequenceNumber = function (seqIdx) {
var seqNum = '';
var loci = this.props.sequenceWrapper.getLoci(seqIdx);
var l = StructureElement.Loci.getFirstLocation(loci, this.location);
if (l) {
if (Unit.isAtomic(l.unit)) {
var seqId = StructureProperties.residue.auth_seq_id(l);
var insCode = StructureProperties.residue.pdbx_PDB_ins_code(l);
seqNum = "".concat(seqId).concat(insCode ? insCode : '');
}
else if (Unit.isCoarse(l.unit)) {
seqNum = "".concat(seqIdx + 1);
}
}
return seqNum;
};
Sequence.prototype.padSeqNum = function (n) {
if (n.length < MaxSequenceNumberSize)
return n + new Array(MaxSequenceNumberSize - n.length + 1).join('\u00A0');
return n;
};
Sequence.prototype.getSequenceNumberSpan = function (seqIdx, label) {
var seqNum = this.getSequenceNumber(seqIdx);
return _jsx("span", __assign({ className: this.getSequenceNumberClass(seqIdx, seqNum, label) }, { children: this.padSeqNum(seqNum) }), "marker-".concat(seqIdx));
};
Sequence.prototype.updateMarker = function () {
if (!this.parentDiv.current)
return;
var xs = this.parentDiv.current.children;
var markerArray = this.props.sequenceWrapper.markerArray;
var hasNumbers = !this.props.hideSequenceNumbers, period = this.sequenceNumberPeriod;
// let first: HTMLSpanElement | undefined;
var o = 0;
for (var i = 0, il = markerArray.length; i < il; i++) {
if (hasNumbers && i % period === 0 && i < il)
o++;
// o + 1 to account for help icon
var span = xs[o];
if (!span)
return;
o++;
// if (!first && markerArray[i] > 0) {
// first = span;
// }
var backgroundColor = this.getBackgroundColor(markerArray[i]);
if (span.style.backgroundColor !== backgroundColor)
span.style.backgroundColor = backgroundColor;
}
// if (first) {
// first.scrollIntoView({ block: 'nearest' });
// }
};
Sequence.prototype.render = function () {
var sw = this.props.sequenceWrapper;
var elems = [];
var hasNumbers = !this.props.hideSequenceNumbers, period = this.sequenceNumberPeriod;
for (var i = 0, il = sw.length; i < il; ++i) {
var label = sw.residueLabel(i);
// add sequence number before name so the html element do not get separated by a line-break
if (hasNumbers && i % period === 0 && i < il) {
elems[elems.length] = this.getSequenceNumberSpan(i, label);
}
elems[elems.length] = this.residue(i, label, sw.markerArray[i]);
}
// calling .updateMarker here is neccesary to ensure existing
// residue spans are updated as react won't update them
this.updateMarker();
return _jsx("div", __assign({ className: 'msp-sequence-wrapper', onContextMenu: this.contextMenu, onMouseDown: this.mouseDown, onMouseUp: this.mouseUp, onMouseMove: this.mouseMove, onMouseLeave: this.mouseLeave, ref: this.parentDiv }, { children: elems }));
};
return Sequence;
}(PluginUIComponent));
export { Sequence };