123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
- */
- import * as React from 'react';
- import {CollapsableControls, PurePluginUIComponent} from 'molstar/lib/mol-plugin-ui/base';
- import {Button, IconButton} from 'molstar/lib/mol-plugin-ui/controls/common';
- import {
- ArrowDownwardSvg,
- ArrowUpwardSvg,
- DeleteOutlinedSvg,
- HelpOutlineSvg,
- Icon
- } from 'molstar/lib/mol-plugin-ui/controls/icons';
- import {ActionMenu} from 'molstar/lib/mol-plugin-ui/controls/action-menu';
- import {StructureSelectionHistoryEntry} from 'molstar/lib/mol-plugin-state/manager/structure/selection';
- import {StructureElement, StructureProperties} from 'molstar/lib/mol-model/structure/structure';
- import {ToggleSelectionModeButton} from 'molstar/lib/mol-plugin-ui/structure/selection';
- import {OrderedSet} from 'molstar/lib/mol-data/int';
- // TODO use prod
- // const ADVANCED_SEARCH_URL = 'https://strucmotif-dev.rcsb.org/search?request=';
- const ADVANCED_SEARCH_URL = 'http://localhost:8080/search?request=';
- const MAX_MOTIF_SIZE = 10;
- export class StrucmotifSubmitControls extends CollapsableControls {
- protected defaultState() {
- return {
- header: 'Structural Motif Search',
- isCollapsed: false,
- brand: { accent: 'gray' as const, svg: SearchIconSvg }
- };
- }
- renderControls() {
- return <>
- <SubmitControls />
- </>;
- }
- }
- const _SearchIcon = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M8 5v14l11-7z' /></svg>;
- export function SearchIconSvg() { return _SearchIcon; }
- const location = StructureElement.Location.create(void 0);
- export class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean }> {
- state = { isBusy: false }
- componentDidMount() {
- this.subscribe(this.selection.events.additionsHistoryUpdated, () => {
- this.forceUpdate();
- });
- this.subscribe(this.plugin.behaviors.state.isBusy, v => {
- this.setState({ isBusy: v });
- });
- }
- get selection() {
- return this.plugin.managers.structure.selection;
- }
- submitSearch = () => {
- const pdbId: Set<string> = new Set();
- const residueIds: { label_asym_id: string, struct_oper_id?: string, label_seq_id: number }[] = [];
- const loci = this.plugin.managers.structure.selection.additionsHistory;
- let structure;
- for (let l of loci) {
- structure = l.loci.structure;
- pdbId.add(structure.model.entry);
- // TODO ensure selection references only polymeric entities
- // only first element and only first index will be considered (ignoring multiple residues)
- const e = l.loci.elements[0];
- StructureElement.Location.set(location, structure, e.unit, e.unit.elements[OrderedSet.getAt(e.indices, 0)]);
- residueIds.push({
- label_asym_id: StructureProperties.chain.label_asym_id(location),
- // struct_oper_id: '1',
- label_seq_id: StructureProperties.residue.label_seq_id(location)
- });
- }
- if (pdbId.size > 1) {
- console.warn('motifs can only be extracted from a single model');
- return;
- }
- if (residueIds.length > MAX_MOTIF_SIZE) {
- console.warn(`maximum motif size is ${MAX_MOTIF_SIZE} residues`);
- return;
- }
- const query = {
- query: {
- type: 'group',
- logical_operator: 'and',
- nodes: [{
- type: 'terminal',
- service: 'strucmotif',
- parameters: {
- value: {
- data: pdbId.values().next().value as string,
- residue_ids: residueIds
- },
- score_cutoff: 5,
- exchanges: []
- },
- label: 'strucmotif',
- node_id: 0
- }],
- label: 'query-builder'
- },
- return_type: 'assembly',
- request_options: {
- pager: {
- start: 0,
- rows: 100
- },
- scoring_strategy: 'combined',
- sort: [{
- sort_by: 'score',
- direction: 'desc'
- }]
- },
- // TODO needed?
- // 'request_info': {
- // 'src': 'ui',
- // 'query_id': 'a4efda380aee3ef202dc59447a419e80'
- // }
- };
- // TODO figure out if Mol* can compose sierra/BioJava operator
- // TODO probably there should be a sierra-endpoint that handles mapping of Mol* operator ids to sierra/BioJava ones
- window.open(ADVANCED_SEARCH_URL + encodeURIComponent(JSON.stringify(query)), '_blank');
- }
- get actions(): ActionMenu.Items {
- const history = this.selection.additionsHistory;
- return [
- {
- kind: 'item',
- label: `Submit Search ${history.length < 3 ? ' (3 selections required)' : ''}`,
- value: this.submitSearch,
- disabled: history.length < 3
- },
- ];
- }
- selectAction: ActionMenu.OnSelect = item => {
- if (!item) return;
- (item?.value as any)();
- }
- highlight(loci: StructureElement.Loci) {
- this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci }, false);
- }
- moveHistory(e: StructureSelectionHistoryEntry, direction: 'up' | 'down') {
- this.plugin.managers.structure.selection.modifyHistory(e, direction, 4);
- }
- focusLoci(loci: StructureElement.Loci) {
- this.plugin.managers.camera.focusLoci(loci);
- }
- historyEntry(e: StructureSelectionHistoryEntry, idx: number) {
- const history = this.plugin.managers.structure.selection.additionsHistory;
- return <div className='msp-flex-row' key={e.id}>
- <Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={this.plugin.managers.interactivity.lociHighlights.clearHighlights}>
- {idx}. <span dangerouslySetInnerHTML={{ __html: e.label }} />
- </Button>
- {history.length > 1 && <IconButton svg={ArrowUpwardSvg} small={true} className='msp-form-control' onClick={() => this.moveHistory(e, 'up')} flex='20px' title={'Move up'} />}
- {history.length > 1 && <IconButton svg={ArrowDownwardSvg} small={true} className='msp-form-control' onClick={() => this.moveHistory(e, 'down')} flex='20px' title={'Move down'} />}
- <IconButton svg={DeleteOutlinedSvg} small={true} className='msp-form-control' onClick={() => this.plugin.managers.structure.selection.modifyHistory(e, 'remove')} flex title={'Remove'} />
- </div>;
- }
- add() {
- const history = this.plugin.managers.structure.selection.additionsHistory;
- const entries: JSX.Element[] = [];
- for (let i = 0, _i = Math.min(history.length, 10); i < _i; i++) {
- entries.push(this.historyEntry(history[i], i + 1));
- }
- return <>
- <ActionMenu items={this.actions} onSelect={this.selectAction} />
- {entries.length > 0 && <div className='msp-control-offset'>
- {entries}
- </div>}
- {entries.length === 0 && <div className='msp-control-offset msp-help-text'>
- <div className='msp-help-description'><Icon svg={HelpOutlineSvg} inline />Add one or more selections (toggle <ToggleSelectionModeButton inline /> mode)</div>
- </div>}
- </>;
- }
- render() {
- return <>
- {this.add()}
- </>;
- }
- }
|