commit 02a760984da202cfa71f319d10da20d9482699df Author: Ozzie Gooen Date: Wed Feb 5 21:09:27 2020 +0000 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9c32a28e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.merlin +.bsb.lock +npm-debug.log +/lib/bs/ +/node_modules/ +/bundleOutput/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..e55514ec --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# ReasonReact Template & Examples + +This is: +- A template for your new ReasonReact project. +- A collection of thin examples illustrating ReasonReact usage. +- Extra helper documentation for ReasonReact (full ReasonReact docs [here](https://reasonml.github.io/reason-react/)). + +`src` contains 4 sub-folders, each an independent, self-contained ReasonReact example. Feel free to delete any of them and shape this into your project! This template's more malleable than you might be used to =). + +The point of this template and examples is to let you understand and personally tweak the entirely of it. We **don't** give you an opaque, elaborate mega build setup just to put some boxes on the screen. It strikes to stay transparent, learnable, and simple. You're encouraged to read every file; it's a great feeling, having the full picture of what you're using and being able to touch any part. + +## Run + +```sh +npm install +npm run server +# in a new tab +npm start +``` + +Open a new web page to `http://localhost:8000/`. Change any `.re` file in `src` to see the page auto-reload. **You don't need any bundler when you're developing**! + +**How come we don't need any bundler during development**? We highly encourage you to open up `index.html` to check for yourself! + +# Features Used + +| | Blinking Greeting | Reducer from ReactJS Docs | Fetch Dog Pictures | Reason Using JS Using Reason | +|---------------------------|-------------------|---------------------------|--------------------|------------------------------| +| No props | | ✓ | | | +| Has props | | | | ✓ | +| Children props | ✓ | | | | +| No state | | | | ✓ | +| Has state | ✓ | | ✓ | | +| Has state with useReducer | | ✓ | | | +| ReasonReact using ReactJS | | | | ✓ | +| ReactJS using ReasonReact | | | | ✓ | +| useEffect | ✓ | | ✓ | | +| Dom attribute | ✓ | ✓ | | ✓ | +| Styling | ✓ | ✓ | ✓ | ✓ | +| React.array | | | ✓ | | + +# Bundle for Production + +We've included a convenience `UNUSED_webpack.config.js`, in case you want to ship your project to production. You can rename and/or remove that in favor of other bundlers, e.g. Rollup. + +We've also provided a barebone `indexProduction.html`, to serve your bundle. + +```sh +npm install webpack webpack-cli +# rename file +mv UNUSED_webpack.config.js webpack.config.js +# call webpack to bundle for production +./node_modules/.bin/webpack +open indexProduction.html +``` + +# Handle Routing Yourself + +To serve the files, this template uses a minimal dependency called `moduleserve`. A URL such as `localhost:8000/scores/john` resolves to the file `scores/john.html`. If you'd like to override this and handle URL resolution yourself, change the `server` command in `package.json` from `moduleserve ./ --port 8000` to `moduleserve ./ --port 8000 --spa` (for "single page application"). This will make `moduleserve` serve the default `index.html` for any URL. Since `index.html` loads `Index.bs.js`, you can grab hold of the URL in the corresponding `Index.re` and do whatever you want. + +By the way, ReasonReact comes with a small [router](https://reasonml.github.io/reason-react/docs/en/router) you might be interested in. diff --git a/UNUSED_webpack.config.js b/UNUSED_webpack.config.js new file mode 100644 index 00000000..3260e8f6 --- /dev/null +++ b/UNUSED_webpack.config.js @@ -0,0 +1,14 @@ +const path = require('path'); + +module.exports = { + entry: './src/Index.bs.js', + // If you ever want to use webpack during development, change 'production' + // to 'development' as per webpack documentation. Again, you don't have to + // use webpack or any other bundler during development! Recheck README if + // you didn't know this + mode: 'production', + output: { + path: path.join(__dirname, "bundleOutput"), + filename: 'index.js', + }, +}; \ No newline at end of file diff --git a/bsconfig.json b/bsconfig.json new file mode 100644 index 00000000..72bbb445 --- /dev/null +++ b/bsconfig.json @@ -0,0 +1,21 @@ +{ + "name": "reason-react-examples", + "reason": { + "react-jsx": 3 + }, + "sources": { + "dir" : "src", + "subdirs" : true + }, + "bsc-flags": ["-bs-super-errors", "-bs-no-version-header"], + "package-specs": [{ + "module": "commonjs", + "in-source": true + }], + "suffix": ".bs.js", + "namespace": true, + "bs-dependencies": [ + "reason-react" + ], + "refmt": 3 +} diff --git a/index.html b/index.html new file mode 100644 index 00000000..94689a3b --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + + ReasonReact Examples + + + + + + + + + + diff --git a/indexProduction.html b/indexProduction.html new file mode 100644 index 00000000..1597bfa6 --- /dev/null +++ b/indexProduction.html @@ -0,0 +1,10 @@ + + + + + ReasonReact Examples + + + + + diff --git a/my-react-app/.gitignore b/my-react-app/.gitignore new file mode 100644 index 00000000..9c32a28e --- /dev/null +++ b/my-react-app/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.merlin +.bsb.lock +npm-debug.log +/lib/bs/ +/node_modules/ +/bundleOutput/ \ No newline at end of file diff --git a/my-react-app/README.md b/my-react-app/README.md new file mode 100644 index 00000000..e55514ec --- /dev/null +++ b/my-react-app/README.md @@ -0,0 +1,61 @@ +# ReasonReact Template & Examples + +This is: +- A template for your new ReasonReact project. +- A collection of thin examples illustrating ReasonReact usage. +- Extra helper documentation for ReasonReact (full ReasonReact docs [here](https://reasonml.github.io/reason-react/)). + +`src` contains 4 sub-folders, each an independent, self-contained ReasonReact example. Feel free to delete any of them and shape this into your project! This template's more malleable than you might be used to =). + +The point of this template and examples is to let you understand and personally tweak the entirely of it. We **don't** give you an opaque, elaborate mega build setup just to put some boxes on the screen. It strikes to stay transparent, learnable, and simple. You're encouraged to read every file; it's a great feeling, having the full picture of what you're using and being able to touch any part. + +## Run + +```sh +npm install +npm run server +# in a new tab +npm start +``` + +Open a new web page to `http://localhost:8000/`. Change any `.re` file in `src` to see the page auto-reload. **You don't need any bundler when you're developing**! + +**How come we don't need any bundler during development**? We highly encourage you to open up `index.html` to check for yourself! + +# Features Used + +| | Blinking Greeting | Reducer from ReactJS Docs | Fetch Dog Pictures | Reason Using JS Using Reason | +|---------------------------|-------------------|---------------------------|--------------------|------------------------------| +| No props | | ✓ | | | +| Has props | | | | ✓ | +| Children props | ✓ | | | | +| No state | | | | ✓ | +| Has state | ✓ | | ✓ | | +| Has state with useReducer | | ✓ | | | +| ReasonReact using ReactJS | | | | ✓ | +| ReactJS using ReasonReact | | | | ✓ | +| useEffect | ✓ | | ✓ | | +| Dom attribute | ✓ | ✓ | | ✓ | +| Styling | ✓ | ✓ | ✓ | ✓ | +| React.array | | | ✓ | | + +# Bundle for Production + +We've included a convenience `UNUSED_webpack.config.js`, in case you want to ship your project to production. You can rename and/or remove that in favor of other bundlers, e.g. Rollup. + +We've also provided a barebone `indexProduction.html`, to serve your bundle. + +```sh +npm install webpack webpack-cli +# rename file +mv UNUSED_webpack.config.js webpack.config.js +# call webpack to bundle for production +./node_modules/.bin/webpack +open indexProduction.html +``` + +# Handle Routing Yourself + +To serve the files, this template uses a minimal dependency called `moduleserve`. A URL such as `localhost:8000/scores/john` resolves to the file `scores/john.html`. If you'd like to override this and handle URL resolution yourself, change the `server` command in `package.json` from `moduleserve ./ --port 8000` to `moduleserve ./ --port 8000 --spa` (for "single page application"). This will make `moduleserve` serve the default `index.html` for any URL. Since `index.html` loads `Index.bs.js`, you can grab hold of the URL in the corresponding `Index.re` and do whatever you want. + +By the way, ReasonReact comes with a small [router](https://reasonml.github.io/reason-react/docs/en/router) you might be interested in. diff --git a/my-react-app/UNUSED_webpack.config.js b/my-react-app/UNUSED_webpack.config.js new file mode 100644 index 00000000..3260e8f6 --- /dev/null +++ b/my-react-app/UNUSED_webpack.config.js @@ -0,0 +1,14 @@ +const path = require('path'); + +module.exports = { + entry: './src/Index.bs.js', + // If you ever want to use webpack during development, change 'production' + // to 'development' as per webpack documentation. Again, you don't have to + // use webpack or any other bundler during development! Recheck README if + // you didn't know this + mode: 'production', + output: { + path: path.join(__dirname, "bundleOutput"), + filename: 'index.js', + }, +}; \ No newline at end of file diff --git a/my-react-app/bsconfig.json b/my-react-app/bsconfig.json new file mode 100644 index 00000000..72bbb445 --- /dev/null +++ b/my-react-app/bsconfig.json @@ -0,0 +1,21 @@ +{ + "name": "reason-react-examples", + "reason": { + "react-jsx": 3 + }, + "sources": { + "dir" : "src", + "subdirs" : true + }, + "bsc-flags": ["-bs-super-errors", "-bs-no-version-header"], + "package-specs": [{ + "module": "commonjs", + "in-source": true + }], + "suffix": ".bs.js", + "namespace": true, + "bs-dependencies": [ + "reason-react" + ], + "refmt": 3 +} diff --git a/my-react-app/index.html b/my-react-app/index.html new file mode 100644 index 00000000..94689a3b --- /dev/null +++ b/my-react-app/index.html @@ -0,0 +1,22 @@ + + + + + ReasonReact Examples + + + + + + + + + + diff --git a/my-react-app/indexProduction.html b/my-react-app/indexProduction.html new file mode 100644 index 00000000..1597bfa6 --- /dev/null +++ b/my-react-app/indexProduction.html @@ -0,0 +1,10 @@ + + + + + ReasonReact Examples + + + + + diff --git a/my-react-app/package.json b/my-react-app/package.json new file mode 100644 index 00000000..c21839e2 --- /dev/null +++ b/my-react-app/package.json @@ -0,0 +1,27 @@ +{ + "name": "my-react-app", + "version": "0.1.0", + "scripts": { + "build": "bsb -make-world", + "start": "bsb -make-world -w -ws _ ", + "clean": "bsb -clean-world", + "server": "moduleserve ./ --port 8000", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "BuckleScript", + "ReasonReact", + "reason-react" + ], + "author": "", + "license": "MIT", + "dependencies": { + "react": "^16.8.1", + "react-dom": "^16.8.1", + "reason-react": ">=0.7.0" + }, + "devDependencies": { + "bs-platform": "^7.1.0", + "moduleserve": "^0.9.0" + } +} diff --git a/my-react-app/src/BlinkingGreeting/BlinkingGreeting.re b/my-react-app/src/BlinkingGreeting/BlinkingGreeting.re new file mode 100644 index 00000000..d1c1865d --- /dev/null +++ b/my-react-app/src/BlinkingGreeting/BlinkingGreeting.re @@ -0,0 +1,25 @@ +[@react.component] +let make = (~children) => { + let (show, setShow) = React.useState(() => true); + + // Notice that instead of `useEffect`, we have `useEffect0`. See + // reasonml.github.io/reason-react/docs/en/components#hooks for more info + React.useEffect0(() => { + let id = + Js.Global.setInterval( + () => setShow(previousShow => !previousShow), + 1000, + ); + + Some(() => Js.Global.clearInterval(id)); + }); + + let style = + if (show) { + ReactDOMRe.Style.make(~opacity="1", ~transition="opacity 1s", ()); + } else { + ReactDOMRe.Style.make(~opacity="0", ~transition="opacity 1s", ()); + }; + +
children
; +}; diff --git a/my-react-app/src/ExampleStyles.re b/my-react-app/src/ExampleStyles.re new file mode 100644 index 00000000..4a097747 --- /dev/null +++ b/my-react-app/src/ExampleStyles.re @@ -0,0 +1,44 @@ +let reasonReactBlue = "#48a9dc"; + +// The {j|...|j} feature is just string interpolation, from +// bucklescript.github.io/docs/en/interop-cheatsheet#string-unicode-interpolation +// This allows us to conveniently write CSS, together with variables, by +// constructing a string +let style = {j| + body { + background-color: rgb(224, 226, 229); + display: flex; + flex-direction: column; + align-items: center; + } + button { + background-color: white; + color: $reasonReactBlue; + box-shadow: 0 0 0 1px $reasonReactBlue; + border: none; + padding: 8px; + font-size: 16px; + } + button:active { + background-color: $reasonReactBlue; + color: white; + } + .container { + margin: 12px 0px; + box-shadow: 0px 4px 16px rgb(200, 200, 200); + width: 720px; + border-radius: 12px; + font-family: sans-serif; + } + .containerTitle { + background-color: rgb(242, 243, 245); + border-radius: 12px 12px 0px 0px; + padding: 12px; + font-weight: bold; + } + .containerContent { + background-color: white; + padding: 16px; + border-radius: 0px 0px 12px 12px; + } +|j}; diff --git a/my-react-app/src/FetchedDogPictures/FetchedDogPictures.re b/my-react-app/src/FetchedDogPictures/FetchedDogPictures.re new file mode 100644 index 00000000..2a32618b --- /dev/null +++ b/my-react-app/src/FetchedDogPictures/FetchedDogPictures.re @@ -0,0 +1,70 @@ +[@bs.val] external fetch: string => Js.Promise.t('a) = "fetch"; + +type state = + | LoadingDogs + | ErrorFetchingDogs + | LoadedDogs(array(string)); + +[@react.component] +let make = () => { + let (state, setState) = React.useState(() => LoadingDogs); + + // Notice that instead of `useEffect`, we have `useEffect0`. See + // reasonml.github.io/reason-react/docs/en/components#hooks for more info + React.useEffect0(() => { + Js.Promise.( + fetch("https://dog.ceo/api/breeds/image/random/3") + |> then_(response => response##json()) + |> then_(jsonResponse => { + setState(_previousState => LoadedDogs(jsonResponse##message)); + Js.Promise.resolve(); + }) + |> catch(_err => { + setState(_previousState => ErrorFetchingDogs); + Js.Promise.resolve(); + }) + |> ignore + ); + + // Returning None, instead of Some(() => ...), means we don't have any + // cleanup to do before unmounting. That's not 100% true. We should + // technically cancel the promise. Unofortunately, there's currently no + // way to cancel a promise. Promises in general should be way less used + // for React components; but since folks do use them, we provide such an + // example here. In reality, this fetch should just be a plain callback, + // with a cancellation API + None; + }); + +
+ {switch (state) { + | ErrorFetchingDogs => React.string("An error occurred!") + | LoadingDogs => React.string("Loading...") + | LoadedDogs(dogs) => + dogs + ->Belt.Array.mapWithIndex((i, dog) => { + let imageStyle = + ReactDOMRe.Style.make( + ~height="120px", + ~width="100%", + ~marginRight=i === Js.Array.length(dogs) - 1 ? "0px" : "8px", + ~borderRadius="8px", + ~boxShadow="0px 4px 16px rgb(200, 200, 200)", + ~backgroundSize="cover", + ~backgroundImage={j|url($dog)|j}, + ~backgroundPosition="center", + (), + ); +
; + }) + ->React.array + }} +
; +}; diff --git a/my-react-app/src/Index.re b/my-react-app/src/Index.re new file mode 100644 index 00000000..c45ca054 --- /dev/null +++ b/my-react-app/src/Index.re @@ -0,0 +1,51 @@ +// Entry point + +[@bs.val] external document: Js.t({..}) = "document"; + +// We're using raw DOM manipulations here, to avoid making you read +// ReasonReact when you might precisely be trying to learn it for the first +// time through the examples later. +let style = document##createElement("style"); +document##head##appendChild(style); +style##innerHTML #= ExampleStyles.style; + +let makeContainer = text => { + let container = document##createElement("div"); + container##className #= "container"; + + let title = document##createElement("div"); + title##className #= "containerTitle"; + title##innerText #= text; + + let content = document##createElement("div"); + content##className #= "containerContent"; + + let () = container##appendChild(title); + let () = container##appendChild(content); + let () = document##body##appendChild(container); + + content; +}; + +// All 4 examples. +ReactDOMRe.render( + + {React.string("Hello!")} + , + makeContainer("Blinking Greeting"), +); + +ReactDOMRe.render( + , + makeContainer("Reducer From ReactJS Docs"), +); + +ReactDOMRe.render( + , + makeContainer("Fetched Dog Pictures"), +); + +ReactDOMRe.render( + , + makeContainer("Reason Using JS Using Reason"), +); diff --git a/my-react-app/src/ReasonUsingJSUsingReason/ReactJSCard.js b/my-react-app/src/ReasonUsingJSUsingReason/ReactJSCard.js new file mode 100644 index 00000000..6e71d726 --- /dev/null +++ b/my-react-app/src/ReasonUsingJSUsingReason/ReactJSCard.js @@ -0,0 +1,31 @@ +// In this Interop example folder, we have: +// - A ReasonReact component, ReasonReactCard.re +// - Used by a ReactJS component, ReactJSCard.js (this file) +// - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re +// - ReasonUsingJSUsingReason.re is used by Index.re + +var ReactDOM = require('react-dom'); +var React = require('react'); + +var ReasonReactCard = require('./ReasonReactCard.bs').make; + +var ReactJSComponent = function() { + let backgroundColor = "rgba(0, 0, 0, 0.05)"; + let padding = "12px"; + + // We're not using JSX here, to avoid folks needing to install the related + // React toolchains just for this example. + //
+ //
This is a ReactJS card
+ // + //
+ return React.createElement( + "div", + {style: {backgroundColor, padding, borderRadius: "8px"}}, + React.createElement("div", {style: {marginBottom: "8px"}}, "This is a ReactJS card"), + React.createElement(ReasonReactCard, {style: {backgroundColor, padding, borderRadius: "4px"}}), + ) +}; +ReactJSComponent.displayName = "MyBanner"; + +module.exports = ReactJSComponent; diff --git a/my-react-app/src/ReasonUsingJSUsingReason/ReasonReactCard.re b/my-react-app/src/ReasonUsingJSUsingReason/ReasonReactCard.re new file mode 100644 index 00000000..71c38005 --- /dev/null +++ b/my-react-app/src/ReasonUsingJSUsingReason/ReasonReactCard.re @@ -0,0 +1,10 @@ +// In this Interop example folder, we have: +// - A ReasonReact component, ReasonReactCard.re (this file) +// - Used by a ReactJS component, ReactJSCard.js +// - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re +// - ReasonUsingJSUsingReason.re is used by Index.re + +[@react.component] +let make = (~style) => { +
{React.string("This is a ReasonReact card")}
; +}; diff --git a/my-react-app/src/ReasonUsingJSUsingReason/ReasonUsingJSUsingReason.re b/my-react-app/src/ReasonUsingJSUsingReason/ReasonUsingJSUsingReason.re new file mode 100644 index 00000000..45a66fcf --- /dev/null +++ b/my-react-app/src/ReasonUsingJSUsingReason/ReasonUsingJSUsingReason.re @@ -0,0 +1,10 @@ +// In this Interop example folder, we have: +// - A ReasonReact component, ReasonReactCard.re +// - Used by a ReactJS component, ReactJSCard.js +// - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re (this file) +// - ReasonUsingJSUsingReason.re is used by Index.re + +// All you need to do to use a ReactJS component in ReasonReact, is to write the lines below! +// reasonml.github.io/reason-react/docs/en/components#import-from-js +[@react.component] [@bs.module] +external make: unit => React.element = "./ReactJSCard"; diff --git a/my-react-app/src/ReducerFromReactJSDocs/ReducerFromReactJSDocs.re b/my-react-app/src/ReducerFromReactJSDocs/ReducerFromReactJSDocs.re new file mode 100644 index 00000000..2bacf52e --- /dev/null +++ b/my-react-app/src/ReducerFromReactJSDocs/ReducerFromReactJSDocs.re @@ -0,0 +1,44 @@ +// This is the ReactJS documentation's useReducer example, directly ported over +// https://reactjs.org/docs/hooks-reference.html#usereducer + +// A little extra we've put, because the ReactJS example has no styling +let leftButtonStyle = ReactDOMRe.Style.make(~borderRadius="4px 0px 0px 4px", ~width="48px", ()); +let rightButtonStyle = ReactDOMRe.Style.make(~borderRadius="0px 4px 4px 0px", ~width="48px", ()); + +// Record and variant need explicit declarations. +type state = {count: int}; + +type action = + | Increment + | Decrement; + +let initialState = {count: 0}; + +let reducer = (state, action) => { + switch (action) { + | Increment => {count: state.count + 1} + | Decrement => {count: state.count - 1} + }; +}; + +[@react.component] +let make = () => { + let (state, dispatch) = React.useReducer(reducer, initialState); + + // We can use a fragment here, but we don't, because we want to style the counter +
+
+ {React.string("Count: ")} + {React.string(string_of_int(state.count))} +
+
+ + +
+
; +}; diff --git a/my-react-app/watcher.js b/my-react-app/watcher.js new file mode 100644 index 00000000..894a19cb --- /dev/null +++ b/my-react-app/watcher.js @@ -0,0 +1,39 @@ +// This is our simple, robust watcher. It hooks into the BuckleScript build +// system to listen for build events. +// See package.json's `start` script and `./node_modules/.bin/bsb --help` + +// Btw, if you change this file and reload the page, your browser cache +// _might_ not pick up the new version. If you're in Chrome, do Force Reload. + +var websocketReloader; +var LAST_SUCCESS_BUILD_STAMP = localStorage.getItem('LAST_SUCCESS_BUILD_STAMP') || 0; +// package.json's `start` script's `bsb -ws _` means it'll pipe build events +// through a websocket connection to a default port of 9999. This is +// configurable, e.g. `-ws 5000` +var webSocketPort = 9999; + +function setUpWebSocket() { + if (websocketReloader == null || websocketReloader.readyState !== 1) { + try { + websocketReloader = new WebSocket(`ws://localhost:${webSocketPort}`); + websocketReloader.onmessage = (message) => { + var newData = JSON.parse(message.data).LAST_SUCCESS_BUILD_STAMP; + if (newData > LAST_SUCCESS_BUILD_STAMP) { + LAST_SUCCESS_BUILD_STAMP = newData; + localStorage.setItem('LAST_SUCCESS_BUILD_STAMP', LAST_SUCCESS_BUILD_STAMP); + // Refresh the page! This will naturally re-run everything, + // including our moduleserve which will re-resolve all the modules. + // No stable build! + location.reload(true); + } + + } + } catch (exn) { + console.error("The watcher tried to connect to web socket, but failed. Here's the message:"); + console.error(exn); + } + } +}; + +setUpWebSocket(); +setInterval(setUpWebSocket, 2000); diff --git a/package.json b/package.json new file mode 100644 index 00000000..dfb8b30d --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "probtest", + "version": "0.1.0", + "scripts": { + "build": "bsb -make-world", + "start": "bsb -make-world -w -ws _ ", + "clean": "bsb -clean-world", + "server": "moduleserve ./ --port 8000", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "BuckleScript", + "ReasonReact", + "reason-react" + ], + "author": "", + "license": "MIT", + "dependencies": { + "react": "^16.8.1", + "react-dom": "^16.8.1", + "reason-react": ">=0.7.0" + }, + "devDependencies": { + "bs-platform": "^7.1.0", + "moduleserve": "^0.9.0" + } +} diff --git a/src/BlinkingGreeting/BlinkingGreeting.bs.js b/src/BlinkingGreeting/BlinkingGreeting.bs.js new file mode 100644 index 00000000..f5f4d0c6 --- /dev/null +++ b/src/BlinkingGreeting/BlinkingGreeting.bs.js @@ -0,0 +1,38 @@ +'use strict'; + +var Curry = require("bs-platform/lib/js/curry.js"); +var React = require("react"); + +function BlinkingGreeting(Props) { + var children = Props.children; + var match = React.useState((function () { + return true; + })); + var setShow = match[1]; + React.useEffect((function () { + var id = setInterval((function (param) { + return Curry._1(setShow, (function (previousShow) { + return !previousShow; + })); + }), 1000); + return (function (param) { + clearInterval(id); + return /* () */0; + }); + }), ([])); + var style = match[0] ? ({ + opacity: "1", + transition: "opacity 1s" + }) : ({ + opacity: "0", + transition: "opacity 1s" + }); + return React.createElement("div", { + style: style + }, children); +} + +var make = BlinkingGreeting; + +exports.make = make; +/* react Not a pure module */ diff --git a/src/BlinkingGreeting/BlinkingGreeting.re b/src/BlinkingGreeting/BlinkingGreeting.re new file mode 100644 index 00000000..d1c1865d --- /dev/null +++ b/src/BlinkingGreeting/BlinkingGreeting.re @@ -0,0 +1,25 @@ +[@react.component] +let make = (~children) => { + let (show, setShow) = React.useState(() => true); + + // Notice that instead of `useEffect`, we have `useEffect0`. See + // reasonml.github.io/reason-react/docs/en/components#hooks for more info + React.useEffect0(() => { + let id = + Js.Global.setInterval( + () => setShow(previousShow => !previousShow), + 1000, + ); + + Some(() => Js.Global.clearInterval(id)); + }); + + let style = + if (show) { + ReactDOMRe.Style.make(~opacity="1", ~transition="opacity 1s", ()); + } else { + ReactDOMRe.Style.make(~opacity="0", ~transition="opacity 1s", ()); + }; + +
children
; +}; diff --git a/src/ExampleStyles.bs.js b/src/ExampleStyles.bs.js new file mode 100644 index 00000000..0d511991 --- /dev/null +++ b/src/ExampleStyles.bs.js @@ -0,0 +1,10 @@ +'use strict'; + + +var reasonReactBlue = "#48a9dc"; + +var style = "\n body {\n background-color: rgb(224, 226, 229);\n display: flex;\n flex-direction: column;\n align-items: center;\n }\n button {\n background-color: white;\n color: " + (String(reasonReactBlue) + (";\n box-shadow: 0 0 0 1px " + (String(reasonReactBlue) + (";\n border: none;\n padding: 8px;\n font-size: 16px;\n }\n button:active {\n background-color: " + (String(reasonReactBlue) + ";\n color: white;\n }\n .container {\n margin: 12px 0px;\n box-shadow: 0px 4px 16px rgb(200, 200, 200);\n width: 720px;\n border-radius: 12px;\n font-family: sans-serif;\n }\n .containerTitle {\n background-color: rgb(242, 243, 245);\n border-radius: 12px 12px 0px 0px;\n padding: 12px;\n font-weight: bold;\n }\n .containerContent {\n background-color: white;\n padding: 16px;\n border-radius: 0px 0px 12px 12px;\n }\n"))))); + +exports.reasonReactBlue = reasonReactBlue; +exports.style = style; +/* style Not a pure module */ diff --git a/src/ExampleStyles.re b/src/ExampleStyles.re new file mode 100644 index 00000000..4a097747 --- /dev/null +++ b/src/ExampleStyles.re @@ -0,0 +1,44 @@ +let reasonReactBlue = "#48a9dc"; + +// The {j|...|j} feature is just string interpolation, from +// bucklescript.github.io/docs/en/interop-cheatsheet#string-unicode-interpolation +// This allows us to conveniently write CSS, together with variables, by +// constructing a string +let style = {j| + body { + background-color: rgb(224, 226, 229); + display: flex; + flex-direction: column; + align-items: center; + } + button { + background-color: white; + color: $reasonReactBlue; + box-shadow: 0 0 0 1px $reasonReactBlue; + border: none; + padding: 8px; + font-size: 16px; + } + button:active { + background-color: $reasonReactBlue; + color: white; + } + .container { + margin: 12px 0px; + box-shadow: 0px 4px 16px rgb(200, 200, 200); + width: 720px; + border-radius: 12px; + font-family: sans-serif; + } + .containerTitle { + background-color: rgb(242, 243, 245); + border-radius: 12px 12px 0px 0px; + padding: 12px; + font-weight: bold; + } + .containerContent { + background-color: white; + padding: 16px; + border-radius: 0px 0px 12px 12px; + } +|j}; diff --git a/src/FetchedDogPictures/FetchedDogPictures.bs.js b/src/FetchedDogPictures/FetchedDogPictures.bs.js new file mode 100644 index 00000000..909b02e5 --- /dev/null +++ b/src/FetchedDogPictures/FetchedDogPictures.bs.js @@ -0,0 +1,64 @@ +'use strict'; + +var Curry = require("bs-platform/lib/js/curry.js"); +var React = require("react"); +var Belt_Array = require("bs-platform/lib/js/belt_Array.js"); + +function FetchedDogPictures(Props) { + var match = React.useState((function () { + return /* LoadingDogs */0; + })); + var setState = match[1]; + var state = match[0]; + React.useEffect((function () { + fetch("https://dog.ceo/api/breeds/image/random/3").then((function (response) { + return response.json(); + })).then((function (jsonResponse) { + Curry._1(setState, (function (_previousState) { + return /* LoadedDogs */[jsonResponse.message]; + })); + return Promise.resolve(/* () */0); + })).catch((function (_err) { + Curry._1(setState, (function (_previousState) { + return /* ErrorFetchingDogs */1; + })); + return Promise.resolve(/* () */0); + })); + return ; + }), ([])); + var tmp; + if (typeof state === "number") { + tmp = state !== 0 ? "An error occurred!" : "Loading..."; + } else { + var dogs = state[0]; + tmp = Belt_Array.mapWithIndex(dogs, (function (i, dog) { + var imageStyle = { + backgroundImage: "url(" + (String(dog) + ")"), + backgroundPosition: "center", + height: "120px", + marginRight: i === (dogs.length - 1 | 0) ? "0px" : "8px", + width: "100%", + backgroundSize: "cover", + borderRadius: "8px", + boxShadow: "0px 4px 16px rgb(200, 200, 200)" + }; + return React.createElement("div", { + key: dog, + style: imageStyle + }); + })); + } + return React.createElement("div", { + style: { + display: "flex", + height: "120px", + alignItems: "center", + justifyContent: "center" + } + }, tmp); +} + +var make = FetchedDogPictures; + +exports.make = make; +/* react Not a pure module */ diff --git a/src/FetchedDogPictures/FetchedDogPictures.re b/src/FetchedDogPictures/FetchedDogPictures.re new file mode 100644 index 00000000..2a32618b --- /dev/null +++ b/src/FetchedDogPictures/FetchedDogPictures.re @@ -0,0 +1,70 @@ +[@bs.val] external fetch: string => Js.Promise.t('a) = "fetch"; + +type state = + | LoadingDogs + | ErrorFetchingDogs + | LoadedDogs(array(string)); + +[@react.component] +let make = () => { + let (state, setState) = React.useState(() => LoadingDogs); + + // Notice that instead of `useEffect`, we have `useEffect0`. See + // reasonml.github.io/reason-react/docs/en/components#hooks for more info + React.useEffect0(() => { + Js.Promise.( + fetch("https://dog.ceo/api/breeds/image/random/3") + |> then_(response => response##json()) + |> then_(jsonResponse => { + setState(_previousState => LoadedDogs(jsonResponse##message)); + Js.Promise.resolve(); + }) + |> catch(_err => { + setState(_previousState => ErrorFetchingDogs); + Js.Promise.resolve(); + }) + |> ignore + ); + + // Returning None, instead of Some(() => ...), means we don't have any + // cleanup to do before unmounting. That's not 100% true. We should + // technically cancel the promise. Unofortunately, there's currently no + // way to cancel a promise. Promises in general should be way less used + // for React components; but since folks do use them, we provide such an + // example here. In reality, this fetch should just be a plain callback, + // with a cancellation API + None; + }); + +
+ {switch (state) { + | ErrorFetchingDogs => React.string("An error occurred!") + | LoadingDogs => React.string("Loading...") + | LoadedDogs(dogs) => + dogs + ->Belt.Array.mapWithIndex((i, dog) => { + let imageStyle = + ReactDOMRe.Style.make( + ~height="120px", + ~width="100%", + ~marginRight=i === Js.Array.length(dogs) - 1 ? "0px" : "8px", + ~borderRadius="8px", + ~boxShadow="0px 4px 16px rgb(200, 200, 200)", + ~backgroundSize="cover", + ~backgroundImage={j|url($dog)|j}, + ~backgroundPosition="center", + (), + ); +
; + }) + ->React.array + }} +
; +}; diff --git a/src/Index.bs.js b/src/Index.bs.js new file mode 100644 index 00000000..236a5a4b --- /dev/null +++ b/src/Index.bs.js @@ -0,0 +1,43 @@ +'use strict'; + +var React = require("react"); +var ReactDom = require("react-dom"); +var ExampleStyles$ReasonReactExamples = require("./ExampleStyles.bs.js"); +var BlinkingGreeting$ReasonReactExamples = require("./BlinkingGreeting/BlinkingGreeting.bs.js"); +var FetchedDogPictures$ReasonReactExamples = require("./FetchedDogPictures/FetchedDogPictures.bs.js"); +var ReducerFromReactJSDocs$ReasonReactExamples = require("./ReducerFromReactJSDocs/ReducerFromReactJSDocs.bs.js"); +var ReasonUsingJSUsingReason$ReasonReactExamples = require("./ReasonUsingJSUsingReason/ReasonUsingJSUsingReason.bs.js"); + +var style = document.createElement("style"); + +document.head.appendChild(style); + +style.innerHTML = ExampleStyles$ReasonReactExamples.style; + +function makeContainer(text) { + var container = document.createElement("div"); + container.className = "container"; + var title = document.createElement("div"); + title.className = "containerTitle"; + title.innerText = text; + var content = document.createElement("div"); + content.className = "containerContent"; + container.appendChild(title); + container.appendChild(content); + document.body.appendChild(container); + return content; +} + +ReactDom.render(React.createElement(BlinkingGreeting$ReasonReactExamples.make, { + children: "Hello!" + }), makeContainer("Blinking Greeting")); + +ReactDom.render(React.createElement(ReducerFromReactJSDocs$ReasonReactExamples.make, { }), makeContainer("Reducer From ReactJS Docs")); + +ReactDom.render(React.createElement(FetchedDogPictures$ReasonReactExamples.make, { }), makeContainer("Fetched Dog Pictures")); + +ReactDom.render(React.createElement(ReasonUsingJSUsingReason$ReasonReactExamples.make, { }), makeContainer("Reason Using JS Using Reason")); + +exports.style = style; +exports.makeContainer = makeContainer; +/* style Not a pure module */ diff --git a/src/Index.re b/src/Index.re new file mode 100644 index 00000000..c45ca054 --- /dev/null +++ b/src/Index.re @@ -0,0 +1,51 @@ +// Entry point + +[@bs.val] external document: Js.t({..}) = "document"; + +// We're using raw DOM manipulations here, to avoid making you read +// ReasonReact when you might precisely be trying to learn it for the first +// time through the examples later. +let style = document##createElement("style"); +document##head##appendChild(style); +style##innerHTML #= ExampleStyles.style; + +let makeContainer = text => { + let container = document##createElement("div"); + container##className #= "container"; + + let title = document##createElement("div"); + title##className #= "containerTitle"; + title##innerText #= text; + + let content = document##createElement("div"); + content##className #= "containerContent"; + + let () = container##appendChild(title); + let () = container##appendChild(content); + let () = document##body##appendChild(container); + + content; +}; + +// All 4 examples. +ReactDOMRe.render( + + {React.string("Hello!")} + , + makeContainer("Blinking Greeting"), +); + +ReactDOMRe.render( + , + makeContainer("Reducer From ReactJS Docs"), +); + +ReactDOMRe.render( + , + makeContainer("Fetched Dog Pictures"), +); + +ReactDOMRe.render( + , + makeContainer("Reason Using JS Using Reason"), +); diff --git a/src/ReasonUsingJSUsingReason/ReactJSCard.js b/src/ReasonUsingJSUsingReason/ReactJSCard.js new file mode 100644 index 00000000..6e71d726 --- /dev/null +++ b/src/ReasonUsingJSUsingReason/ReactJSCard.js @@ -0,0 +1,31 @@ +// In this Interop example folder, we have: +// - A ReasonReact component, ReasonReactCard.re +// - Used by a ReactJS component, ReactJSCard.js (this file) +// - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re +// - ReasonUsingJSUsingReason.re is used by Index.re + +var ReactDOM = require('react-dom'); +var React = require('react'); + +var ReasonReactCard = require('./ReasonReactCard.bs').make; + +var ReactJSComponent = function() { + let backgroundColor = "rgba(0, 0, 0, 0.05)"; + let padding = "12px"; + + // We're not using JSX here, to avoid folks needing to install the related + // React toolchains just for this example. + //
+ //
This is a ReactJS card
+ // + //
+ return React.createElement( + "div", + {style: {backgroundColor, padding, borderRadius: "8px"}}, + React.createElement("div", {style: {marginBottom: "8px"}}, "This is a ReactJS card"), + React.createElement(ReasonReactCard, {style: {backgroundColor, padding, borderRadius: "4px"}}), + ) +}; +ReactJSComponent.displayName = "MyBanner"; + +module.exports = ReactJSComponent; diff --git a/src/ReasonUsingJSUsingReason/ReasonReactCard.bs.js b/src/ReasonUsingJSUsingReason/ReasonReactCard.bs.js new file mode 100644 index 00000000..d22ad57f --- /dev/null +++ b/src/ReasonUsingJSUsingReason/ReasonReactCard.bs.js @@ -0,0 +1,15 @@ +'use strict'; + +var React = require("react"); + +function ReasonReactCard(Props) { + var style = Props.style; + return React.createElement("div", { + style: style + }, "This is a ReasonReact card"); +} + +var make = ReasonReactCard; + +exports.make = make; +/* react Not a pure module */ diff --git a/src/ReasonUsingJSUsingReason/ReasonReactCard.re b/src/ReasonUsingJSUsingReason/ReasonReactCard.re new file mode 100644 index 00000000..71c38005 --- /dev/null +++ b/src/ReasonUsingJSUsingReason/ReasonReactCard.re @@ -0,0 +1,10 @@ +// In this Interop example folder, we have: +// - A ReasonReact component, ReasonReactCard.re (this file) +// - Used by a ReactJS component, ReactJSCard.js +// - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re +// - ReasonUsingJSUsingReason.re is used by Index.re + +[@react.component] +let make = (~style) => { +
{React.string("This is a ReasonReact card")}
; +}; diff --git a/src/ReasonUsingJSUsingReason/ReasonUsingJSUsingReason.bs.js b/src/ReasonUsingJSUsingReason/ReasonUsingJSUsingReason.bs.js new file mode 100644 index 00000000..3e9c5c73 --- /dev/null +++ b/src/ReasonUsingJSUsingReason/ReasonUsingJSUsingReason.bs.js @@ -0,0 +1,8 @@ +'use strict'; + +var ReactJSCard = require("./ReactJSCard"); + +var make = ReactJSCard; + +exports.make = make; +/* make Not a pure module */ diff --git a/src/ReasonUsingJSUsingReason/ReasonUsingJSUsingReason.re b/src/ReasonUsingJSUsingReason/ReasonUsingJSUsingReason.re new file mode 100644 index 00000000..45a66fcf --- /dev/null +++ b/src/ReasonUsingJSUsingReason/ReasonUsingJSUsingReason.re @@ -0,0 +1,10 @@ +// In this Interop example folder, we have: +// - A ReasonReact component, ReasonReactCard.re +// - Used by a ReactJS component, ReactJSCard.js +// - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re (this file) +// - ReasonUsingJSUsingReason.re is used by Index.re + +// All you need to do to use a ReactJS component in ReasonReact, is to write the lines below! +// reasonml.github.io/reason-react/docs/en/components#import-from-js +[@react.component] [@bs.module] +external make: unit => React.element = "./ReactJSCard"; diff --git a/src/ReducerFromReactJSDocs/ReducerFromReactJSDocs.bs.js b/src/ReducerFromReactJSDocs/ReducerFromReactJSDocs.bs.js new file mode 100644 index 00000000..87b526cf --- /dev/null +++ b/src/ReducerFromReactJSDocs/ReducerFromReactJSDocs.bs.js @@ -0,0 +1,61 @@ +'use strict'; + +var Curry = require("bs-platform/lib/js/curry.js"); +var React = require("react"); + +var leftButtonStyle = { + width: "48px", + borderRadius: "4px 0px 0px 4px" +}; + +var rightButtonStyle = { + width: "48px", + borderRadius: "0px 4px 4px 0px" +}; + +var initialState = { + count: 0 +}; + +function reducer(state, action) { + if (action) { + return { + count: state.count - 1 | 0 + }; + } else { + return { + count: state.count + 1 | 0 + }; + } +} + +function ReducerFromReactJSDocs(Props) { + var match = React.useReducer(reducer, initialState); + var dispatch = match[1]; + return React.createElement("div", { + style: { + display: "flex", + alignItems: "center", + justifyContent: "space-between" + } + }, React.createElement("div", undefined, "Count: ", String(match[0].count)), React.createElement("div", undefined, React.createElement("button", { + style: leftButtonStyle, + onClick: (function (_event) { + return Curry._1(dispatch, /* Decrement */1); + }) + }, "-"), React.createElement("button", { + style: rightButtonStyle, + onClick: (function (_event) { + return Curry._1(dispatch, /* Increment */0); + }) + }, "+"))); +} + +var make = ReducerFromReactJSDocs; + +exports.leftButtonStyle = leftButtonStyle; +exports.rightButtonStyle = rightButtonStyle; +exports.initialState = initialState; +exports.reducer = reducer; +exports.make = make; +/* react Not a pure module */ diff --git a/src/ReducerFromReactJSDocs/ReducerFromReactJSDocs.re b/src/ReducerFromReactJSDocs/ReducerFromReactJSDocs.re new file mode 100644 index 00000000..2bacf52e --- /dev/null +++ b/src/ReducerFromReactJSDocs/ReducerFromReactJSDocs.re @@ -0,0 +1,44 @@ +// This is the ReactJS documentation's useReducer example, directly ported over +// https://reactjs.org/docs/hooks-reference.html#usereducer + +// A little extra we've put, because the ReactJS example has no styling +let leftButtonStyle = ReactDOMRe.Style.make(~borderRadius="4px 0px 0px 4px", ~width="48px", ()); +let rightButtonStyle = ReactDOMRe.Style.make(~borderRadius="0px 4px 4px 0px", ~width="48px", ()); + +// Record and variant need explicit declarations. +type state = {count: int}; + +type action = + | Increment + | Decrement; + +let initialState = {count: 0}; + +let reducer = (state, action) => { + switch (action) { + | Increment => {count: state.count + 1} + | Decrement => {count: state.count - 1} + }; +}; + +[@react.component] +let make = () => { + let (state, dispatch) = React.useReducer(reducer, initialState); + + // We can use a fragment here, but we don't, because we want to style the counter +
+
+ {React.string("Count: ")} + {React.string(string_of_int(state.count))} +
+
+ + +
+
; +}; diff --git a/src/Test1.bs.js b/src/Test1.bs.js new file mode 100644 index 00000000..ba331676 --- /dev/null +++ b/src/Test1.bs.js @@ -0,0 +1,63 @@ +'use strict'; + +var Block = require("bs-platform/lib/js/block.js"); + +function foo(joint) { + return [ + { + statements: [ + { + statement: /* Senate */Block.__(0, [/* DEMOCRAT_VICTORY */0]), + outcome: /* Bool */Block.__(0, [true]) + }, + { + statement: /* House */Block.__(1, [/* DEMOCRAT_VICTORY */0]), + outcome: /* Bool */Block.__(0, [true]) + } + ], + probability: 0.2 + }, + { + statements: [ + { + statement: /* Senate */Block.__(0, [/* DEMOCRAT_VICTORY */0]), + outcome: /* Bool */Block.__(0, [true]) + }, + { + statement: /* House */Block.__(1, [/* DEMOCRAT_VICTORY */0]), + outcome: /* Bool */Block.__(0, [false]) + } + ], + probability: 0.2 + }, + { + statements: [ + { + statement: /* Senate */Block.__(0, [/* DEMOCRAT_VICTORY */0]), + outcome: /* Bool */Block.__(0, [false]) + }, + { + statement: /* House */Block.__(1, [/* DEMOCRAT_VICTORY */0]), + outcome: /* Bool */Block.__(0, [true]) + } + ], + probability: 0.5 + }, + { + statements: [ + { + statement: /* Senate */Block.__(0, [/* DEMOCRAT_VICTORY */0]), + outcome: /* Bool */Block.__(0, [false]) + }, + { + statement: /* House */Block.__(1, [/* DEMOCRAT_VICTORY */0]), + outcome: /* Bool */Block.__(0, [false]) + } + ], + probability: 0.1 + } + ]; +} + +exports.foo = foo; +/* No side effect */ diff --git a/src/Test1.re b/src/Test1.re new file mode 100644 index 00000000..58091049 --- /dev/null +++ b/src/Test1.re @@ -0,0 +1,56 @@ +type senate = + | DEMOCRAT_VICTORY; + +type house = + | DEMOCRAT_VICTORY; + +type statement = + | Senate(senate) + | House(house); + +type outcome = + | Bool(bool) + | Option(string); + +type fullStatement = { + statement, + outcome, +}; + +type jointStatement = { + statements: array(fullStatement), + probability: option(float), +}; + +let foo = (joint: jointStatement) => { + [| + { + statements: [| + {statement: Senate(DEMOCRAT_VICTORY), outcome: Bool(true)}, + {statement: House(DEMOCRAT_VICTORY), outcome: Bool(true)}, + |], + probability: Some(0.2), + }, + { + statements: [| + {statement: Senate(DEMOCRAT_VICTORY), outcome: Bool(true)}, + {statement: House(DEMOCRAT_VICTORY), outcome: Bool(false)}, + |], + probability: Some(0.2), + }, + { + statements: [| + {statement: Senate(DEMOCRAT_VICTORY), outcome: Bool(false)}, + {statement: House(DEMOCRAT_VICTORY), outcome: Bool(true)}, + |], + probability: Some(0.5), + }, + { + statements: [| + {statement: Senate(DEMOCRAT_VICTORY), outcome: Bool(false)}, + {statement: House(DEMOCRAT_VICTORY), outcome: Bool(false)}, + |], + probability: Some(0.1), + }, + |]; +}; \ No newline at end of file diff --git a/src/Test2.bs.js b/src/Test2.bs.js new file mode 100644 index 00000000..5109f18d --- /dev/null +++ b/src/Test2.bs.js @@ -0,0 +1,49 @@ +'use strict'; + + +function normal(mean, std) { + var nMean = mean.toString(); + var nStd = std.toString(); + return "normal(" + (String(nMean) + (", " + (String(nStd) + ")"))); +} + +function divide(str1, str2) { + return "" + (String(str1) + ("/" + (String(str2) + ""))); +} + +var $$Math = { + normal: normal, + divide: divide +}; + +function run(company, year, param) { + var match = company.currentPrice; + switch (param) { + case /* SHARE_PRICE */0 : + if (match !== undefined && year > 2019 && year < 2030) { + var diffYears = year - 2020 | 0; + return normal(match, diffYears * 0.1); + } else { + return ; + } + case /* SHARES_OUTSTANDING */1 : + if (year > 2019 && year < 2030) { + var price = run(company, year, /* SHARE_PRICE */0); + var marketCap = run(company, year, /* MARKET_CAP */2); + if (price !== undefined && marketCap !== undefined) { + return divide(marketCap, price); + } else { + return ; + } + } else { + return ; + } + case /* MARKET_CAP */2 : + return ; + + } +} + +exports.$$Math = $$Math; +exports.run = run; +/* No side effect */ diff --git a/src/Test2.re b/src/Test2.re new file mode 100644 index 00000000..0746738d --- /dev/null +++ b/src/Test2.re @@ -0,0 +1,43 @@ +module Math = { + let normal = (mean: float, std: float) => + Js.Float.( + { + let nMean = toString(mean); + let nStd = toString(std); + {j|normal($(nMean), $(nStd))|j}; + } + ); + + let divide = (str1: string, str2: string) => {j|$(str1)/$(str2)|j}; +}; + +type param = + | SHARE_PRICE + | SHARES_OUTSTANDING + | MARKET_CAP; + +type company = { + name: string, + currentPrice: option(float), +}; + +let rec run = (company: company, year: int, param: param) => { + switch (param, year, company.currentPrice) { + | (SHARE_PRICE, year, Some(price)) when year > 2019 && year < 2030 => + let diffYears = year - 2020; + let diffPerYear = 0.1; + Some(Math.normal(price, float_of_int(diffYears) *. diffPerYear)); + + | (SHARES_OUTSTANDING, year, _) when year > 2019 && year < 2030 => + let price = run(company, year, SHARE_PRICE); + let marketCap = run(company, year, MARKET_CAP); + switch (price, marketCap) { + | (Some(price), Some(marketCap)) => + Some(Math.divide(marketCap, price)) + + | _ => None + }; + + | _ => None + }; +}; \ No newline at end of file diff --git a/watcher.js b/watcher.js new file mode 100644 index 00000000..894a19cb --- /dev/null +++ b/watcher.js @@ -0,0 +1,39 @@ +// This is our simple, robust watcher. It hooks into the BuckleScript build +// system to listen for build events. +// See package.json's `start` script and `./node_modules/.bin/bsb --help` + +// Btw, if you change this file and reload the page, your browser cache +// _might_ not pick up the new version. If you're in Chrome, do Force Reload. + +var websocketReloader; +var LAST_SUCCESS_BUILD_STAMP = localStorage.getItem('LAST_SUCCESS_BUILD_STAMP') || 0; +// package.json's `start` script's `bsb -ws _` means it'll pipe build events +// through a websocket connection to a default port of 9999. This is +// configurable, e.g. `-ws 5000` +var webSocketPort = 9999; + +function setUpWebSocket() { + if (websocketReloader == null || websocketReloader.readyState !== 1) { + try { + websocketReloader = new WebSocket(`ws://localhost:${webSocketPort}`); + websocketReloader.onmessage = (message) => { + var newData = JSON.parse(message.data).LAST_SUCCESS_BUILD_STAMP; + if (newData > LAST_SUCCESS_BUILD_STAMP) { + LAST_SUCCESS_BUILD_STAMP = newData; + localStorage.setItem('LAST_SUCCESS_BUILD_STAMP', LAST_SUCCESS_BUILD_STAMP); + // Refresh the page! This will naturally re-run everything, + // including our moduleserve which will re-resolve all the modules. + // No stable build! + location.reload(true); + } + + } + } catch (exn) { + console.error("The watcher tried to connect to web socket, but failed. Here's the message:"); + console.error(exn); + } + } +}; + +setUpWebSocket(); +setInterval(setUpWebSocket, 2000); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..391527e4 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,216 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +bs-platform@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/bs-platform/-/bs-platform-7.1.0.tgz#72b52148b1c4be7f878969e6e2afd1bfab068cdd" + integrity sha512-XUeZf1nGzmsVymG89j5L8G9YNDHl0J/5iDGExXA7a4RKxnbvP2TselBZAzFeEH4rs3gG01b7yKt+h2pm7yh7Ww== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +moduleserve@^0.9.0: + version "0.9.1" + resolved "https://registry.yarnpkg.com/moduleserve/-/moduleserve-0.9.1.tgz#11bad4337ea248d7eaf10d2c7f8649a8c3b9c1f8" + integrity sha512-WF2BeGnM2Ko7bdICgJO+Ibu+ZD33ExJHzOzTDsCUzfZnvnFfheEIYBTWyIqSRU0tXh4UTQ1krDOCglFTJPBMow== + dependencies: + send "^0.17.1" + serve-static "^1.14.1" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +prop-types@^15.6.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +react-dom@>=16.8.1, react-dom@^16.8.1: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.12.0.tgz#0da4b714b8d13c2038c9396b54a92baea633fe11" + integrity sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.18.0" + +react-is@^16.8.1: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" + integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== + +react@>=16.8.1, react@^16.8.1: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83" + integrity sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + +reason-react@>=0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/reason-react/-/reason-react-0.7.0.tgz#46a975c321e81cd51310d7b1a02418ca7667b0d6" + integrity sha512-czR/f0lY5iyLCki9gwftOFF5Zs40l7ZSFmpGK/Z6hx2jBVeFDmIiXB8bAQW/cO6IvtuEt97OmsYueiuOYG9XjQ== + dependencies: + react ">=16.8.1" + react-dom ">=16.8.1" + +scheduler@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4" + integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +send@0.17.1, send@^0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serve-static@^1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==