From f5335da356175b172ca73f3976c283d02873f559 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 25 May 2020 11:48:48 +0200 Subject: [PATCH 01/12] Use jsonschema-react-form component to render/validate form --- README.md | 141 +++++++++++++++++++++++++++- src/js/app.js | 5 +- src/js/example-app.html | 4 + src/js/example-jsonschema-form.html | 16 ++++ src/js/jsonschema-app.js | 71 ++++++++++++++ 5 files changed, 230 insertions(+), 7 deletions(-) create mode 100644 src/js/example-jsonschema-form.html create mode 100644 src/js/jsonschema-app.js diff --git a/README.md b/README.md index 6d0ed55..1838f37 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - [Executing long running methods in JavaScript](#executing-long-running-methods-in-JavaScript) - [Single page application](#single-page-application) - [React component](#react-component) - - [Form](#form) + - [JSON schema powered form](#json-schema-powered-form) - [Visualization](#visualization) [![CI](https://github.com/NLESC-JCER/cpp2wasm/workflows/CI/badge.svg)](https://github.com/NLESC-JCER/cpp2wasm/actions?query=workflow%3ACI) @@ -983,8 +983,8 @@ To use React we need to import the React library. A React application is constructed from React components. The simplest React component is a function which returns a HTML tag with a variable inside. -```{.jsx file=src/js/app.js} -// this JavaScript snippet is stored as src/js/app.js +```{.jsx #heading-component} +// this JavaScript snippet is later referred to as <> function Heading() { const title = 'Root finding web application'; return

{title}

@@ -1129,8 +1129,8 @@ To render the result we can use a React Component which has `root` as a property When the calculation has not been done yet, it will render `Not submitted`. When the `root` property value is set then we will show it. -```{.jsx file=src/js/app.js} -// this JavaScript snippet stored as src/js/app.js +```{.jsx #result-component} +// this JavaScript snippet is later referred to as <> function Result(props) { const root = props.root; let message = 'Not submitted'; @@ -1144,6 +1144,9 @@ function Result(props) { We can combine the heading, form and result components and all the states and handleSubmit function into the `App` React component. ```{.jsx file=src/js/app.js} +<> +<> + // this JavaScript snippet appenended to src/js/app.js function App() { <> @@ -1185,6 +1188,134 @@ Visit [http://localhost:8000/src/js/example-app.html](http://localhost:8000/src/ The JSON schema can be used to generate a form. The form submission will be validated against the schema. The most popular JSON schema form for React is [react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form). +In the [Web service](#web-service) an OpenAPI specification was used to specify the request and response schema. For the form we need the request schema in JSON format which is + +```{.js #jsonschema-app} +// this JavaScript snippet is later referred to as <> +const schema = { + "type": "object", + "properties": { + "epsilon": { + "title": "Epsilon", + "type": "number", + "minimum": 0, + "default": 0.001 + }, + "guess": { + "title": "Initial guess", + "type": "number", + "default": -20 + } + }, + "required": ["epsilon", "guess"], + "additionalProperties": false +} +``` + +To render the application we need a HTML page. We will reuse the imports we did in the previous chapter. + +```{.html file=src/js/example-jsonschema-form.html} + + + + <> +
+ + + +``` + +To use the `react-jsonschema-form` React component we need to import it. + +```{.html #imports} + + +``` + +The form by default uses the [Bootstrap 3](https://getbootstrap.com/docs/3.4/) theme, to use theme we need to import the Bootstrap CSS file. + +```{.html #imports} + + +``` + +The form component is exported as `JSONSchemaForm.default` and can be aliases with + +```{.js #jsonschema-app} +// this JavaScript snippet is appended to <> +const Form = JSONSchemaForm.default; +``` + +The form can be rendered with + +```{.jsx #jsonschema-form} +{ /* this JavaScript snippet is later referred to as <> */} +
+``` + +The `handleSubmit` function recieves the form input values and use the web worker we created earlier to perform the calculation and render the result. + +```{.js #jsonschema-app} +// this JavaScript snippet is appended to <> +const [root, setRoot] = React.useState(undefined); + +function handleSubmit({formData}, event) { + event.preventDefault(); + const worker = new Worker('worker.js'); + worker.postMessage({ + type: 'CALCULATE', + payload: { epsilon: formData.epsilon, guess: formData.guess } + }); + worker.onmessage = function(message) { + if (message.data.type === 'RESULT') { + const result = message.data.payload.root; + setRoot(result); + worker.terminate(); + } + }; +} +``` + +The App component can be defined and rendered with. + +```{.jsx file=src/js/jsonschema-app.js} +// this JavaScript snippet stored as src/js/jsonschema-app.js +function App() { + <> + + return ( +
+ + <> + +
+ ); +} + +ReactDOM.render( + , + document.getElementById('container') +); +``` + +The `Heading` and `Result` React component can be reused. + +```{.jsx file=src/js/jsonschema-app.js} +// this JavaScript snippet appended to src/js/jsonschema-app.js +<> +<> +``` + +Like before we also need to host the files in a web server with + +```shell +python3 -m http.server 8000 +``` + +Visit [http://localhost:8000/src/js/example-jsonschema-form.html](http://localhost:8000/src/js/example-jsonschema-form.html) to see the root answer. + +If you enter a negative number in the `epsilon` field the form will be invalid and refuse to submit. + ### Visualization The plots in web apllicatoin can be made using [vega-lite](https://vega.github.io/vega-lite/). Vega-lite is a JS library which accepts a JSON document describing the plot and generates interactive graphics. diff --git a/src/js/app.js b/src/js/app.js index a320aff..a405e78 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -1,9 +1,9 @@ -// this JavaScript snippet is stored as src/js/app.js +// this JavaScript snippet is later referred to as <> function Heading() { const title = 'Root finding web application'; return

{title}

} -// this JavaScript snippet stored as src/js/app.js +// this JavaScript snippet is later referred to as <> function Result(props) { const root = props.root; let message = 'Not submitted'; @@ -12,6 +12,7 @@ function Result(props) { } return
{message}
; } + // this JavaScript snippet appenended to src/js/app.js function App() { // this JavaScript snippet is later referred to as <> diff --git a/src/js/example-app.html b/src/js/example-app.html index e24de67..fab26d9 100644 --- a/src/js/example-app.html +++ b/src/js/example-app.html @@ -6,6 +6,10 @@ + + + +
diff --git a/src/js/example-jsonschema-form.html b/src/js/example-jsonschema-form.html new file mode 100644 index 0000000..d3fc452 --- /dev/null +++ b/src/js/example-jsonschema-form.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/src/js/jsonschema-app.js b/src/js/jsonschema-app.js new file mode 100644 index 0000000..179d9b0 --- /dev/null +++ b/src/js/jsonschema-app.js @@ -0,0 +1,71 @@ +// this JavaScript snippet stored as src/js/jsonschema-app.js +function App() { + // this JavaScript snippet is later referred to as <> + const schema = { + "type": "object", + "properties": { + "epsilon": { + "title": "Epsilon", + "type": "number", + "minimum": 0, + "default": 0.001 + }, + "guess": { + "title": "Initial guess", + "type": "number", + "default": -20 + } + }, + "required": ["epsilon", "guess"], + "additionalProperties": false + } + // this JavaScript snippet is appended to <> + const Form = JSONSchemaForm.default; + // this JavaScript snippet is appended to <> + const [root, setRoot] = React.useState(undefined); + + function handleSubmit({formData}, event) { + event.preventDefault(); + const worker = new Worker('worker.js'); + worker.postMessage({ + type: 'CALCULATE', + payload: { epsilon: formData.epsilon, guess: formData.guess } + }); + worker.onmessage = function(message) { + if (message.data.type === 'RESULT') { + const result = message.data.payload.root; + setRoot(result); + worker.terminate(); + } + }; + } + + return ( +
+ + { /* this JavaScript snippet is later referred to as <> */} + + +
+ ); +} + +ReactDOM.render( + , + document.getElementById('container') +); +// this JavaScript snippet appended to src/js/jsonschema-app.js +// this JavaScript snippet is later referred to as <> +function Heading() { + const title = 'Root finding web application'; + return

{title}

+} +// this JavaScript snippet is later referred to as <> +function Result(props) { + const root = props.root; + let message = 'Not submitted'; + if (root !== undefined) { + message = 'Root = ' + root; + } + return
{message}
; +} \ No newline at end of file From 5759bf31d08e02783b6046b619351d3b25dc7327 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 25 May 2020 14:50:32 +0200 Subject: [PATCH 02/12] Make form controlled + added range slider --- README.md | 41 ++++++++++++++++++++++++++++++++++------ src/js/jsonschema-app.js | 30 ++++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1838f37..0e222fb 100644 --- a/README.md +++ b/README.md @@ -952,7 +952,6 @@ To make writing a SPA easier, a number of frameworks have been developed. The mo They have their strengths and weaknesses which are summarized in the [here](https://en.wikipedia.org/wiki/Comparison_of_JavaScript_frameworks#Features). - For Newton-Raphson web application I picked React as it is light and functional, because I like the small API footprint and the functional programming paradigm. The C++ algorithm is compiled into a wasm file using bindings. When a calculation form is submitted in the React application a web worker loads the wasm file, starts the calculation, renders the result. With this architecture the application only needs cheap static file hosting to host the html, js and wasm files. **The calculation will be done in the web browser on the end users machine instead of a server**. @@ -1198,13 +1197,13 @@ const schema = { "epsilon": { "title": "Epsilon", "type": "number", - "minimum": 0, - "default": 0.001 + "minimum": 0 }, "guess": { "title": "Initial guess", - "type": "number", - "default": -20 + "type": "integer", + "minimum": -100, + "maximum": 100 } }, "required": ["epsilon", "guess"], @@ -1246,11 +1245,41 @@ The form component is exported as `JSONSchemaForm.default` and can be aliases wi const Form = JSONSchemaForm.default; ``` +The react-jsonschema-form component normally renders an integer with a updown selector. To use a range slider instead we configure a [user interface schema](https://react-jsonschema-form.readthedocs.io/en/latest/quickstart/#form-uischema). + +```{.js #jsonschema-app} +const uiSchema = { + "guess": { + "ui:widget": "range" + } +} +``` + +The values in the form must be initialized and updated whenever the form changes. + +```{.js #jsonschema-app} +// this JavaScript snippet is appended to <> +const [formData, setFormData] = React.useState({ + epsilon: 0.001, + guess: -20 +}); + +function handleChange(event) { + setFormData(event.formData); +} +``` + The form can be rendered with ```{.jsx #jsonschema-form} { /* this JavaScript snippet is later referred to as <> */} - + ``` The `handleSubmit` function recieves the form input values and use the web worker we created earlier to perform the calculation and render the result. diff --git a/src/js/jsonschema-app.js b/src/js/jsonschema-app.js index 179d9b0..6aff24d 100644 --- a/src/js/jsonschema-app.js +++ b/src/js/jsonschema-app.js @@ -7,13 +7,13 @@ function App() { "epsilon": { "title": "Epsilon", "type": "number", - "minimum": 0, - "default": 0.001 + "minimum": 0 }, "guess": { "title": "Initial guess", - "type": "number", - "default": -20 + "type": "integer", + "minimum": -100, + "maximum": 100 } }, "required": ["epsilon", "guess"], @@ -21,6 +21,20 @@ function App() { } // this JavaScript snippet is appended to <> const Form = JSONSchemaForm.default; + const uiSchema = { + "guess": { + "ui:widget": "range" + } + } + // this JavaScript snippet is appended to <> + const [formData, setFormData] = React.useState({ + epsilon: 0.001, + guess: -20 + }); + + function handleChange(event) { + setFormData(event.formData); + } // this JavaScript snippet is appended to <> const [root, setRoot] = React.useState(undefined); @@ -44,7 +58,13 @@ function App() {
{ /* this JavaScript snippet is later referred to as <> */} - +
); From b052c5dd31dd98742dc3674febf63ae1d46a3892 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 25 May 2020 17:32:44 +0200 Subject: [PATCH 03/12] Reuse JSON schema + added test for jsonschema form powered app --- README.md | 24 ++++++++++++++----- TESTING.md | 20 ++++++++++++++-- cypress/integration/example-app_spec.js | 1 + .../example-jsonschema-form_spec.js | 9 +++++++ src/js/jsonschema-app.js | 2 ++ 5 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 cypress/integration/example-jsonschema-form_spec.js diff --git a/README.md b/README.md index 0e222fb..b05331c 100644 --- a/README.md +++ b/README.md @@ -156,13 +156,23 @@ An example of JSON schema: ```json { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://example.com/schemas/person.json", + "$id": "https://nlesc-jcer.github.io/cpp2wasm/NNRequest.json", "type": "object", "properties": { - "name": { "type": "string" }, - "age": { "type": "number", "minimum": 0 } + "epsilon": { + "title": "Epsilon", + "type": "number", + "minimum": 0 + }, + "guess": { + "title": "Initial guess", + "type": "integer", + "minimum": -100, + "maximum": 100 + } }, - "required": [ "name" ] + "required": ["epsilon", "guess"], + "additionalProperties": false } ``` @@ -170,8 +180,8 @@ And a valid document: ```json { - "name": "me", - "age": 42 + "epsilon": 0.001, + "guess": -20 } ``` @@ -1192,6 +1202,8 @@ In the [Web service](#web-service) an OpenAPI specification was used to specify ```{.js #jsonschema-app} // this JavaScript snippet is later referred to as <> const schema = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nlesc-jcer.github.io/cpp2wasm/NNRequest.json", "type": "object", "properties": { "epsilon": { diff --git a/TESTING.md b/TESTING.md index f72b947..21fb37d 100644 --- a/TESTING.md +++ b/TESTING.md @@ -31,20 +31,36 @@ describe('src/js/example-web-worker.html', () => { }); ``` -And lastly a test for the full React/form/Web worker/WebAssembly combination. +And a test for the full React/form/Web worker/WebAssembly combination. +Let us also change the guess value. ```{.js file=cypress/integration/example-app_spec.js} describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); cy.get('input[name=guess]').type('-30'); + cy.get('input[id=root_guess]').contains('-30'); cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); }); ``` -The test can be run with +And another test for the full application, but now with JSON schema powered form. + +```{.js file=cypress/integration/example-jsonschema-form_spec.js} +describe('src/js/example-jsonschema-form.html', () => { + it('should render -1.00', () => { + cy.visit('http://localhost:8000/src/js/example-jsonschema-form.html'); + cy.get('input[id=root_guess]').type('-30'); + cy.get('input[id=root_guess]').contains('-30'); + cy.contains('Submit').click(); + cy.get('#answer').contains('-1.00'); + }); +}); +``` + +The tests can be run with ```{.awk #test-wasm} npx cypress run --config-file false diff --git a/cypress/integration/example-app_spec.js b/cypress/integration/example-app_spec.js index f831bac..f084140 100644 --- a/cypress/integration/example-app_spec.js +++ b/cypress/integration/example-app_spec.js @@ -2,6 +2,7 @@ describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); cy.get('input[name=guess]').type('-30'); + cy.get('input[id=root_guess]').contains('-30'); cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); diff --git a/cypress/integration/example-jsonschema-form_spec.js b/cypress/integration/example-jsonschema-form_spec.js new file mode 100644 index 0000000..7069561 --- /dev/null +++ b/cypress/integration/example-jsonschema-form_spec.js @@ -0,0 +1,9 @@ +describe('src/js/example-jsonschema-form.html', () => { + it('should render -1.00', () => { + cy.visit('http://localhost:8000/src/js/example-jsonschema-form.html'); + cy.get('input[id=root_guess]').type('-30'); + cy.get('input[id=root_guess]').contains('-30'); + cy.contains('Submit').click(); + cy.get('#answer').contains('-1.00'); + }); +}); \ No newline at end of file diff --git a/src/js/jsonschema-app.js b/src/js/jsonschema-app.js index 6aff24d..3425c07 100644 --- a/src/js/jsonschema-app.js +++ b/src/js/jsonschema-app.js @@ -2,6 +2,8 @@ function App() { // this JavaScript snippet is later referred to as <> const schema = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nlesc-jcer.github.io/cpp2wasm/NNRequest.json", "type": "object", "properties": { "epsilon": { From 8c420c693754fe2af0c5096e462c8d1f7d237c60 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Tue, 26 May 2020 11:18:07 +0200 Subject: [PATCH 04/12] Updated README.md --- README.md | 28 ++++++++++++++-------------- src/js/jsonschema-app.js | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index b05331c..1421ea0 100644 --- a/README.md +++ b/README.md @@ -1194,8 +1194,8 @@ Visit [http://localhost:8000/src/js/example-app.html](http://localhost:8000/src/ ### JSON schema powered form -The JSON schema can be used to generate a form. The form submission will be validated against the schema. -The most popular JSON schema form for React is [react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form). +The JSON schema can be used to generate a form. The form values will be validated against the schema. +The most popular JSON schema form for React is [react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form) so we will write a web application with it. In the [Web service](#web-service) an OpenAPI specification was used to specify the request and response schema. For the form we need the request schema in JSON format which is @@ -1236,28 +1236,28 @@ To render the application we need a HTML page. We will reuse the imports we did ``` -To use the `react-jsonschema-form` React component we need to import it. +To use the [react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form) React component we need to import it. ```{.html #imports} ``` -The form by default uses the [Bootstrap 3](https://getbootstrap.com/docs/3.4/) theme, to use theme we need to import the Bootstrap CSS file. - -```{.html #imports} - - -``` - -The form component is exported as `JSONSchemaForm.default` and can be aliases with +The form component is exported as `JSONSchemaForm.default` and can be aliases to `Form` with ```{.js #jsonschema-app} // this JavaScript snippet is appended to <> const Form = JSONSchemaForm.default; ``` -The react-jsonschema-form component normally renders an integer with a updown selector. To use a range slider instead we configure a [user interface schema](https://react-jsonschema-form.readthedocs.io/en/latest/quickstart/#form-uischema). +The form [by default](https://react-jsonschema-form.readthedocs.io/en/latest/usage/themes/) uses the [Bootstrap 3](https://getbootstrap.com/docs/3.4/) theme. The theme injects class names into the HTML tags. The styles associated with the class names must be imported from the Bootstrap CSS file. + +```{.html #imports} + + +``` + +The react-jsonschema-form component normally renders an integer with a updown selector. To use a range slider instead configure a [user interface schema](https://react-jsonschema-form.readthedocs.io/en/latest/quickstart/#form-uischema). ```{.js #jsonschema-app} const uiSchema = { @@ -1305,7 +1305,7 @@ function handleSubmit({formData}, event) { const worker = new Worker('worker.js'); worker.postMessage({ type: 'CALCULATE', - payload: { epsilon: formData.epsilon, guess: formData.guess } + payload: formData }); worker.onmessage = function(message) { if (message.data.type === 'RESULT') { @@ -1355,7 +1355,7 @@ python3 -m http.server 8000 Visit [http://localhost:8000/src/js/example-jsonschema-form.html](http://localhost:8000/src/js/example-jsonschema-form.html) to see the root answer. -If you enter a negative number in the `epsilon` field the form will be invalid and refuse to submit. +If you enter a negative number in the `epsilon` field the form will become invalid with a error message. ### Visualization diff --git a/src/js/jsonschema-app.js b/src/js/jsonschema-app.js index 3425c07..0237341 100644 --- a/src/js/jsonschema-app.js +++ b/src/js/jsonschema-app.js @@ -45,7 +45,7 @@ function App() { const worker = new Worker('worker.js'); worker.postMessage({ type: 'CALCULATE', - payload: { epsilon: formData.epsilon, guess: formData.guess } + payload: formData }); worker.onmessage = function(message) { if (message.data.type === 'RESULT') { From 2a090809c6e21ae7ff18b2ec75c30e01f73398f5 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Tue, 26 May 2020 11:34:00 +0200 Subject: [PATCH 05/12] Fix tests --- README.md | 6 +++--- TESTING.md | 8 ++++---- cypress/integration/example-app_spec.js | 4 ++-- cypress/integration/example-jsonschema-form_spec.js | 4 ++-- src/js/app.js | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 1421ea0..94065ff 100644 --- a/README.md +++ b/README.md @@ -1067,12 +1067,12 @@ const [epsilon, setEpsilon] = React.useState(0.001); The argument of the `useState` function is the initial value. The `epsilon` variable contains the current value for epsilon and `setEpsilon` is a function to set epsilon to a new value. -The input tag in the form will call the `onChange` function with a event object. We need to extract the user input from the event and pass it to `setEpsilon`. +The input tag in the form will call the `onChange` function with a event object. We need to extract the user input from the event and pass it to `setEpsilon`. The value should be a number, so we use `*1` to cast the string from the event to a number. ```{.js #react-state} // this JavaScript snippet is appended to <> function onEpsilonChange(event) { - setEpsilon(event.target.value); + setEpsilon(event.target.value*1); } ``` @@ -1083,7 +1083,7 @@ We will follow the same steps for the guess input as well. const [guess, setGuess] = React.useState(-20); function onGuessChange(event) { - setGuess(event.target.value); + setGuess(event.target.value*1); } ``` diff --git a/TESTING.md b/TESTING.md index 21fb37d..55ee274 100644 --- a/TESTING.md +++ b/TESTING.md @@ -38,8 +38,8 @@ Let us also change the guess value. describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); - cy.get('input[name=guess]').type('-30'); - cy.get('input[id=root_guess]').contains('-30'); + cy.get('input[name=guess]').type('0'); + // TODO assert value is set cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); @@ -52,8 +52,8 @@ And another test for the full application, but now with JSON schema powered form describe('src/js/example-jsonschema-form.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-jsonschema-form.html'); - cy.get('input[id=root_guess]').type('-30'); - cy.get('input[id=root_guess]').contains('-30'); + cy.get('input[id=root_epsilon]').type('{selectall}0.1'); + // TODO assert value is set cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); diff --git a/cypress/integration/example-app_spec.js b/cypress/integration/example-app_spec.js index f084140..65ea69e 100644 --- a/cypress/integration/example-app_spec.js +++ b/cypress/integration/example-app_spec.js @@ -1,8 +1,8 @@ describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); - cy.get('input[name=guess]').type('-30'); - cy.get('input[id=root_guess]').contains('-30'); + cy.get('input[name=guess]').type('0'); + // TODO assert value is set cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); diff --git a/cypress/integration/example-jsonschema-form_spec.js b/cypress/integration/example-jsonschema-form_spec.js index 7069561..ead379d 100644 --- a/cypress/integration/example-jsonschema-form_spec.js +++ b/cypress/integration/example-jsonschema-form_spec.js @@ -1,8 +1,8 @@ describe('src/js/example-jsonschema-form.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-jsonschema-form.html'); - cy.get('input[id=root_guess]').type('-30'); - cy.get('input[id=root_guess]').contains('-30'); + cy.get('input[id=root_epsilon]').type('{selectall}0.1'); + // TODO assert value is set cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); diff --git a/src/js/app.js b/src/js/app.js index a405e78..cfcbbc1 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -19,13 +19,13 @@ function App() { const [epsilon, setEpsilon] = React.useState(0.001); // this JavaScript snippet is appended to <> function onEpsilonChange(event) { - setEpsilon(event.target.value); + setEpsilon(event.target.value*1); } // this JavaScript snippet is appended to <> const [guess, setGuess] = React.useState(-20); function onGuessChange(event) { - setGuess(event.target.value); + setGuess(event.target.value*1); } // this JavaScript snippet is appended to <> const [root, setRoot] = React.useState(undefined); From 87a7c6f0b2cff209da0f5fde7e3888756cef7ab0 Mon Sep 17 00:00:00 2001 From: Johannes Hidding Date: Tue, 2 Jun 2020 11:32:09 +0200 Subject: [PATCH 06/12] Update INSTALL.md Instructions should be updated to new Entangled version --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index d6dd941..2be4ec1 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -18,7 +18,7 @@ Entangled is used to convert code blocks in Markdown to source code files. 2. Run entangled daemon with ```shell -entangled README.md INSTALL.md +entangled daemon README.md INSTALL.md ``` Or the [Entangled - Pandoc filters](https://github.com/entangled/filters) Docker image can be used From 2c5baadcb30c854b700685cf479438dfa311b795 Mon Sep 17 00:00:00 2001 From: Johannes Hidding Date: Tue, 2 Jun 2020 11:37:40 +0200 Subject: [PATCH 07/12] Create entangled.dhall --- entangled.dhall | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 entangled.dhall diff --git a/entangled.dhall b/entangled.dhall new file mode 100644 index 0000000..464f0bb --- /dev/null +++ b/entangled.dhall @@ -0,0 +1,7 @@ +let entangled = https://raw.githubusercontent.com/entangled/entangled/v1.0.1/data/config-schema.dhall + sha256:9fd18824499379eee53b974ca7570b3bc064fda546348d9b31841afab3b053a7 + +in { entangled = entangled.Config :: { database = Some ".entangled/db.sqlite" + , watchList = ["README.md", "INSTALL.md"] : List Text + } + } From 64f7335ae3ab1f51e2d057514a2517846d9c662c Mon Sep 17 00:00:00 2001 From: Johannes Hidding Date: Tue, 2 Jun 2020 11:38:13 +0200 Subject: [PATCH 08/12] Update INSTALL.md --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 2be4ec1..2cdd609 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -18,7 +18,7 @@ Entangled is used to convert code blocks in Markdown to source code files. 2. Run entangled daemon with ```shell -entangled daemon README.md INSTALL.md +entangled daemon ``` Or the [Entangled - Pandoc filters](https://github.com/entangled/filters) Docker image can be used From 1bb93c5da803427c5e2add9d9734375925aa5fa4 Mon Sep 17 00:00:00 2001 From: Johannes Hidding Date: Tue, 2 Jun 2020 13:15:38 +0200 Subject: [PATCH 09/12] Add Entangled badge Up for discussion, but I thought this is a nice touch. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3c3da4a..3f0e181 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ - [Visualization](#visualization) [![CI](https://github.com/NLESC-JCER/cpp2wasm/workflows/CI/badge.svg)](https://github.com/NLESC-JCER/cpp2wasm/actions?query=workflow%3ACI) +[![Entangled](https://img.shields.io/badge/entangled-Use%20the%20source!-%2300aeff)](https://entangled.github.io/) Document describing a way that a researcher with a C++ algorithm can make it available as a web application. We will host the C++ algorithm as an web application in several different ways: From af7ecd01cd23622dc6cad86f28c634a507001880 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Tue, 2 Jun 2020 13:29:05 +0200 Subject: [PATCH 10/12] Use entangled v1 subcommand to run daemon --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 29cc302..f3bf527 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,7 +123,7 @@ Each time a source code file is changed the code blocks in the Markdown files wi 2. Run entangled daemon with ```shell -entangled *.md +entangled daemon ``` ### Generate code from Markdown on commit From bf9d3395028e22206bde8bd04b48ff9f9a93ed59 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Tue, 2 Jun 2020 13:29:25 +0200 Subject: [PATCH 11/12] Make entangled aware of all Markdown files --- entangled.dhall | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entangled.dhall b/entangled.dhall index 464f0bb..ea5bcba 100644 --- a/entangled.dhall +++ b/entangled.dhall @@ -2,6 +2,6 @@ let entangled = https://raw.githubusercontent.com/entangled/entangled/v1.0.1/dat sha256:9fd18824499379eee53b974ca7570b3bc064fda546348d9b31841afab3b053a7 in { entangled = entangled.Config :: { database = Some ".entangled/db.sqlite" - , watchList = ["README.md", "INSTALL.md"] : List Text + , watchList = ["README.md", "INSTALL.md", "CONTRIBUTING.md", "TESTING.md"] : List Text } } From 5208404406f85a1dd0be882268b21df8728e0c9d Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Thu, 4 Jun 2020 12:06:03 +0200 Subject: [PATCH 12/12] Use Number() instead of *1 --- README.md | 6 +++--- src/js/app.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a0ca2b0..d75f520 100644 --- a/README.md +++ b/README.md @@ -1005,12 +1005,12 @@ const [epsilon, setEpsilon] = React.useState(0.001); The argument of the `useState` function is the initial value. The `epsilon` variable contains the current value for epsilon and `setEpsilon` is a function to set epsilon to a new value. -The input tag in the form will call the `onChange` function with a event object. We need to extract the user input from the event and pass it to `setEpsilon`. The value should be a number, so we use `*1` to cast the string from the event to a number. +The input tag in the form will call the `onChange` function with a event object. We need to extract the user input from the event and pass it to `setEpsilon`. The value should be a number, so we use `Number()` to cast the string from the event to a number. ```{.js #react-state} // this JavaScript snippet is appended to <> function onEpsilonChange(event) { - setEpsilon(event.target.value*1); + setEpsilon(Number(event.target.value)); } ``` @@ -1021,7 +1021,7 @@ We will follow the same steps for the guess input as well. const [guess, setGuess] = React.useState(-20); function onGuessChange(event) { - setGuess(event.target.value*1); + setGuess(Number(event.target.value)); } ``` diff --git a/src/js/app.js b/src/js/app.js index cfcbbc1..239d078 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -19,13 +19,13 @@ function App() { const [epsilon, setEpsilon] = React.useState(0.001); // this JavaScript snippet is appended to <> function onEpsilonChange(event) { - setEpsilon(event.target.value*1); + setEpsilon(Number(event.target.value)); } // this JavaScript snippet is appended to <> const [guess, setGuess] = React.useState(-20); function onGuessChange(event) { - setGuess(event.target.value*1); + setGuess(Number(event.target.value)); } // this JavaScript snippet is appended to <> const [root, setRoot] = React.useState(undefined);