Commit b6c34cdb authored by Ian Sillitoe's avatar Ian Sillitoe
Browse files

improve frontend

parent 742d5d82
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import TextField from '@material-ui/core/TextField';
import FormControl from '@material-ui/core/FormControl';
import Button from '@material-ui/core/Button';
import Divider from '@material-ui/core/Divider';
import CardActions from '@material-ui/core/CardActions';
const styles = theme => ({
root: {
width: '100%',
backgroundColor: theme.palette.background.paper,
},
section: {
margin: theme.spacing(3, 2),
},
helpTitle: {
fontSize: 14,
},
formControl: {
},
querySequence: {
marginLeft: theme.spacing(1),
marginRight: theme.spacing(1),
},
querySequenceInput: {
fontFamily: "Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace",
fontSize: 12,
lineHeight: 1.2,
},
});
const exampleSequences = {
'A0A0Q0Y989': 'MNDFHRDTWAEVDLDAIYDNVANLRRLLPDDTHIMAVVKANAYGHGDVQVARTALEAGASRLAVAFLDEALALREKGIEAPILVLGASRPADAALAAQQRIALTVFRSDWLEEASALYSGPFPIHFHLKMDTGMGRLGVKDEEETKRIVALIERHPHFVLEGVYTHFATADEVNTDYFSYQYTRFLHMLEWLPSRPPLVHCANSAASLRFPDRTFNMVRFGIAMYGLAPSPGIKPLLPYPLKEAFSLHSRLVHVKKLQPGEKVSYGATYTAQTEEWIGTIPIGYADGWLRRLQHFHVLVDGQKAPIVGRICMDQCMIRLPGPLPVGTKVTLIGRQGDEVISIDDVARHLETINYEVPCTISYRVPRIFFRHKRIMEVRNAIGRGESSA',
'O014992': 'MSVVGIDLGFQSCYVAVARAGGIETIANEYSDRCTPACISFGPKNRSIGAAAKSQVISNAKNTVQGFKRFHGRAFSDPFVEAEKSNLAYDIVQLPTGLTGIKVTYMEEERNFTTEQVTAMLLSKLKETAESVLKKPVVDCVVSVPCFYTDAERRSVMDATQIAGLNCLRLMNETTAVALAYGIYKQDLPALEEKPRNVVFVDMGHSAYQVSVCAFNRGKLKVLATAFDTTLGGRKFDEVLVNHFCEEFGKKYKLDIKSKIRALLRLSQECEKLKKLMSANASDLPLSIECFMNDVDVSGTMNRGKFLEMCNDLLARVEPPLRSVLEQTKLKKEDIYAVEIVGGATRIPAVKEKISKFFGKELSTTLNADEAVTRGCALQCAILSPAFKVREFSITDVVPYPISLRWNSPAEEGSSDCEVFSKNHAAPFSKVLTFYRKEPFTLEAYYSSPSGFALSRSQFSVQKVLLSLMAPVQK',
};
class QuerySequenceStep extends React.Component {
constructor(props) {
super(props);
this.state = {
error: false,
errorMessage: null,
querySequenceId: "",
querySequence: "",
};
// This binding is necessary to make `this` work in the callback
this._handleTextFieldChange = this._handleTextFieldChange.bind(this);
this._handleSubmit = this._handleSubmit.bind(this);
}
setError(msg) {
this.setState({
error: true,
errorMessage: msg,
});
}
_handleTextFieldChange(e) {
const queryFasta = e.target.value;
this.setSequenceFromFasta(queryFasta);
}
_handleSubmit(ev) {
const { querySequence, querySequenceId } = this.state;
this.props.onSubmit(ev, { id: querySequenceId, seq: querySequence });
}
setSequenceFromFasta(queryFasta) {
this.setState({ queryFasta });
queryFasta = queryFasta.trim();
if (queryFasta === "") {
this.setState({ error: false, errorMessage: null, queryId: null, querySequence: null });
return;
}
const lines = queryFasta.split('\n');
const header = lines.shift();
if (!header.startsWith('>')) {
return this.setError("Expected FASTA header to start with '>'");
}
const id_re = /^>(\S+)/;
const id_match = header.match(id_re);
if (!id_match) {
return this.setError(`Failed to parse ID from FASTA header '${header}'`);
}
const id = id_match[1];
let seq = '';
lines.forEach(function (line, line_num) {
if (line.match(id_re)) {
return;
}
seq += line.trim();
});
console.log("Calling onChange with seq details", this.props, id, seq);
this.props.onChange(id, seq);
}
_handleExampleClick(exampleId) {
const exampleSeq = exampleSequences[exampleId];
console.log("_handleExampleClick", this, exampleId, exampleSeq);
this.setState(state => {
return { querySequenceId: exampleId, querySequence: exampleSeq };
});
}
getFasta() {
if (this.state.queryId && this.state.querySequence) {
return '>' + this.state.queryId + '\n' + this.state.querySequence + '\n';
}
else {
return;
}
}
renderError() {
}
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<div className={classes.section}>
<FormControl fullWidth>
<TextField
InputProps={{ classes: { input: classes.querySequenceInput } }}
className={classes.querySequence}
id="query-sequence"
label="Query Protein Sequence"
placeholder="Paste your protein sequence here"
helperText="Protein sequence should be a string of amino-acids"
multiline
autoFocus
value={this.state.querySequence}
error={this.state.error}
required
/>
</FormControl>
<FormControl>
<TextField
InputProps={{ classes: { input: classes.querySequenceInput } }}
className={classes.querySequence}
id="query-sequence-id"
label="Sequence ID"
placeholder="Add a name/id for your sequence"
helperText="Add a name/id for your sequence"
value={this.state.querySequenceId}
error={this.state.error}
required
/>
</FormControl>
</div>
<Divider variant="middle" />
<div className={classes.section}>
<CardActions>
<Button variant="contained"
onClick={() => this._handleExampleClick("A0A0Q0Y989")}>Example1</Button>
<Button variant="contained"
onClick={() => this._handleExampleClick("O014992")}>Example2</Button>
<Button variant="contained" color="secondary"
onClick={this._handleClear}>Clear</Button>
<Button variant="contained" color="primary"
onClick={this._handleSubmit}>Submit</Button>
</CardActions>
</div>
</div >);
}
}
QuerySequenceStep.propTypes = {
classes: PropTypes.object.isRequired,
onSubmit: PropTypes.func.isRequired,
queryId: PropTypes.string,
querySequence: PropTypes.string,
};
export default withStyles(styles)(QuerySequenceStep);
......@@ -118,6 +118,11 @@ class SubmitCheckResultProvider extends Component {
if (this.props.onCheckResponse) {
this.props.onCheckResponse(checkResponse);
}
const error = this.props.isErrorFromCheck(checkResponse);
if (error) {
this.setState({ completed: true, error: true, message: "Error: " + error });
return;
}
const complete = this.props.isCompleteFromCheck(checkResponse);
if (complete) {
this.setState({ completed: true, message: "Complete" });
......@@ -237,6 +242,7 @@ SubmitCheckResultProvider.propTypes = {
checkMaxAttempts: PropTypes.number.isRequired,
taskIdFromSubmit: PropTypes.func.isRequired,
isCompleteFromCheck: PropTypes.func.isRequired,
isErrorFromCheck: PropTypes.func.isRequired,
onError: PropTypes.func.isRequired,
onSubmitResponse: PropTypes.func,
......
......@@ -6,33 +6,39 @@ import Step from "@material-ui/core/Step";
import StepLabel from "@material-ui/core/StepLabel";
import StepContent from "@material-ui/core/StepContent";
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import CardActions from "@material-ui/core/CardActions";
import Paper from "@material-ui/core/Paper";
import Typography from "@material-ui/core/Typography";
import QuerySequence from "./QuerySequence.js";
import QuerySequenceStep from "./QuerySequenceStep.js";
import FunfamMatchList from "./FunfamMatchList.js";
import ModelStructure from "./ModelStructure.js";
import SubmitCheckResultProvider from "./SubmitCheckResultProvider.js";
import { parseCathScanResponseData } from "../models/SearchScan.js";
import DummyCathScanResults from "../models/test_data/CathScanResults.test.json";
import DummyCathScanResults from "../models/test_data/CathScanResults.test.json";
const STEP_QUERY=0, STEP_TEMPLATE=1, STEP_MODEL=2;
const STEP_QUERY = 0, STEP_TEMPLATE = 1, STEP_MODEL = 2;
const styles = theme => ({
root: {
width: "100%"
},
stepContent: {
padding: theme.spacing(2),
},
button: {
marginTop: theme.spacing.unit,
marginRight: theme.spacing.unit
marginTop: theme.spacing(1),
marginRight: theme.spacing(1),
},
actionsContainer: {
marginBottom: theme.spacing.unit * 2
marginBottom: theme.spacing(2)
},
resetContainer: {
padding: theme.spacing.unit * 3
padding: theme.spacing(2)
}
});
......@@ -48,8 +54,8 @@ const STEPS_CONFIG = [
resultClass: "SelectTemplateTable",
providerClass: "SubmitCheckResultProvider",
providerProps: {
apiBase: "http://localhost:8000/",
authTokenEndpoint: "api/api-auth-token/",
apiBase: "https://api01.cathdb.info/",
authTokenEndpoint: "api/api-token-auth/",
submitEndpoint: "api/select-template/",
checkEndpoint: "api/select-template/<id>/",
resultEndpoint: "api/select-template/<id>/results",
......@@ -59,6 +65,10 @@ const STEPS_CONFIG = [
isCompleteFromCheck: data => {
return data["status"] === "success";
},
isErrorFromCheck: data => {
const msg = data["message"];
return data["status"] === "error" ? msg : false;
},
username: "apiuser",
password: "apiuserpassword"
},
......@@ -86,6 +96,9 @@ const STEPS_CONFIG = [
isCompleteFromCheck: data => {
return data["status"] === "success";
},
isErrorFromCheck: data => {
return data["status"] === "error";
},
username: "ian",
password: "4cathuse"
},
......@@ -109,18 +122,24 @@ class WorkFlow extends React.Component {
activeStep: 0,
queryId: null,
querySequence: null,
taskId: null,
templates: null,
templateTaskId: null,
hits: null,
templateHitId: null,
hitResult: null,
templateModelId: null,
modelResult: null,
templateError: null,
templateScanResult: null
};
this.handleChangeSequence = this.handleChangeSequence.bind(this);
this.handleSubmitSequence = this.handleSubmitSequence.bind(this);
this.handleTemplateError = this.handleTemplateError.bind(this);
this.handleTemplateSubmit = this.handleTemplateSubmit.bind(this);
this.handleTemplateCheck = this.handleTemplateCheck.bind(this);
this.handleTemplateResult = this.handleTemplateResult.bind(this);
this.handleExampleSequence = this.handleExampleSequence.bind(this);
this.handleExampleScanResults = this.handleExampleScanResults.bind(this);
// this.handleModelError = this.handleModelError.bind(this);
......@@ -129,9 +148,23 @@ class WorkFlow extends React.Component {
// this.handleModelResult = this.handleModelResult.bind(this);
}
handleChangeSequence(queryId, querySequence) {
console.log("Setting query sequence", queryId, querySequence);
this.setState({ queryId, querySequence });
handleSubmitSequence(ev, seq) {
console.log("Setting sequence: ", seq);
const queryId = seq.id;
const querySequence = seq.seq;
this.setState(state => {
return {
activeStep: STEP_TEMPLATE,
queryId: queryId,
querySequence: querySequence,
templateTaskId: null,
hits: null,
templateHitId: null,
hitResult: null,
templateModelId: null,
modelResult: null,
};
});
}
handleNext = e => {
......@@ -157,12 +190,10 @@ class WorkFlow extends React.Component {
const queryId = this.state.queryId;
const querySeq = this.state.querySequence;
return (
<QuerySequence
<QuerySequenceStep
queryId={queryId}
querySequence={querySeq}
onChange={this.handleChangeSequence}
onExampleScanResults={this.handleExampleScanResults}
onExampleSequence={this.handleExampleSequence}
onSubmit={this.handleSubmitSequence}
/>
);
}
......@@ -184,20 +215,6 @@ class WorkFlow extends React.Component {
console.log("handleTemplateCheck", data);
}
handleExampleSequence() {
this.setState({
})
}
handleExampleScanResults() {
console.log("handleExampleScanResults", this);
this.setState({
activeStep: STEP_TEMPLATE,
templateScanResult: DummyCathScanResults,
});
}
handleTemplateResult(rawdata) {
console.log("handleTemplateResult", rawdata);
const scan = this.parseTemplateResultData(rawdata);
......@@ -210,6 +227,10 @@ class WorkFlow extends React.Component {
this.setState({ templateScanResult: scanResult });
}
handleExampleScanResults(ev) {
console.log("handleExampleScanResults");
}
parseTemplateResultData(rawdata) {
console.log("parseTemplateResultData", rawdata);
const results_json = rawdata.results_json;
......@@ -268,55 +289,43 @@ class WorkFlow extends React.Component {
const { classes } = this.props;
const { activeStep } = this.state;
const steps = [
{ label: "Query Sequence", content: this.renderQuerySequence() },
{ label: "Select Template", content: this.renderSelectTemplate() },
{ label: "Model Structure", content: this.renderModelStructure() }
{ label: "Submit Sequence", renderer: this.renderQuerySequence },
{ label: "Select Template", renderer: this.renderSelectTemplate },
{ label: "Model Structure", renderer: this.renderModelStructure },
];
const stepContent = steps[activeStep].renderer.bind(this)();
return (
<div className={classes.root}>
<Stepper activeStep={activeStep} orientation="vertical">
{steps.map((step, index) => {
const label = step["label"];
const stepContent = step["content"];
const nextId = "next" + step["tag"];
return (
<Step key={label}>
<StepLabel>{label}</StepLabel>
<StepContent>
{stepContent}
<div className={classes.actionsContainer}>
<div>
<Button
disabled={activeStep === 0}
onClick={this.handleBack}
className={classes.button}
>
Back
</Button>
<Button
variant="contained"
color="primary"
key={nextId}
onClick={this.handleNext}
className={classes.button}
>
{activeStep === steps.length - 1 ? "Finish" : "Next"}
</Button>
</div>
</div>
</StepContent>
</Step>
);
})}
</Stepper>
{activeStep === steps.length && (
<Paper square elevation={0} className={classes.resetContainer}>
<Typography>All steps completed - you&quot;re finished</Typography>
<Button onClick={this.handleReset} className={classes.button}>
Reset
</Button>
</Paper>
)}
<Card>
<CardContent>
<Stepper activeStep={activeStep} alternativeLabel>
{steps.map((step, index) => {
const label = step["label"];
const nextId = "next" + step["tag"];
return (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
);
})}
</Stepper>
<div className={classes.stepContent}>
{stepContent}
</div>
</CardContent>
<CardActions>
{activeStep === steps.length && (
<Paper square elevation={0} className={classes.resetContainer}>
<Typography>All steps completed - you&quot;re finished</Typography>
<Button onClick={this.handleReset} className={classes.button}>
Reset
</Button>
</Paper>
)}
</CardActions>
</Card>
</div>
);
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment