/** * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Sebastian Bittrich */ 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 <> ; } } const _SearchIcon = ; 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 = 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
{history.length > 1 && this.moveHistory(e, 'up')} flex='20px' title={'Move up'} />} {history.length > 1 && this.moveHistory(e, 'down')} flex='20px' title={'Move down'} />} this.plugin.managers.structure.selection.modifyHistory(e, 'remove')} flex title={'Remove'} />
; } 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 <> {entries.length > 0 &&
{entries}
} {entries.length === 0 &&
Add one or more selections (toggle mode)
} ; } render() { return <> {this.add()} ; } }