Skip to content

Commit 8d2a7f0

Browse files
committed
Merge branch 'development' of https://github.com/metacell/netpyne-ui into feature/iframe-throttling-fix-rebase
2 parents 6d10519 + aca1517 commit 8d2a7f0

6 files changed

Lines changed: 170 additions & 66 deletions

File tree

Dockerfile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ RUN mkdir -p /opt/workspace
5353
RUN mkdir -p /opt/user
5454

5555

56-
57-
5856
ENV NEURON_HOME=/opt/conda
5957

6058

@@ -65,8 +63,10 @@ RUN jupyter nbextension enable --py --sys-prefix jupyter_geppetto
6563
RUN jupyter nbextension enable --py --sys-prefix widgetsnbextension
6664
RUN jupyter serverextension enable --py --sys-prefix jupyter_geppetto
6765

68-
RUN --mount=type=cache,target=/root/.cache python -m pip install --upgrade pip &&\
69-
python utilities/install.py --npm-skip --no-test
66+
ARG BUILD_ARGS=""
67+
ARG WORKSPACE_VERSION=master
68+
RUN --mount=type=cache,target=/root/.cache python -m pip install --upgrade pip &&\
69+
python utilities/install.py ${BUILD_ARGS} --workspace $WORKSPACE_VERSION
7070

7171

7272
RUN mv workspace /opt/workspace/tutorials

README.md

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,59 @@ your system and you have had troubles installing it you can opt for the Virtual
3030

3131
### Python Dependencies
3232

33-
We recommend the use of a new python 3 virtual environment:
33+
We recommend the use of a new python 3.7 virtual environment.
34+
Currently, NetPyNE-UI only supports Python 3.7, but that can change in the future.
35+
36+
For NetPyNE-UI, the preferred way of creating a virtual env is to pass by conda/miniconda or mamba/micromamba.
37+
The pyenv tool can be also used, but it requires to be compiled with some special options to have NEURON running properly the simulation using `nrniv`.
38+
The reason behind this is that NEURON is distributed as a Python wheel with a specific option which force NEURON to look for the CPython dynamic lib.
39+
However, pyenv by default installs the static version of the CPython lib, resulting in `nrniv -python` not being able to run.
40+
41+
The way of creating the virtualenv using (mini)conda is the following
42+
43+
```bash
44+
conda create -n netpyne python=3.7
45+
conda activate netpyne
46+
```
47+
48+
Here is how to create the virtualenv using (micro)mamba
49+
50+
```bash
51+
mamba create -n netpyne python=3.7 -c conda-forge
52+
mamba activate netpyne
53+
```
54+
55+
You can also directly create a virtualenv using your `python3` executable, obviously if it's the 3.7 version.
3456

3557
```bash
3658
python3 -m venv npenv
3759
source npenv/bin/activate
3860
```
3961

40-
Or, with conda
62+
If you want to use anyway pyenv, here is how to properly create the virtualenv and activate it.
4163

4264
```bash
43-
conda create -n netpyne python=3.7
44-
conda activate netpyne
65+
env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install --verbose 3.7.17
66+
pyenv virtualenv 3.7.17 netpyne
67+
pyenv shell netpyne
4568
```
4669

4770
### Run install script
4871

72+
When you are in your virtualenv, here is how you can install the "basic" version of NetPyNE-UI:
73+
4974
```bash
5075
python utilities/install.py
5176
```
5277

78+
If you want to have a different version of NetPyNE or geppetto meta, you can pass the version you want to the installer:
79+
80+
```bash
81+
python utilities/install.py --dev --netpyne development --geppetto development --no-test
82+
```
83+
84+
This command will install the `development` version of netpyne and geppetto and disable the tests during the installation.
85+
5386
### Start application
5487

5588
```bash
@@ -62,6 +95,15 @@ For debugging you can use `run.py` instead
6295
python run.py
6396
```
6497

98+
To run the UI in dev mode, you need to run `python run.py` in one terminal, and use `yarn` from the `webapp` folder, using node v14 (use `nvm` if you need to have a different version of node than the one installed on your system):
99+
100+
```bash
101+
cd webapp
102+
yarn start
103+
```
104+
105+
You can then navigate to `http://127.0.0.1:8081/` to access the dev version of the UI.
106+
65107
## Run NetPyNE User Interface in Docker
66108

67109
Ensure that you have Docker installed on your system.
@@ -75,7 +117,7 @@ docker build -t netpyne-ui .
75117
Run the image
76118

77119
```bash
78-
docker run -p 8888:8888 netpyne-ui
120+
docker run -p 8888:8888 netpyne-ui
79121
```
80122

81123
## End-to-end tests

tests/frontend/e2e/jest-puppeteer.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = {
22
launch: {
3-
headless: true,
3+
headless: 'new',
44
defaultViewport: {
55
width: 1300,
66
height: 1024

tests/frontend/e2e/tests/SaveOpenFile.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const PASSWORD = 'testpassword'
3939

4040
jest.setTimeout(300000);
4141

42-
describe('Save / Open File testing', () => {
42+
describe.skip('Save / Open File testing', () => {
4343

4444
beforeAll(async () => {
4545
await page.goto(baseURL);

webapp/components/general/AdapterComponent.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export default class AdapterComponent extends Component {
5151

5252
// Call to conversion function
5353
const newValue = this.props.convertToPython(this.state);
54-
if (newValue != undefined && this.state.value != newValue) {
54+
if (newValue != undefined && this.state.value != newValue && this.props.onChange) {
5555
this.props.onChange(null, null, newValue);
5656
}
5757
}

webapp/components/general/NetPyNECoordsRange.js

Lines changed: 116 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,29 @@ import Utils from '../../Utils';
99
export default class NetPyNECoordsRange extends Component {
1010
constructor (props) {
1111
super(props);
12-
this.state = { rangeType: undefined };
12+
this.state = { rangeType: undefined, rangeValue: [undefined, undefined] };
1313

1414
this._isMounted = false;
1515
}
1616

17-
triggerUpdate (updateMethod) {
18-
// common strategy when triggering processing of a value change, delay it, every time there is a change we reset
19-
if (this.updateTimer != undefined) {
20-
clearTimeout(this.updateTimer);
21-
}
22-
this.updateTimer = setTimeout(updateMethod, 1000);
23-
}
24-
2517
componentDidMount () {
2618
this._isMounted = true;
2719
this.updateLayout();
2820
}
2921

3022
componentDidUpdate (prevProps, prevState) {
3123
if (this.props.name != prevProps.name) {
32-
this.triggerUpdate(() => {
33-
const message = `netpyne_geppetto.${this.props.model}['${this.props.name}']${(this.props.conds != undefined) ? `['${this.props.conds}']` : ''}`;
34-
Utils
35-
.evalPythonMessage(`[key in ${message} for key in ['${this.props.items[0].value}', '${this.props.items[1].value}']]`)
36-
.then((response) => {
37-
if (response[0] && this._isMounted === true) {
38-
this.setState({ rangeType: this.props.items[0].value });
39-
} else if (response[1] && this._isMounted === true) {
40-
this.setState({ rangeType: this.props.items[1].value });
41-
} else if (this._isMounted === true) {
42-
this.setState({ rangeType: undefined });
43-
}
44-
});
24+
const message = `netpyne_geppetto.${this.props.model}['${this.props.name}']${(this.props.conds != undefined) ? `['${this.props.conds}']` : ''}`;
25+
Utils
26+
.evalPythonMessage(`[key in ${message} for key in ['${this.props.items[0].value}', '${this.props.items[1].value}']]`)
27+
.then((response) => {
28+
if (response[0] && this._isMounted === true) {
29+
this.setState({ rangeType: this.props.items[0].value });
30+
} else if (response[1] && this._isMounted === true) {
31+
this.setState({ rangeType: this.props.items[1].value });
32+
} else if (this._isMounted === true) {
33+
this.setState({ rangeType: undefined });
34+
}
4535
});
4636
} else if (this.props.conds != prevProps.conds) {
4737
this.updateLayout();
@@ -58,17 +48,37 @@ export default class NetPyNECoordsRange extends Component {
5848

5949
const message = `netpyne_geppetto.${model}['${name}']${(conds !== undefined)
6050
? `['${conds}']` : ''}`;
51+
const evalMessage = `[key in ${message} for key in ['${items[0].value}', '${items[1].value}']]` ;
52+
6153
Utils
62-
.evalPythonMessage(`[key in ${message} for key in ['${items[0].value}', '${items[1].value}']]`)
63-
.then((response) => {
64-
if (response[0] && this._isMounted === true) {
65-
this.setState({ rangeType: items[0].value });
66-
} else if (response[1] && this._isMounted === true) {
67-
this.setState({ rangeType: items[1].value });
68-
} else if (this._isMounted === true) {
69-
this.setState({ rangeType: undefined });
70-
}
71-
});
54+
.evalPythonMessage(evalMessage)
55+
.then((response) => {
56+
57+
let rangeType = undefined ;
58+
59+
if (response[0] && this._isMounted === true) {
60+
rangeType = items[0].value ;
61+
} else if (response[1] && this._isMounted === true) {
62+
rangeType = items[1].value ;
63+
}
64+
65+
this.setState({ rangeType });
66+
67+
if (rangeType)
68+
{
69+
const pythonMessage = `netpyne_geppetto.${model}['${name}']['${conds}']['${this.state.rangeType}']` ;
70+
71+
Utils
72+
.evalPythonMessage(pythonMessage)
73+
.then((response) => {
74+
if (response && response.length > 0 ) {
75+
this.setState({ rangeValue: response });
76+
}});
77+
}
78+
else {
79+
this.setState({ rangeValue: [undefined, undefined] });
80+
}
81+
});
7282
}
7383

7484
createMenuItems = () => this.props.items.map((obj) => (
@@ -85,6 +95,73 @@ export default class NetPyNECoordsRange extends Component {
8595
this._isMounted = false;
8696
}
8797

98+
handleRangeTypeChange(event) {
99+
const {
100+
model,
101+
conds,
102+
name,
103+
} = this.props;
104+
105+
const rangeType = event.target.value ;
106+
107+
if (this.state.rangeType && this.state.rangeType.length > 0)
108+
{
109+
const pyPath = `netpyne_geppetto.${model}['${name}']['${conds}']`;
110+
const startLetter = String(this.state.rangeType)[0];
111+
const pythonMessageDelOpposite = `${pyPath} = {k: v for k, v in ${pyPath}.items() if not k.startswith('${startLetter}')}`;
112+
Utils.execPythonMessage(
113+
pythonMessageDelOpposite
114+
);
115+
116+
const rangeValue = this.state.rangeValue ;
117+
118+
if (!rangeValue.some(e => e === undefined))
119+
{
120+
const pythonMessage = `netpyne_geppetto.${model}['${name}']['${conds}']['${rangeType}'] = [${rangeValue}]` ;
121+
Utils.execPythonMessage(
122+
pythonMessage
123+
);
124+
}
125+
}
126+
127+
this.setState({ rangeType})
128+
}
129+
130+
//preConds: pop, cellType, cellModel, x, y, z, xnorm, ynorm, znorm
131+
handleCoordParamChange(index, newValue) {
132+
const {
133+
model,
134+
conds,
135+
name,
136+
} = this.props;
137+
138+
139+
if (newValue === '' || (/^\d+$/.test(newValue))) {
140+
if (this.state.rangeType && this.state.rangeType.length > 0)
141+
{
142+
const pyPath = `netpyne_geppetto.${model}['${name}']['${conds}']`;
143+
const startLetter = String(this.state.rangeType)[0];
144+
const pythonMessageDelOpposite = `${pyPath} = {k: v for k, v in ${pyPath}.items() if not k.startswith('${startLetter}')}`;
145+
Utils.execPythonMessage(
146+
pythonMessageDelOpposite
147+
);
148+
}
149+
150+
const rangeValue = this.state.rangeValue ;
151+
rangeValue[index] = newValue ;
152+
153+
if (!rangeValue.some(e => e === undefined))
154+
{
155+
const pythonMessage = `netpyne_geppetto.${model}['${name}']['${conds}']['${this.state.rangeType}'] = [${rangeValue}]` ;
156+
Utils.execPythonMessage(
157+
pythonMessage
158+
);
159+
}
160+
161+
this.setState({ rangeValue })
162+
}
163+
}
164+
88165
render () {
89166
if (this.props.conds != undefined) {
90167
var meta = `${this.props.model}.${this.props.conds}.${this.props.items[0].value}`;
@@ -95,42 +172,27 @@ export default class NetPyNECoordsRange extends Component {
95172
}
96173
const min = `${this.props.id}MinRange`;
97174
const max = `${this.props.id}MaxRange`;
175+
176+
const minVal = this.state.rangeValue[0];
177+
const maxVal = this.state.rangeValue[1];
178+
98179
return (
99180
<div>
100181
<NetPyNEField id={meta}>
101182
<SelectField
102183
id={`${this.props.id}Select`}
103184
label="Range type"
104185
value={this.state.rangeType || ''}
105-
onChange={(event) => this.setState({ rangeType: event.target.value })}
186+
onChange={(event) => { this.handleRangeTypeChange(event); }}
106187
>
107188
{this.createMenuItems()}
108189
</SelectField>
109190
</NetPyNEField>
110191
{(this.state.rangeType != undefined)
111192
? (
112193
<Box width="100%" p={1}>
113-
<AdapterComponent
114-
model={path}
115-
convertToPython={(state) => {
116-
if (!state[state.lastUpdated].toString()
117-
.endsWith('.')
118-
&& ((!isNaN(parseFloat(state[min]))) && (!isNaN(parseFloat(state[max]))))) {
119-
return [parseFloat(state[min]), parseFloat(state[max])];
120-
}
121-
}}
122-
convertFromPython={(prevProps, prevState, value) => {
123-
if (value != undefined && prevProps.value != value && value != '') {
124-
const output = {};
125-
output[min] = value[0];
126-
output[max] = value[1];
127-
return output;
128-
}
129-
}}
130-
>
131-
<TextField label="Minimum" id={min} variant="filled" fullWidth />
132-
<TextField label="Maximum" id={max} variant="filled" fullWidth />
133-
</AdapterComponent>
194+
<TextField type="number" label="Minimum" id={min} variant="filled" value={minVal} fullWidth onChange={ (e) => { this.handleCoordParamChange(0, parseInt(e.target.value)) } } />
195+
<TextField type="number" label="Maximum" id={max} variant="filled" value={maxVal} fullWidth onChange={ (e) => { this.handleCoordParamChange(1, parseInt(e.target.value)) } } />
134196
</Box>
135197
)
136198
: null}

0 commit comments

Comments
 (0)