Commit 99e92796 authored by dcoudrin's avatar dcoudrin
Browse files

handling comms

parent 6195cdcd
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$/biocham_gui" libraries="{biocham/biocham_gui/node_modules}" />
<file url="PROJECT" libraries="{biocham/biocham_gui/node_modules}" />
</component>
</project>
\ No newline at end of file
<component name="libraryTable">
<library name="Generated files" type="javaScript">
<properties>
<sourceFilesUrls>
<item url="file://$PROJECT_DIR$/dist/biocham_gui/src/components/forms/formComponents.js" />
<item url="file://$PROJECT_DIR$/dist/biocham_gui/src/utils/utils.js" />
</sourceFilesUrls>
</properties>
<CLASSES>
<root url="file://$PROJECT_DIR$/dist/biocham_gui/src/components/forms/formComponents.js" />
<root url="file://$PROJECT_DIR$/dist/biocham_gui/src/utils/utils.js" />
</CLASSES>
<SOURCES />
</library>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="JSX" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (biocham)" project-jdk-type="Python SDK" />
<component name="WebPackConfiguration">
<option name="path" value="$PROJECT_DIR$/biocham_gui/webpack.config.js" />
</component>
</project>
\ No newline at end of file
......@@ -94,7 +94,7 @@ test_%: biocham_debug
jupyter: biocham biocham_kernel/commands.js biocham_kernel/commands.py
- jupyter kernelspec install --user --name=biocham biocham_kernel
- cd biocham_gui; npm install; npm run build;
- cd biocham_gui; npm install; npm run dev;
- cd ..
- jupyter nbextension install --user biocham_gui/dist/
- jupyter nbextension enable --user dist/bundle
......
export const widgets = [ {
"type": "TextInput",
"biocham_type": ["object", "parameter_name", "reaction", "name", ""]
}, {
"type": "NumberInput",
"biocham_type": ["number"]
}, {
"type": "FileInput",
"biocham_type": ["input_file"]
}, {
"type": "",
"biocham_type": []
}, {
"type": "",
"biocham_type": []
}, {
"type": "",
"biocham_type": []
}, {
"type": "",
"biocham_type": []
}, {
"type": "",
"biocham_type": []
}, {
"type": "",
"biocham_type": []
}, {
"type": "",
"biocham_type": []
}]
export const CRUD_SECTIONS = [
'Molecules',
'Initial state',
'Parameters',
'Functions',
'Reaction editor',
'Influence editor',
'Events',
'Conservation laws and invariants',
]
{
"name": "gui",
"version": "1.0.0",
"description": "",
"version": "0.1.0",
"description": "biocham gui in jupyter notebook",
"main": "src/main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack --mode development",
"build": "webpack --mode production"
},
"author": "",
"author": "David Coudrin <david.coudrin@inria.fr>",
"license": "ISC",
"devDependencies": {
"babel-cli": "^6.26.0",
......@@ -18,8 +18,10 @@
"babel-plugin-transform-es2015-modules-amd": "^6.24.1",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
"clean-webpack-plugin": "^0.1.19",
"css-loader": "^0.28.10",
"file-loader": "^1.1.11",
"html-webpack-plugin": "^3.2.0",
"lodash": "^4.17.5",
"react-draggable": "^3.0.5",
"react-hot-loader": "^4.3.3",
......
import React, { Component } from 'react';
export default class GridItem extends Component {
render() {
return {
<div></div>
}
}
}
......@@ -5,7 +5,9 @@ function Item({ children, remove }) {
return (
<li className="list-group-item">
{children}
<Glyphicon glyph="align-right remove" onClick={() => {remove()}}/>
{remove && <Glyphicon glyph="remove align-right"
onClick={() => {remove()}}
style={{float: 'right'}}/>}
</li>
)
}
......@@ -13,35 +15,78 @@ function Item({ children, remove }) {
export default class GridItemList extends Component {
state = {
input: ''
value: ''
}
generateList = () => {
this.props.content.map(item => {
return <Item remove={this.props.remove}>{item}</Item>
})
return (this.props.output ? this.props.output.split(/\r\n/).map(item => {
const itemStr = item.replace(/\[\d+]/, '');
return (item !== '' && <Item remove={this.hasDelete() && this.handleRemove}>{itemStr}</Item>)
}) : <div>No entry</div>
)
}
updateInputValue = (evt) => {
this.setState({input: evt.target.value})
this.setState({value: evt.target.value})
}
hasAdd = () => {
return (this.props.crud && this.props.crud.hasOwnProperty('add'))
}
hasDelete = () => {
return (this.props.crud && this.props.crud.hasOwnProperty('remove'))
}
handleUpdate = () => {
this.props.add(this.state.input);
handleAdd = () => {
const comm = Jupyter.notebook.kernel.comm_manager.new_comm('gui_handler', {})
const code = `${this.props.crud.add}(${this.state.value}).`
comm.send({code: code});
comm.on_msg(msg => {
if (msg.content.data.res.statut === 'ok') {
this.setState({value: ''});
this.props.onAddClick();
} else {
alert(`Error with command ${code} : ${msg.content.data.res.output}`);
console.log(msg);
}
});
}
handleRemove = (e) => {
//TODO define handleRemove but needs to find correct string to send to kernel (parameter with =
// or initial state with , etc
alert(e.currentTarget.textContent);
}
formatTitle = () => {
const command = this.props.crud.name;
const regexp = /list_(.*)/;
let title = regexp.exec(command);
title = title[1].replace('_', ' ');
return `${title[0].toUpperCase()}${title.substring(1, title.length)}`;
}
render () {
return (
<div>
<ListGroup componentClass="ul">
<h4><strong>{this.formatTitle()}</strong></h4>
<ListGroup componentClass="ul" style={{padding: '6px'}}>
{this.generateList()}
</ListGroup>
<div>
<input type="text" placeholder={this.props.placeholder} onChange={(evt) => this.updateInputValue(evt)}/>
<Button>
<Glyphicon glyph="plus" onClick={this.handleUpdate}/>
</Button>
</div>
{this.hasAdd() && (
<div>
<input type="text"
placeholder={this.props.placeholder}
onChange={(evt) => this.updateInputValue(evt)}
value={this.state.value}/>
<Button>
<Glyphicon glyph="plus" onClick={this.handleAdd} value={this.state.value}/>
</Button>
</div>
)}
</div>
)
}
......
import React, { Component } from 'react';
import
......@@ -28,6 +28,7 @@ export default class Editor extends Component {
}
updateInputValue = (evt) => {
console.log(evt.target.value);
this.setState({inputValue: evt.target.value});
}
......
......@@ -11,7 +11,7 @@ export function FieldGroup({ id, label, doc, onClick, type, ...props }) {
)
return (
<FormGroup controlId={id}>
<div>
<ControlLabel>
<strong>{` ${label} `}</strong>
<OverlayTrigger overlay={tooltip}
......@@ -21,7 +21,7 @@ export function FieldGroup({ id, label, doc, onClick, type, ...props }) {
<FormControl type={type} {...props}/>
<button onClick={onClick}>Submit</button>
</ControlLabel>
</FormGroup>
</div>
);
}
import React, { Component } from 'react';
import { Panel, PanelGroup } from 'react-bootstrap';
import ModelGrid from './ModelGrid';
import ChapterGrid from './ChapterGrid'
import { menu } from '../../../../biocham_kernel/cmd_tree';
import {crudAndOthers, getContainersFromManual} from "../../utils/utils";
import config from '../../../config/config.js';
export default class BiochamGrid extends Component {
static defaultProps = {
containers: crudAndOthers(menu).filtered
}
generateLayout = () => {
return this.props.containers.map(container => {
return ( container.children.length > 0 &&
<Panel eventKey={container.id}>
<Panel.Heading>
<Panel.Title toggle>
{container.name}
</Panel.Title>
</Panel.Heading>
<Panel.Body collapsible>
<ChapterGrid content={container.children}/>
</Panel.Body>
</Panel>
)
})
}
render () {
return (
<div>
<ModelGrid/>
<PanelGroup accordion id="biocham-accordion">
{this.generateLayout()}
</PanelGroup>
</div>
)
}
}
import React, { Component } from 'react';
import { Grid, Row, Col } from 'react-bootstrap';
import SectionGrid from './SectionGrid'
export default class ChapterGrid extends Component {
generateLayout = () => {
return this.props.content.map(section => {
return (
section.children.length > 0 &&
<Col xs={6} md={4}>
<SectionGrid style={{border: '1px solid black'}}
content={section.children}/>
</Col>
)
})
}
render() {
return (
<Grid fluid>
<Row>
{this.generateLayout()}
</Row>
</Grid>
)
}
}
import React, { Component } from 'react';
import { Panel, Grid, Row, Col, Button } from 'react-bootstrap';
import GridItemList from '../containers/GridItemList';
import {handleResult} from '../../utils/utils';
import { handleResult } from '../../utils/utils';
import { MAPPED_CRUD } from "../../utils/const";
export default class ModelGrid extends Component {
......@@ -15,11 +15,16 @@ export default class ModelGrid extends Component {
}
componentDidMount() {
return new Promise((resolve, reject) => {
const events = Jupyter.notebook.events;
events.on('kernel_ready.Kernel', () => {
this.updateModel();
})
const events = Jupyter.notebook.events;
events.on('kernel_ready.Kernel', () => {
this.updateModel();
});
}
componentDidUpdate() {
const events = Jupyter.notebook.events;
events.on('input_reply.Kernel', () => {
this.updateModel();
})
}
......@@ -30,6 +35,7 @@ export default class ModelGrid extends Component {
comm.send({code: `${command.name}.`})
comm.on_msg((msg) => {
const content = msg.content.data;
// Check previous state and only updates with result of current command
this.setState(prevState => ({
currentModel: {
...prevState.currentModel,
......@@ -53,7 +59,7 @@ export default class ModelGrid extends Component {
render () {
return (
<Panel>
<Panel defaultExpanded>
<Panel.Heading>
<Panel.Title toggle>Current Model</Panel.Title>
</Panel.Heading>
......
import React, { Component } from 'react';
import { ListGroup, ListGroupItem} from 'react-bootstrap';
import Command from '../input/Command'
export default class SectionGrid extends Component {
generateLayout = () => {
return this.props.content.map(command => {
return (
<ListGroupItem>
<Command content={command}/>
</ListGroupItem>)
})
}
render() {
return (
<ListGroup>
{this.generateLayout()}
</ListGroup>
)
}
}
import React, { Component } from 'react';
import { Tooltip, Glyphicon, OverlayTrigger, Button } from 'react-bootstrap';
import {handleResult} from "../../utils/utils"
export default class Command extends Component {
state = {
value: ''
}
handleChange = (file) => {
const r = new FileReader();
r.onload = this.handleFile;
r.readAsText(file);
}
handleFile = (e) => {
this.setState({value: e.target.result});
const comm = Jupyter.notebook.kernel.comm_manager.new_comm('gui_handler', {});
comm.send({code: this.state.value});
comm.on_msg((msg) => {
})
}
render () {
const tooltip = <Tooltip>{this.props.content.doc}</Tooltip>
const args = this.props.content.arguments
const type = (args.length > 0 ? args[0].type === 'input_file' ? 'file' : 'text' : 'hidden')
return (
<div>
<strong>{this.props.content.name} </strong>
<OverlayTrigger overlay={tooltip}
placement="right">
<Glyphicon glyph="info-sign"/>
</OverlayTrigger>
{args.length > 0 && <input type={type} onChange={(e) => this.handleChange(e.target.files[0])}/>}
{type !== 'file' && <Button style={{float: 'right'}}>Run</Button>}
</div>
)
}
}
import React, { Component } from 'react';
export class TextInput extends Component {
state = {
value: ''
}
handleChange = (e) => {
this.setState({value: e.target.value});
}
render () {
return (
<div>
<input type="text" autoComplete="off" onChange={(e) => this.handleChange(e)}/>
</div>
)
}
}
export class NumberInput extends Component {
state = {
value: ''
}
handleChange = (e) => {
this.setState({value: e.target.value});
}
render () {
return (
<div>
<input type="number" autoComplete="off" onChange={(e) => this.handleChange(e)}/>
</div>
)
}
}
export class FileInput extends Component {
handleChange = (file) => {
const reader = new FileReader();
reader.readAsText(file);
console.log(reader.result);
}
render() {
return (
<div>
<input type="file" id={this.props.id} onChange={(e) => this.handleChange(e.target.files[0])}/>
</div>
)
}
}
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { NavItem, NavDropdown } from 'react-bootstrap';
import Section from './Section';
export default class Chapter extends Component {
generateDropdown = () => {
return (
this.props.item.children.map(section => {
return (
<Section eventKey={section.name} item={section} handler={this.props.handler}/>
)
})
)
}
render () {
const item = this.props.item;
// Check if chapter contains section
if (item.children.length > 0) {
if (item.children[0].kind === 'section') {
return (
<NavDropdown title={item.name}>{this.generateDropdown()}</NavDropdown>
)
} else if (item.children[0].kind === 'command') {
return (
<NavItem>{item.name}</NavItem>
)
}
} else {
// Only if Chapter contains no section/commands
// TODO: do cleaner return
return <div></div>
}
}
}
import React, { Component } from 'react';
import ListItem from '../../components/ListItem';
export default class Command extends Component {
static defaultProps = {
level:2
}
state = {
expanded: false
}
expandMenu() {
this.setState(prevState => ({
expanded: !prevState.expanded
}))
}
render () {
const item = this.props.item;
return (
<ListItem level={this.props.level}
onClick={this.expandMenu()}
item={item}/>
)
}
}
import React, { Component } from 'react';
import Chapter from "./Chapter";
export default class Menu extends Component {
render () {