Переглянути джерело

wip refactor history entries to keep track of exchanges

JonStargaryen 4 роки тому
батько
коміт
5801f28c69
2 змінених файлів з 131 додано та 12 видалено
  1. 84 0
      src/viewer/ui/exchanges.tsx
  2. 47 12
      src/viewer/ui/strucmotif.tsx

+ 84 - 0
src/viewer/ui/exchanges.tsx

@@ -0,0 +1,84 @@
+/**
+ * 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 {Button} from 'molstar/lib/mol-plugin-ui/controls/common';
+
+export const DefaultExchanges = [
+    ['ALA', 'Alanine'],
+    ['CYS', 'Cysteine'],
+    ['ASP', 'Aspartic Acid'],
+    ['GLU', 'Glutamic Acid'],
+    ['PHE', 'Phenylalanine'],
+    ['GLY', 'Glycine'],
+    ['HIS', 'Histidine'],
+    ['ILE', 'Isoleucine'],
+    ['LYS', 'Lysine'],
+    ['LEU', 'Leucine'],
+    ['MET', 'Methionine'],
+    ['ASN', 'Asparagine'],
+    ['PRO', 'Proline'],
+    ['GLN', 'Glutamine'],
+    ['ARG', 'Arginine'],
+    ['SER', 'Serine'],
+    ['THR', 'Threonine'],
+    ['VAL', 'Valine'],
+    ['TRP', 'Tryptophan'],
+    ['TYR', 'Tyrosine'],
+    ['A', 'Adenosine'],
+    ['C', 'Cytidine'],
+    ['DA', 'Deoxyadenosine'],
+    ['DC', 'Deoxycytidine'],
+    ['DG', 'Deoxyguanosine'],
+    ['G', ',Guanosine'],
+    ['T', 'Thymidine'],
+    ['U', 'Uridine']
+];
+
+export class ExchangesControl extends React.PureComponent<{}, { exchanges: Set<string> }> {
+    state = {
+        exchanges: new Set<string>()
+    }
+
+    onClickSwatch = (e: React.MouseEvent<HTMLButtonElement>) => {
+        const tlc = e.currentTarget.getAttribute('data-id')!;
+        if (this.state.exchanges.has(tlc)) {
+            this.setState(({ exchanges }) => {
+                const newExchanges = new Set(exchanges);
+                newExchanges.delete(tlc);
+
+                return {
+                    exchanges: newExchanges
+                };
+            });
+        } else {
+            this.setState(({ exchanges }) => ({
+                exchanges: new Set(exchanges).add(tlc)
+            }));
+        }
+    }
+
+    swatch() {
+        // TODO it would be nice to only display relevant exchanges (amino acids for amino acids, nucleotides for nucl)
+        // TODO update of isSelected style is delayed
+        return <div className='msp-combined-color-swatch'>
+            {DefaultExchanges.map(e => {
+                const isSelected = this.state.exchanges.has(e[0]);
+                const className = isSelected ? 'msp-control-current' : '';
+                return <Button key={e[0]} title={e[1]} inline data-id={e[0]} onClick={this.onClickSwatch} style={{ padding: 0, fontSize: '13px' }} className={className}>
+                    {e[0] && isSelected ? <b>{e[0]}</b> : e[0]}
+                </Button>;
+            })}
+        </div>;
+    }
+
+    render() {
+        return <>
+            <div className='msp-control-offset'>
+                {this.swatch()}
+            </div>
+        </>;
+    }
+}

+ 47 - 12
src/viewer/ui/strucmotif.tsx

@@ -19,6 +19,7 @@ import {StructureSelectionHistoryEntry} from 'molstar/lib/mol-plugin-state/manag
 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';
+import {ExchangesControl} from './exchanges';
 
 // TODO use prod
 // const ADVANCED_SEARCH_URL = 'https://localhost:8080/search?request=';
@@ -55,8 +56,12 @@ type ExchangeState = 'exchanges-0' | 'exchanges-1' | 'exchanges-2' | 'exchanges-
 /**
  * The inner component of strucmotif search that can be collapsed.
  */
-class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean, action?: ExchangeState }> {
-    state = { isBusy: false, action: void 0 as ExchangeState | undefined }
+class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean, residueMap: Map<StructureSelectionHistoryEntry, Residue>, action?: ExchangeState }> {
+    state = {
+        isBusy: false,
+        residueMap: new Map<StructureSelectionHistoryEntry, Residue>(),
+        action: void 0 as ExchangeState | undefined
+    };
 
     componentDidMount() {
         this.subscribe(this.selection.events.additionsHistoryUpdated, () => {
@@ -166,33 +171,43 @@ class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean, action
         this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci }, false);
     }
 
-    moveHistory(e: StructureSelectionHistoryEntry, direction: 'up' | 'down') {
+    moveHistory(e: Residue, direction: 'up' | 'down') {
         this.setState({ action: void 0 });
-        this.plugin.managers.structure.selection.modifyHistory(e, direction, MAX_MOTIF_SIZE);
+        this.plugin.managers.structure.selection.modifyHistory(e.entry, direction, MAX_MOTIF_SIZE);
+        this.updateResidues();
     }
 
-    modifyHistory(e: StructureSelectionHistoryEntry, a: 'remove', idx: number) {
+    modifyHistory(e: Residue, a: 'remove', idx: number) {
         this.setState({ action: void 0 });
-        this.plugin.managers.structure.selection.modifyHistory(e, a);
+        this.plugin.managers.structure.selection.modifyHistory(e.entry, a);
+        this.updateResidues();
+    }
+
+    updateResidues() {
+        const newResidueMap = new Map<StructureSelectionHistoryEntry, Residue>();
+        this.selection.additionsHistory.forEach(entry => {
+            newResidueMap.set(entry, this.state.residueMap.get(entry)!);
+        });
+        this.setState({ residueMap: newResidueMap });
     }
 
     focusLoci(loci: StructureElement.Loci) {
         this.plugin.managers.camera.focusLoci(loci);
     }
 
-    historyEntry(e: StructureSelectionHistoryEntry, idx: number) {
+    historyEntry(e: Residue, idx: number) {
         const history = this.plugin.managers.structure.selection.additionsHistory;
-        return <div key={e.id}>
+        return <div key={e.entry.id}>
             <div className='msp-flex-row'>
-                <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 noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.entry.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.entry.loci)} onMouseLeave={this.plugin.managers.interactivity.lociHighlights.clearHighlights}>
+                    {idx}. <span dangerouslySetInnerHTML={{ __html: e.entry.label }} />
                 </Button>
                 <ToggleButton icon={TuneSvg} className='msp-form-control' title='Define Exchanges' toggle={() => this.toggleExchanges(idx)} isSelected={this.state.action === `exchanges-${idx}`} disabled={this.state.isBusy} style={{ flex: '0 0 40px', padding: 0 }} />
                 {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.modifyHistory(e, 'remove', idx)} flex title={'Remove'} />
             </div>
-            { this.state.action === `exchanges-${idx}` && <div className='msp-flex-row'>Options...</div> }
+            { this.state.action === `exchanges-${idx}` && <ExchangesControl /> }
         </div>;
     }
 
@@ -201,7 +216,7 @@ class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean, action
 
         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));
+            entries.push(this.historyEntry(new Residue(history[i]), i + 1));
         }
 
         return <>
@@ -221,3 +236,23 @@ class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean, action
         </>;
     }
 }
+
+export class Residue {
+    readonly exchanges: Set<string>;
+
+    constructor(readonly entry: StructureSelectionHistoryEntry) {
+        this.exchanges = new Set<string>();
+    }
+
+    toggleExchange(val: string): void {
+        if (this.hasExchange(val)) {
+            this.exchanges.delete(val);
+        } else {
+            this.exchanges.add(val);
+        }
+    }
+
+    hasExchange(val: string): boolean {
+        return this.exchanges.has(val);
+    }
+}