git.fiddlerwoaroof.com
Browse code

Add basic implementation

Ed Langley authored on 08/05/2019 00:05:39
Showing 11 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,28 @@
1
+{
2
+    "extends": [
3
+        "eslint:recommended",
4
+        "plugin:react/recommended"
5
+    ],
6
+    "parser": "babel-eslint",
7
+    "env": {
8
+        "browser": true,
9
+        "node": true,
10
+        "jest": true,
11
+        "es6": true
12
+    },
13
+    "parserOptions": {
14
+        "ecmaVersion": 2018,
15
+        "sourceType": "module"
16
+    },
17
+    "settings": {
18
+        "react": {
19
+            "version": "16.0"
20
+        }
21
+    },
22
+    "rules": {
23
+        "no-unused-vars": ["error", {
24
+            "argsIgnorePattern": "(^[_][_]*$)|(^.$)",
25
+            "varsIgnorePattern": "(^[_][_]*$)|(^R$)"
26
+        }]
27
+    }
28
+}
... ...
@@ -1 +1,3 @@
1 1
 node_modules
2
+.cache
3
+dist
2 4
new file mode 100644
... ...
@@ -0,0 +1,11 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+  <meta charset="UTF-8">
5
+  <title></title>
6
+</head>
7
+<body>
8
+  <div id="root"></div>
9
+  <script src="src/index.js"></script>
10
+</body>
11
+</html>
... ...
@@ -1326,6 +1326,16 @@
1326 1326
       "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
1327 1327
       "dev": true
1328 1328
     },
1329
+    "array-includes": {
1330
+      "version": "3.0.3",
1331
+      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
1332
+      "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=",
1333
+      "dev": true,
1334
+      "requires": {
1335
+        "define-properties": "^1.1.2",
1336
+        "es-abstract": "^1.7.0"
1337
+      }
1338
+    },
1329 1339
     "array-unique": {
1330 1340
       "version": "0.3.2",
1331 1341
       "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
... ...
@@ -1432,6 +1442,32 @@
1432 1442
       "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
1433 1443
       "dev": true
1434 1444
     },
1445
+    "babel-eslint": {
1446
+      "version": "10.0.1",
1447
+      "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.1.tgz",
1448
+      "integrity": "sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==",
1449
+      "dev": true,
1450
+      "requires": {
1451
+        "@babel/code-frame": "^7.0.0",
1452
+        "@babel/parser": "^7.0.0",
1453
+        "@babel/traverse": "^7.0.0",
1454
+        "@babel/types": "^7.0.0",
1455
+        "eslint-scope": "3.7.1",
1456
+        "eslint-visitor-keys": "^1.0.0"
1457
+      },
1458
+      "dependencies": {
1459
+        "eslint-scope": {
1460
+          "version": "3.7.1",
1461
+          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz",
1462
+          "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=",
1463
+          "dev": true,
1464
+          "requires": {
1465
+            "esrecurse": "^4.1.0",
1466
+            "estraverse": "^4.1.1"
1467
+          }
1468
+        }
1469
+      }
1470
+    },
1435 1471
     "babel-runtime": {
1436 1472
       "version": "6.26.0",
1437 1473
       "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
... ...
@@ -2960,6 +2996,32 @@
2960 2996
         }
2961 2997
       }
2962 2998
     },
2999
+    "eslint-plugin-react": {
3000
+      "version": "7.13.0",
3001
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.13.0.tgz",
3002
+      "integrity": "sha512-uA5LrHylu8lW/eAH3bEQe9YdzpPaFd9yAJTwTi/i/BKTD7j6aQMKVAdGM/ML72zD6womuSK7EiGtMKuK06lWjQ==",
3003
+      "dev": true,
3004
+      "requires": {
3005
+        "array-includes": "^3.0.3",
3006
+        "doctrine": "^2.1.0",
3007
+        "has": "^1.0.3",
3008
+        "jsx-ast-utils": "^2.1.0",
3009
+        "object.fromentries": "^2.0.0",
3010
+        "prop-types": "^15.7.2",
3011
+        "resolve": "^1.10.1"
3012
+      },
3013
+      "dependencies": {
3014
+        "doctrine": {
3015
+          "version": "2.1.0",
3016
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
3017
+          "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
3018
+          "dev": true,
3019
+          "requires": {
3020
+            "esutils": "^2.0.2"
3021
+          }
3022
+        }
3023
+      }
3024
+    },
2963 3025
     "eslint-scope": {
2964 3026
       "version": "4.0.3",
2965 3027
       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
... ...
@@ -4808,6 +4870,15 @@
4808 4870
         "verror": "1.10.0"
4809 4871
       }
4810 4872
     },
4873
+    "jsx-ast-utils": {
4874
+      "version": "2.1.0",
4875
+      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.1.0.tgz",
4876
+      "integrity": "sha512-yDGDG2DS4JcqhA6blsuYbtsT09xL8AoLuUR2Gb5exrw7UEM19sBcOTq+YBBhrNbl0PUC4R4LnFu+dHg2HKeVvA==",
4877
+      "dev": true,
4878
+      "requires": {
4879
+        "array-includes": "^3.0.3"
4880
+      }
4881
+    },
4811 4882
     "kind-of": {
4812 4883
       "version": "6.0.2",
4813 4884
       "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
... ...
@@ -5290,6 +5361,18 @@
5290 5361
         "isobject": "^3.0.0"
5291 5362
       }
5292 5363
     },
5364
+    "object.fromentries": {
5365
+      "version": "2.0.0",
5366
+      "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz",
5367
+      "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==",
5368
+      "dev": true,
5369
+      "requires": {
5370
+        "define-properties": "^1.1.2",
5371
+        "es-abstract": "^1.11.0",
5372
+        "function-bind": "^1.1.1",
5373
+        "has": "^1.0.1"
5374
+      }
5375
+    },
5293 5376
     "object.getownpropertydescriptors": {
5294 5377
       "version": "2.0.3",
5295 5378
       "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
... ...
@@ -6114,6 +6197,12 @@
6114 6197
       "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
6115 6198
       "dev": true
6116 6199
     },
6200
+    "prettier": {
6201
+      "version": "1.17.0",
6202
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.0.tgz",
6203
+      "integrity": "sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==",
6204
+      "dev": true
6205
+    },
6117 6206
     "private": {
6118 6207
       "version": "0.1.8",
6119 6208
       "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
... ...
@@ -6221,6 +6310,11 @@
6221 6310
         "through2": "^2.0.0"
6222 6311
       }
6223 6312
     },
6313
+    "ramda": {
6314
+      "version": "0.26.1",
6315
+      "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
6316
+      "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ=="
6317
+    },
6224 6318
     "randombytes": {
6225 6319
       "version": "2.1.0",
6226 6320
       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
... ...
@@ -6257,6 +6351,17 @@
6257 6351
         "scheduler": "^0.13.6"
6258 6352
       }
6259 6353
     },
6354
+    "react-dom": {
6355
+      "version": "16.8.6",
6356
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz",
6357
+      "integrity": "sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==",
6358
+      "requires": {
6359
+        "loose-envify": "^1.1.0",
6360
+        "object-assign": "^4.1.1",
6361
+        "prop-types": "^15.6.2",
6362
+        "scheduler": "^0.13.6"
6363
+      }
6364
+    },
6260 6365
     "react-is": {
6261 6366
       "version": "16.8.6",
6262 6367
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
... ...
@@ -9,13 +9,31 @@
9 9
   "author": "",
10 10
   "license": "ISC",
11 11
   "devDependencies": {
12
+    "babel-eslint": "^10.0.1",
12 13
     "eslint": "^5.16.0",
13
-    "parcel-bundler": "^1.12.3"
14
+    "eslint-plugin-react": "^7.13.0",
15
+    "parcel-bundler": "^1.12.3",
16
+    "prettier": "^1.17.0"
14 17
   },
15 18
   "dependencies": {
19
+    "prop-types": "^15.7.2",
20
+    "ramda": "^0.26.1",
16 21
     "react": "^16.8.6",
22
+    "react-dom": "^16.8.6",
17 23
     "react-redux": "^7.0.3",
18 24
     "redux": "^4.0.1",
19 25
     "redux-saga": "^1.0.2"
20
-  }
26
+  },
27
+  "rules": {
28
+    "no-unused-vars": [
29
+      "error",
30
+      {
31
+        "argsIgnorePattern": "(^[_][_]*$)|(^.$)",
32
+        "varsIgnorePattern": "(^[_][_]*$)|(^R$)"
33
+      }
34
+    ]
35
+  },
36
+  "browserslist": [
37
+    "last 2 Chrome versions"
38
+  ]
21 39
 }
22 40
new file mode 100644
... ...
@@ -0,0 +1,13 @@
1
+import React from "react";
2
+import PropTypes from "prop-types";
3
+
4
+export const IpControl = ({ ip, getIp }) =>
5
+  ip === "" ? (
6
+    <button onClick={getIp}>Get Ip</button>
7
+  ) : (
8
+    <div>Your IP is: {ip}</div>
9
+  );
10
+IpControl.propTypes = {
11
+  ip: PropTypes.string,
12
+  getIp: PropTypes.func
13
+};
0 14
new file mode 100644
... ...
@@ -0,0 +1,34 @@
1
+import React from "react";
2
+import PropTypes from "prop-types";
3
+
4
+export class NameControl extends React.Component {
5
+  constructor(props) {
6
+    super(props);
7
+    this.state = {
8
+      cur_input: ""
9
+    };
10
+  }
11
+
12
+  render() {
13
+    if (this.props.name === "") {
14
+      return (
15
+        <div>
16
+          <input
17
+            type="text"
18
+            value={this.state.cur_input}
19
+            onChange={e => this.setState({ cur_input: e.target.value })}
20
+          />
21
+          <button onClick={() => this.props.updateName(this.state.cur_input)}>
22
+            Set Name
23
+          </button>
24
+        </div>
25
+      );
26
+    } else {
27
+      return <div>Hello, {this.props.name}</div>;
28
+    }
29
+  }
30
+}
31
+NameControl.propTypes = {
32
+  name: PropTypes.string,
33
+  updateName: PropTypes.func
34
+};
... ...
@@ -0,0 +1,48 @@
1
+import React from "react";
2
+import ReactDOM from "react-dom";
3
+import { Provider, connect } from "react-redux";
4
+import { createStore, applyMiddleware, compose } from "redux";
5
+import createSagaMiddleware from "redux-saga";
6
+
7
+import { Root } from "./react";
8
+import { rootReducer, updateName } from "./redux";
9
+import { rootSaga, getIp } from "./saga";
10
+
11
+// Store Setup ==============================================================================
12
+
13
+// Make redux devtools work, if present
14
+const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
15
+
16
+// Make the redux store, with saga middleware enabled
17
+const sagaMiddleware = createSagaMiddleware();
18
+export const store = createStore(
19
+  rootReducer,
20
+  composeEnhancers(applyMiddleware(sagaMiddleware))
21
+);
22
+
23
+// Run the root saga
24
+sagaMiddleware.run(rootSaga);
25
+
26
+// Connect Redux to Toplevel Component ======================================================
27
+const ConnectedRoot = connect(
28
+  // Map the store's state to the toplevel component's props
29
+  ({ name, ip }) => ({ name, ip }),
30
+  // Map redux's dispatch function to props that will call it with a specific action
31
+  dispatch => ({
32
+    getIp() {
33
+      dispatch(getIp());
34
+    },
35
+    updateName(v) {
36
+      dispatch(updateName(v));
37
+    }
38
+  })
39
+)(Root);
40
+
41
+// Render Connected Component to the dom at #root ===========================================
42
+ReactDOM.render(
43
+  // The Toplevel component is wrapped in a provider, to make the store available to connect
44
+  <Provider store={store}>
45
+    <ConnectedRoot />
46
+  </Provider>,
47
+  document.getElementById("root")
48
+);
... ...
@@ -0,0 +1,17 @@
1
+import React from "react";
2
+import PropTypes from "prop-types";
3
+import { NameControl } from "./NameControl";
4
+import { IpControl } from "./IpControl";
5
+
6
+export const Root = ({ name, updateName, ip, getIp }) => (
7
+  <div>
8
+    <NameControl name={name} updateName={updateName} />
9
+    <IpControl ip={ip} getIp={getIp} />
10
+  </div>
11
+);
12
+Root.propTypes = {
13
+  ip: PropTypes.string,
14
+  name: PropTypes.string,
15
+  getIp: PropTypes.func,
16
+  updateName: PropTypes.func
17
+};
... ...
@@ -0,0 +1,22 @@
1
+const initialState = {
2
+  name: "",
3
+  ip: ""
4
+};
5
+
6
+export const updateName = newName => {
7
+  return { type: "UPDATE_NAME", data: newName };
8
+};
9
+
10
+export const updateIp = newIp => {
11
+  return { type: "UPDATE_IP", data: newIp };
12
+};
13
+
14
+export const rootReducer = (state = initialState, action) => {
15
+  if (action.type === "UPDATE_NAME") {
16
+    return { ...state, name: action.data };
17
+  } else if (action.type === "UPDATE_IP") {
18
+    return { ...state, ip: action.data };
19
+  } else {
20
+    return state;
21
+  }
22
+};
... ...
@@ -0,0 +1,16 @@
1
+import { takeLatest, put } from "redux-saga/effects";
2
+import { updateIp } from "./redux";
3
+
4
+export function* rootSaga() {
5
+  yield takeLatest("GET_IP", ipWorker);
6
+}
7
+
8
+export function getIp() {
9
+  return { type: "GET_IP" };
10
+}
11
+
12
+function* ipWorker() {
13
+  const ipR = yield fetch("https://api.ipify.org");
14
+  const ip = yield ipR.text();
15
+  yield put(updateIp(ip));
16
+}
0 17
\ No newline at end of file