From cb3e7928bdb8aeeaadd2ba826413f9194c0243a9 Mon Sep 17 00:00:00 2001 From: Jaden Diefenbaugh <jaden.diefenbaugh@idiap.ch> Date: Tue, 31 Jul 2018 11:18:06 -0700 Subject: [PATCH] [js][db] added basic protocol template insertion support, #74 --- .../components/database/DatabaseEditor.jsx | 147 ++++++++++++++---- 1 file changed, 121 insertions(+), 26 deletions(-) diff --git a/conda/js/src/components/database/DatabaseEditor.jsx b/conda/js/src/components/database/DatabaseEditor.jsx index b7f506e8..eaec5a44 100644 --- a/conda/js/src/components/database/DatabaseEditor.jsx +++ b/conda/js/src/components/database/DatabaseEditor.jsx @@ -19,7 +19,7 @@ import { FormFeedback, Alert, InputGroupAddon, - UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem, + Dropdown, UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem, ButtonGroup, ListGroup, ListGroupItem, UncontrolledTooltip, @@ -32,6 +32,7 @@ import './DatabaseEditor.css'; import { changeObjFieldName, copyObj } from '@helpers'; import { getValidDatabaseObj as getValidObj } from '@helpers/beat'; import type { BeatObject } from '@helpers/beat'; +import fuse from 'fuse.js'; import * as Selectors from '@store/selectors.js'; @@ -50,10 +51,23 @@ type Props = { databases: BeatObject[], dataformats: BeatObject[], updateFunc: (BeatObject) => any, + protocols: SelectorProtocol[] }; +export type Protocol = { + name: string, + template: string, + sets: Set[], +}; + +// the protocols from the databaseProtocols selector has an additional field +// that's not in the protocols in database objects +type SelectorProtocol = Protocol & { database: string }; + type State = { activeProtocol: number, + insertProtocolOpen: boolean, + searchResults: SelectorProtocol[], }; export type Set = { @@ -68,12 +82,17 @@ export type Set = { }, }; -export type Protocol = { - name: string, - template: string, - sets: Set[], +const FuseOptions = { + shouldSort: true, + tokenize: true, + threshold: 0.6, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 1, + keys: ['template'], }; - +let Fuse; export class DatabaseEditor extends React.Component<Props, State> { constructor(props: Props) { @@ -82,6 +101,8 @@ export class DatabaseEditor extends React.Component<Props, State> { state = { activeProtocol: 0, + insertProtocolOpen: false, + searchResults: [], } setContents = (newContents: any) => { @@ -94,6 +115,21 @@ export class DatabaseEditor extends React.Component<Props, State> { }); } + componentDidMount = () => { + this.updateFuseInstance(); + } + + componentDidUpdate = (prevProps: Props, prevState: State) => { + this.updateFuseInstance(prevProps); + } + + updateFuseInstance = (prevProps?: Props) => { + // update fuse & reset results if search data changed + if(!prevProps || this.props.protocols !== prevProps.protocols){ + Fuse = new fuse(this.props.protocols, FuseOptions); + } + } + activateProtocol = (index: number) => { this.setState({ activeProtocol: index }); } @@ -116,6 +152,18 @@ export class DatabaseEditor extends React.Component<Props, State> { ); } + toggleInsertProtocol = (val: boolean = !this.state.insertProtocolOpen) => { + this.setState({ insertProtocolOpen: val }); + }; + + protocolSearch = (str: string) => { + const res = Fuse.search(str); + this.setState(prevState => ({ + searchResults: res.slice(0, 5), + insertProtocolOpen: res.length > 0, + })); + }; + renderProtocol = (index: number, protocol: Protocol) => ( <Container className='protocol'> <FormGroup row> @@ -604,28 +652,74 @@ export class DatabaseEditor extends React.Component<Props, State> { > Clone </Button> - </ButtonGroup> - <Button - className='ml-3' - color='success' - id='newProtocol' - onClick={e => { - const protocols = [...this.props.data.contents.protocols, { - name: '', - template: '', - sets: [], - }]; + <Button + outline + color='success' + id='newProtocol' + onClick={e => { + const protocols = [...this.props.data.contents.protocols, { + name: '', + template: '', + sets: [], + }]; - this.setContents({ - ...this.props.data.contents, - protocols - }); + this.setContents({ + ...this.props.data.contents, + protocols + }); - this.activateProtocol(protocols.length - 1); - }} + this.activateProtocol(protocols.length - 1); + }} + > + Create + </Button> + </ButtonGroup> + <Dropdown + isOpen={this.state.insertProtocolOpen} + toggle={e => this.toggleInsertProtocol(false)} + style={{flex: 1}} + className='ml-2 mr-2' > - + - </Button> + <DropdownToggle + tag='span' + > + <Input + onChange={e => { + this.protocolSearch(e.target.value); + }} + placeholder={`Insert protocol from template...`} + title={`You may insert a protocol from a template from any database`} + /> + </DropdownToggle> + <DropdownMenu> + { + this.state.searchResults.map((r, i) => + <DropdownItem + tag={Button} + key={i} + onClick={e => { + const newProtocol = copyObj(r); + delete newProtocol['database']; + + const newProtocols = [...this.props.data.contents.protocols, newProtocol]; + this.setContents({ + ...this.props.data.contents, + protocols: newProtocols, + }); + + this.activateProtocol(newProtocols.length - 1); + }} + > + { `${ r.template } (${ r.database })` } + {' '} + <small className='text-muted'> + Sets: { r.sets.length } + </small> + </DropdownItem> + ) + } + </DropdownMenu> + </Dropdown> </div> { protocol && this.renderProtocol( protocolIdx, @@ -805,7 +899,8 @@ const mapStateToProps = (state, ownProps) => { const obj = { databases: dbs, dataformats: Selectors.dataformatGet(state), - data: dbs[ownProps.index] || getValidObj() + data: dbs[ownProps.index] || getValidObj(), + protocols: Selectors.databaseProtocols(state), }; return obj; }; -- GitLab