From 35d7a9595ab4ac09c8312790390e96bf1413e80c Mon Sep 17 00:00:00 2001 From: caydenreynolds <46609378+caydenreynolds@users.noreply.github.com> Date: Wed, 2 Dec 2020 21:15:43 -0500 Subject: [PATCH] Front end (#69) * basic react app you **probably** don't want to pull this yet. I'm just using guthub to get these files from my desktop to my laptop * Created Basic Homepage for MS1 * Committing to show routing * Good enough mock up of simulation builder * Added RouteBuilder, to handle all of our routing * Added docstrings * Began work on Playerpage.js (#36) Began work on Playerpage.js. The page still needs remodeled. Co-Authored-By: Alex Louderback Co-authored-by: Alex Louderback * Added importable stylesheet file * Mockup account (#41) * Publishing mockup-login/signup branch I mostly have the page mockup done and am publishing the branch for it. * Publishing mockup-account I am publishing the branch for the mockup of account. Page is done but needs remodeling. I didn't think I would be done this fast after the other branch, so merge this one after Login/Signup branch? I don't know if there would be any git conflicts that would delete something from the other branch. Anyway, solves #39 * Plugin playerpage (#42) * Added simple schemas * Playerpage mostly-sorta works now * Removed some extra stuff * Added response to playerpage * Fixed imports * Adding schema submodule (#43) * Almost there * Forgot to remove the old stuff * Changed the color, code cleanup (#48) * Changed the color, code cleanup I changed the color, remodeled some buttons, and cleaned up some unnecessary code. Co-Authored-By: caydenreynolds <46609378+caydenreynolds@users.noreply.github.com> * Delete theme.html We don't need this. * Remodeled some more, add About Page I've added the about page mockup, and edited alot of other pages to get rid of code that seemed to not be doing anything. I did not mess with factorypage or playerpage as I didn't want to step on any toes. Co-authored-by: caydenreynolds <46609378+caydenreynolds@users.noreply.github.com> * Fixed navbar mobile problems (#46) * Simul;ation Factory Start Still need to finish splitting add event, prompt, and response * Split response, prompt, event methods Cards still aren't built * Broken implementation of dialog box with lookup table just moving between computers & using github to push changes. not functional. probably shouldve been working in my own branch anyways. * Fixing up playerpage a bit. Commented out some unused lines of code. Fixed some compiler warnings. Edited the create simulation page to have textfields for the event and response buttons. The six extra textfields for response is only temporary, we plan to add a way to dynamically add textfields. * Added the dynamic buttons. Added the dynamic buttons. * Built table component Ended up building my own spreadsheet from scratch because nothing was giving me what I wanted. Probably overengineered it, but its now more customizable than anything else out there. still need to add ability for users to add/delete rows * Made the Add player, resource button dialogs I've fixed an import and added player and resource adding dialogs, all we have to do is make it so they actually work. There is also a wierd error where the compiler complains about /factorypage already existing whenever the moar_watches thing updates the page. * Playerpage minimally functional (#54) * Committing to change branch * Playerpage works, kinda * Env variables Co-authored-by: Brandon Miller <8723104+ScratchnSniff0@users.noreply.github.com> * Commiting to change branch * Factory page creates simulations and frames * Front end lookup table (#68) * Begun work on importing excel files I've begun cleaning up a bit by removing the table, and adding a dropbox instead. The drag and drop doesn't work yet, BUT if you click on it you can select your file. The contents of the file will then be outputted to the console log. In the future we can split up the data and send it to the proper place. * Code Cleanup and Some Documentation Cleaned up some imports, added some newlines to the end of files, added some comments to the files to document functionality. * Edited Backend-Drop-Sim_schem Getting ready to connect to backend. * End-to-end functionality, with help from Brandon * About Page Did more with the about page. Co-authored-by: Brandon Miller <8723104+ScratchnSniff0@users.noreply.github.com> Co-authored-by: alouderback Co-authored-by: Brandon Miller <8723104+ScratchnSniff0@users.noreply.github.com> --- .gitmodules | 3 + increase_watches.sh | 5 + simulationfactory/.gitignore | 59 ++ simulationfactory/README.md | 68 +++ simulationfactory/package.json | 38 ++ simulationfactory/public/favicon.ico | Bin 0 -> 3150 bytes simulationfactory/public/index.html | 43 ++ simulationfactory/public/manifest.json | 25 + simulationfactory/public/robots.txt | 3 + simulationfactory/src/App.css | 38 ++ simulationfactory/src/App.js | 29 + simulationfactory/src/App.test.js | 9 + simulationfactory/src/components/Cell.js | 218 ++++++++ .../src/components/Navigation.js | 124 +++++ simulationfactory/src/components/Row.js | 20 + simulationfactory/src/components/TabPanel.js | 14 + simulationfactory/src/components/Table.js | 36 ++ simulationfactory/src/index.css | 13 + simulationfactory/src/index.js | 17 + simulationfactory/src/logo.svg | 7 + simulationfactory/src/pages/Aboutpage.js | 93 ++++ simulationfactory/src/pages/Accountpage.js | 100 ++++ simulationfactory/src/pages/Factorypage.js | 527 ++++++++++++++++++ simulationfactory/src/pages/Homepage.js | 94 ++++ .../src/pages/Loginsignuppage.js | 62 +++ simulationfactory/src/pages/Playerpage.js | 187 +++++++ simulationfactory/src/serviceWorker.js | 141 +++++ simulationfactory/src/setupTests.js | 5 + simulationfactory/src/simulation-schema | 1 + simulationfactory/src/util/AddDeleteTab.js | 0 simulationfactory/src/util/Backend.js | 112 ++++ simulationfactory/src/util/FormatString.js | 23 + simulationfactory/src/util/FrameDropzone.js | 47 ++ simulationfactory/src/util/RouteBuilder.js | 37 ++ simulationfactory/src/util/Stylesheet.js | 68 +++ simulationfactory/src/util/Validator.js | 2 + simulationfactory/src/util/palette.json | 37 ++ 37 files changed, 2305 insertions(+) create mode 100644 .gitmodules create mode 100755 increase_watches.sh create mode 100644 simulationfactory/.gitignore create mode 100644 simulationfactory/README.md create mode 100644 simulationfactory/package.json create mode 100644 simulationfactory/public/favicon.ico create mode 100644 simulationfactory/public/index.html create mode 100644 simulationfactory/public/manifest.json create mode 100644 simulationfactory/public/robots.txt create mode 100644 simulationfactory/src/App.css create mode 100644 simulationfactory/src/App.js create mode 100644 simulationfactory/src/App.test.js create mode 100644 simulationfactory/src/components/Cell.js create mode 100644 simulationfactory/src/components/Navigation.js create mode 100644 simulationfactory/src/components/Row.js create mode 100644 simulationfactory/src/components/TabPanel.js create mode 100644 simulationfactory/src/components/Table.js create mode 100644 simulationfactory/src/index.css create mode 100644 simulationfactory/src/index.js create mode 100644 simulationfactory/src/logo.svg create mode 100644 simulationfactory/src/pages/Aboutpage.js create mode 100644 simulationfactory/src/pages/Accountpage.js create mode 100644 simulationfactory/src/pages/Factorypage.js create mode 100644 simulationfactory/src/pages/Homepage.js create mode 100644 simulationfactory/src/pages/Loginsignuppage.js create mode 100644 simulationfactory/src/pages/Playerpage.js create mode 100644 simulationfactory/src/serviceWorker.js create mode 100644 simulationfactory/src/setupTests.js create mode 160000 simulationfactory/src/simulation-schema create mode 100644 simulationfactory/src/util/AddDeleteTab.js create mode 100644 simulationfactory/src/util/Backend.js create mode 100644 simulationfactory/src/util/FormatString.js create mode 100644 simulationfactory/src/util/FrameDropzone.js create mode 100644 simulationfactory/src/util/RouteBuilder.js create mode 100644 simulationfactory/src/util/Stylesheet.js create mode 100644 simulationfactory/src/util/Validator.js create mode 100644 simulationfactory/src/util/palette.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b7247be --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "simulationfactory/src/simulation-schema"] + path = simulationfactory/src/simulation-schema + url = https://github.com/CS481/simulation-schema.git diff --git a/increase_watches.sh b/increase_watches.sh new file mode 100755 index 0000000..c7bb95f --- /dev/null +++ b/increase_watches.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Increases the maximum number of file system watches +# This solves problems with automatic recompiling +sudo sysctl fs.inotify.max_user_watches=524288 +sudo sysctl -p \ No newline at end of file diff --git a/simulationfactory/.gitignore b/simulationfactory/.gitignore new file mode 100644 index 0000000..a1e5ac7 --- /dev/null +++ b/simulationfactory/.gitignore @@ -0,0 +1,59 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Created by https://www.toptal.com/developers/gitignore/api/react,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=react,visualstudiocode + +### react ### +.DS_* +*.log +logs +**/*.backup.* +**/*.back.* + +node_modules +bower_components + +*.sublime* + +psd +thumb +sketch + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history + +# End of https://www.toptal.com/developers/gitignore/api/react,visualstudiocode + +package-lock.json +**/.eslintcache \ No newline at end of file diff --git a/simulationfactory/README.md b/simulationfactory/README.md new file mode 100644 index 0000000..54ef094 --- /dev/null +++ b/simulationfactory/README.md @@ -0,0 +1,68 @@ +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+You will also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting + +### Analyzing the Bundle Size + +This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size + +### Making a Progressive Web App + +This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app + +### Advanced Configuration + +This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration + +### Deployment + +This section has moved here: https://facebook.github.io/create-react-app/docs/deployment + +### `npm run build` fails to minify + +This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify diff --git a/simulationfactory/package.json b/simulationfactory/package.json new file mode 100644 index 0000000..617c7da --- /dev/null +++ b/simulationfactory/package.json @@ -0,0 +1,38 @@ +{ + "name": "simulationfactory", + "version": "0.1.0", + "private": true, + "dependencies": { + "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.5.0", + "@testing-library/user-event": "^7.2.1", + "ajv": "6.12.5", + "contentful": "^7.14.6", + "react": "^16.13.1", + "react-dom": "^16.14.0", + "react-dropzone": "^11.2.4", + "react-router-dom": "5.2.0", + "react-scripts": "^4.0.0", + "xlsx": "^0.16.8" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/simulationfactory/public/favicon.ico b/simulationfactory/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..bcd5dfd67cd0361b78123e95c2dd96031f27f743 GIT binary patch literal 3150 zcmaKtc{Ei0AIGn;MZ^<@lHD*OV;K7~W1q3jSjJcqNywTkMOhP*k~Oj?GO|6{m(*C2 zC7JA+hN%%Bp7T4;J@?%2_x=5zbI<2~->=X60stMr0B~{wzpi9D0MG|# zyuANt7z6;uz%?PEfAnimLl^)6h5ARwGXemG2>?hqQv-I^Gpyh$JH}Ag92}3{$a#z& zd`il2Sb#$U&e&4#^4R|GTgk!Qs+x*PCL{2+`uB5mqtnqLaaw`*H2oqJ?XF(zUACc2 zSibBrdQzcidqv*TK}rpEv1ie&;Famq2IK5%4c}1Jt2b1x_{y1C!?EU)@`_F)yN*NK z)(u03@%g%uDawwXGAMm%EnP9FgoucUedioDwL~{6RVO@A-Q$+pwVRR%WYR>{K3E&Q zzqzT!EEZ$_NHGYM6&PK#CGUV$pTWsiI5#~m>htoJ!vbc0=gm3H8sz8KzIiVN5xdCT z%;}`UH2Pc8))1VS-unh?v4*H*NIy5On{MRKw7BTmOO9oE2UApwkCl9Z?^dod9M^#w z51tEZhf+#dpTo#GDDy#kuzoIjMjZ?%v*h$ z*vwUMOjGc?R0(FjLWkMD)kca4z6~H45FIzQ!Zzu&-yWyMdCBsDr2`l}Q{8fH$H@O< z$&snNzbqLk?(GIe?!PVh?F~2qk4z^rMcp$P^hw^rUPjyCyoNTRw%;hNOwrCoN?G0E z!wT^=4Loa9@O{t;Wk(Nj=?ms1Z?UN_;21m%sUm?uib=pg&x|u)8pP#l--$;B9l47n zUUnMV0sXLe*@Gvy>XWjRoqc2tOzgYn%?g@Lb8C&WsxV1Kjssh^ZBs*Ysr+E6%tsC_ zCo-)hkYY=Bn?wMB4sqm?WS>{kh<6*DO)vXnQpQ9`-_qF6!#b;3Nf@;#B>e2j$yokl6F|9p1<($2 z=WSr%)Z?^|r6njhgbuMrIN>8JE05u0x5t@_dEfbGn9r0hK4c2vp>(*$GXsjeLL_uz zWpyfUgdv!~-2N;llVzik#s2*XB*%7u8(^sJv&T3pzaR&<9({17Zs~UY>#ugZZkHBs zD+>0_an$?}utGp$dcXtyFHnTQZJ}SF=oZ}X07dz~K>^o(vjTzw8ZQc!Fw1W=&Z?9% zv63|~l}70sJbY?H8ON8j)w5=6OpXuaZ}YT03`2%u8{;B0Vafo_iY7&BiQTbRkdJBYL}?%ATfmc zLG$uXt$@3j#OIjALdT&Ut$=9F8cgV{w_f5eS)PjoVi z&oemp-SKJ~UuGuCP1|iY?J^S&P z)-IG?O-*=z6kfZrX5H*G=aQ{ZaqnOqP@&+_;nq@mA>EcjgxrYX8EK|Iq4&E&rxR?R z8N$QOdRwY zr{P`O)=87>YLHtFfGXW z6P)ucrhj~It_9w<^v5>T6N1U}+BkS))=WX*2JY=}^b2czGhH<`?`(}}qMcpPx_%>M zM|fs(+I1m&_h(zqp-HgP>re$2O^o$q)xu#fl0ivOJE({duU)a*OD(eYgSi^cdTn}pqcPM(;S)2%1By^Wh%-CaC%>d9hi`7J zaxL7@;nhA>PE%s99&;z{8>VFgf{u!(-B-x7Of6ueme+ScryL`h(^qKE)DtieWY>-7 zgB)VJESQS4*1LU(2&@pgLvSt{(((C?K_V(rQk``i&5}ZPG;G^FiPlZ$7|-vEmMWlU z5lQ%iK2nu=h2wd_7>gK@vX=*AG+u~rQP$NwPC`ZA?4nh{3tui1x@bT6-;Rk3yDQ>d z?3qRD#+PeV7#FAa>s`Xwxsx_oRFcN$StW2=CW`=qObsT?SD^#^jM1Yk}PSPxJ zG@-_mnNU_)vM|iLRSI>UMp|hatyS}17R{10IuL0TLlupt>9dRs_SPQbv7BLYyC#qv16E-y@XZ= z-!p7I%#r-BVi$nQq3&ssRc_IC%R6$tA&^s_l46880~Wst3@>(|EO<}T4~ci~#!=e; zD)B>o%1+$ksURD1p7I-<3ehlFyVkqrySf&gg>Bp0Z9?JaG|gyTZ{Cb8SdvAWVmFX7v2ohs!OCc!Udk zUITUpmZ33rKLI#(&lDj}cKA#dpL4Fil=$5pu_wi1XJR!llw` zSItPBDEdMHk2>c7#%lBxZHHvtVUOZ$}v?=?AT~9!Jcqa@IJGuMg(s^7r>pcTrd)pS`{5Cu8WPey` z9)!!OUUY@L%9Q+bZa*S5`3f_|lFCPN6kdp_M2>{le8;cn^XUsPa+TUk47qd6)IBR% zk*&Ip?!Ge_gmmdj)BX}P_5o@VI2*wbZ^>UhFju}0gQZh!pP%4XT9{@w;G#b3XK8sN zF(7i$Jv(IM$8Akys9dhP^^~H2(7BfJp}yDW1#@!CL-!mGcSCnJ599WK9MV@yo_u$v MDeX2GIKR{Qf5okjU;qFB literal 0 HcmV?d00001 diff --git a/simulationfactory/public/index.html b/simulationfactory/public/index.html new file mode 100644 index 0000000..aa069f2 --- /dev/null +++ b/simulationfactory/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/simulationfactory/public/manifest.json b/simulationfactory/public/manifest.json new file mode 100644 index 0000000..080d6c7 --- /dev/null +++ b/simulationfactory/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/simulationfactory/public/robots.txt b/simulationfactory/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/simulationfactory/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/simulationfactory/src/App.css b/simulationfactory/src/App.css new file mode 100644 index 0000000..74b5e05 --- /dev/null +++ b/simulationfactory/src/App.css @@ -0,0 +1,38 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/simulationfactory/src/App.js b/simulationfactory/src/App.js new file mode 100644 index 0000000..fb2382d --- /dev/null +++ b/simulationfactory/src/App.js @@ -0,0 +1,29 @@ +import React, { Component } from 'react'; +import { BrowserRouter } from "react-router-dom"; + +import {BuildRoutes} from './util/RouteBuilder'; + +/* This is the only real weirdness with my solution. You MUST import all of the pages here, or the routes will not be built + Fortunately, importing for side effects isn't entirely unheard of *cough* jsx *cough* +*/ +import HomePage from './pages/Homepage'; +import FactoryPage from './pages/Factorypage'; +import PlayerPage from './pages/Playerpage'; +import LoginSignUpPage from './pages/Loginsignuppage'; +import AccountPage from './pages/Accountpage'; +import AboutPage from './pages/Aboutpage' +import { ThemeProvider } from '@material-ui/core'; +import { Theme } from "./util/Stylesheet" + +class App extends Component { + render() { + return ( + + + + + + ); + } +} +export default App \ No newline at end of file diff --git a/simulationfactory/src/App.test.js b/simulationfactory/src/App.test.js new file mode 100644 index 0000000..4db7ebc --- /dev/null +++ b/simulationfactory/src/App.test.js @@ -0,0 +1,9 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import App from './App'; + +test('renders learn react link', () => { + const { getByText } = render(); + const linkElement = getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/simulationfactory/src/components/Cell.js b/simulationfactory/src/components/Cell.js new file mode 100644 index 0000000..e1c2f56 --- /dev/null +++ b/simulationfactory/src/components/Cell.js @@ -0,0 +1,218 @@ +import React from 'react' +/** + * Cell represents the atomic element of a table + */ +export default class Cell extends React.Component { + constructor(props) { + super(props) + this.state = { + editing: false, + value: props.value, + } + this.display = this.determineDisplay( + { x: props.x, y: props.y }, + props.value + ) + this.timer = 0 + this.delay = 200 + this.prevent = false + } + /** + * Add listener to the `unselectAll` event used to broadcast the + * unselect all event + */ + componentDidMount() { + window.document.addEventListener('unselectAll', + this.handleUnselectAll) + } + /** + * Before updating, execute the formula on the Cell value to + * calculate the `display` value. Especially useful when a + * redraw is pushed upon this cell when editing another cell + * that this might depend upon + */ + componentWillUpdate() { + this.display = this.determineDisplay( + { x: this.props.x, y: this.props.y }, this.state.value) + } + /** + * Remove the `unselectAll` event listener added in + * `componentDidMount()` + */ + componentWillUnmount() { + window.document.removeEventListener('unselectAll', + this.handleUnselectAll) + } + /** + * When a Cell value changes, re-determine the display value + * by calling the formula calculation + */ + onChange = (e) => { + this.setState({ value: e.target.value }) + this.display = this.determineDisplay( + { x: this.props.x, y: this.props.y }, e.target.value) + } + /** + * Handle pressing a key when the Cell is an input element + */ + onKeyPressOnInput = (e) => { + if (e.key === 'Enter') { + this.hasNewValue(e.target.value) + } + } + /** + * Handle pressing a key when the Cell is a span element, + * not yet in editing mode + */ + onKeyPressOnSpan = () => { + if (!this.state.editing) { + this.setState({ editing: true }) + } + } + /** + * Handle moving away from a cell, stores the new value + */ + onBlur = (e) => { + this.hasNewValue(e.target.value) + } + /** + * Used by `componentDid(Un)Mount`, handles the `unselectAll` + * event response + */ + handleUnselectAll = () => { + if (this.state.selected || this.state.editing) { + this.setState({ selected: false, editing: false }) + } + } + /** + * Called by the `onBlur` or `onKeyPressOnInput` event handlers, + * it escalates the value changed event, and restore the editing + * state to `false`. + */ + hasNewValue = (value) => { + this.props.onChangedValue( + { + x: this.props.x, + y: this.props.y, + }, + value, + ) + this.setState({ editing: false }) + } + /** + * Emits the `unselectAll` event, used to tell all the other + * cells to unselect + */ + emitUnselectAllEvent = () => { + const unselectAllEvent = new Event('unselectAll') + window.document.dispatchEvent(unselectAllEvent) + } + /** + * Handle clicking a Cell. + */ + clicked = () => { + // Prevent click and double click to conflict + this.timer = setTimeout(() => { + if (!this.prevent) { + // Unselect all the other cells and set the current + // Cell state to `selected` + this.emitUnselectAllEvent() + this.setState({ selected: true }) + } + this.prevent = false + }, this.delay) + } + /** + * Handle doubleclicking a Cell. + */ + doubleClicked = () => { + // Prevent click and double click to conflict + clearTimeout(this.timer) + this.prevent = true + // Unselect all the other cells and set the current + // Cell state to `selected` & `editing` + this.emitUnselectAllEvent() + this.setState({ editing: true, selected: true }) + } + determineDisplay = ({ x, y }, value) => { + return value + } + /** + * Calculates a cell's CSS values + */ + calculateCss = () => { + const css = { + width: '80px', + padding: '4px', + margin: '0', + height: '25px', + boxSizing: 'border-box', + position: 'relative', + display: 'inline-block', + color: 'black', + border: '1px solid #cacaca', + textAlign: 'left', + verticalAlign: 'top', + fontSize: '14px', + lineHeight: '15px', + overflow: 'hidden', + fontFamily: 'Calibri, \'Segoe UI\', Thonburi, Arial, Verdana, sans-serif', + } + if (this.props.x === 0 || this.props.y === 0) { + css.textAlign = 'center' + css.backgroundColor = '#f0f0f0' + css.fontWeight = 'bold' + } + return css + } + render() { + const css = this.calculateCss() + // column 0 + if (this.props.x === 0) { + return ( + + {this.props.y} + + ) + } + // row 0 + if (this.props.y === 0) { + const alpha = ' abcdefghijklmnopqrstuvwxyz'.split('') + return ( + + {alpha[this.props.x]} + + ) + } + if (this.state.selected) { + css.outlineColor = 'lightblue' + css.outlineStyle = 'dotted' + } + if (this.state.editing) { + return ( + + ) + } + return ( + this.clicked(e)} + onDoubleClick={e => this.doubleClicked(e)} + style={css} + role="presentation" + > + {this.display} + + ) + } +} \ No newline at end of file diff --git a/simulationfactory/src/components/Navigation.js b/simulationfactory/src/components/Navigation.js new file mode 100644 index 0000000..79861f9 --- /dev/null +++ b/simulationfactory/src/components/Navigation.js @@ -0,0 +1,124 @@ +import React from "react"; +import MenuIcon from "@material-ui/icons/Menu"; +import { Link } from "react-router-dom"; + +import { + List, + ListItem, + ListItemText, + AppBar, + IconButton, + Toolbar, + Typography, + Divider, + Drawer, + Hidden, + SwipeableDrawer, + Button + } from "@material-ui/core"; + +class Navigation extends React.Component { + constructor(props) { + super(props); + this.state = {open: false}; + this.Styles = props.Styles; + } + + render() { + return ( + + {this.renderDrawer()} + {this.renderTopbar()} + + ) + } + + renderDrawer() { + return ( + + ); + } + + renderDrawerContents() { + return ( + +
+ + + + Home + + + Create Simulation + + + Join Simulation + + + Login/SignUp + + + Your Account + + + + + + About + + + + ) + } + + renderTopbar() { + return ( + + + this.setState({open: true})} + className={this.Styles.menuButton} + > + + + {this.props.TopbarMessage} + {this.props.children} + + + ); + } +} + +export default Navigation; diff --git a/simulationfactory/src/components/Row.js b/simulationfactory/src/components/Row.js new file mode 100644 index 0000000..909f2ce --- /dev/null +++ b/simulationfactory/src/components/Row.js @@ -0,0 +1,20 @@ +import React from 'react' +import Cell from './Cell' +const Row = props => { + const cells = [] + const y = props.y + for (let x = 0; x < props.x; x += 1) { + cells.push( + + ) + } + return
{cells}
+} +export default Row \ No newline at end of file diff --git a/simulationfactory/src/components/TabPanel.js b/simulationfactory/src/components/TabPanel.js new file mode 100644 index 0000000..08096a1 --- /dev/null +++ b/simulationfactory/src/components/TabPanel.js @@ -0,0 +1,14 @@ +import React from "react"; +import { Box } from "@material-ui/core"; + +function TabPanel(props) { + const {value, index, children, other} = props; + + return ( +
+ {value === index && {children}} +
+ ); +} + +export default TabPanel; \ No newline at end of file diff --git a/simulationfactory/src/components/Table.js b/simulationfactory/src/components/Table.js new file mode 100644 index 0000000..a50956e --- /dev/null +++ b/simulationfactory/src/components/Table.js @@ -0,0 +1,36 @@ +import React from 'react' +import Row from './Row' +export default class Table extends React.Component { + constructor(props) { + super(props) + this.state = { + data: {} + } + } + handleChangedCell = ({ x, y }, value) => { + const modifiedData = Object.assign({}, this.state.data) + if (!modifiedData[y]) modifiedData[y] = {} + modifiedData[y][x] = value + this.setState({ data: modifiedData }) + } + updateCells = () => { + this.forceUpdate() + } + render() { + const rows = [] + for (let y = 0; y < this.props.y + 1; y += 1) { + const rowData = this.state.data[y] || {} + rows.push( + + ) + } + return
{rows}
+ } +} \ No newline at end of file diff --git a/simulationfactory/src/index.css b/simulationfactory/src/index.css new file mode 100644 index 0000000..ec2585e --- /dev/null +++ b/simulationfactory/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/simulationfactory/src/index.js b/simulationfactory/src/index.js new file mode 100644 index 0000000..f5185c1 --- /dev/null +++ b/simulationfactory/src/index.js @@ -0,0 +1,17 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; +import * as serviceWorker from './serviceWorker'; + +ReactDOM.render( + + + , + document.getElementById('root') +); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: https://bit.ly/CRA-PWA +serviceWorker.unregister(); diff --git a/simulationfactory/src/logo.svg b/simulationfactory/src/logo.svg new file mode 100644 index 0000000..6b60c10 --- /dev/null +++ b/simulationfactory/src/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/simulationfactory/src/pages/Aboutpage.js b/simulationfactory/src/pages/Aboutpage.js new file mode 100644 index 0000000..8091fe3 --- /dev/null +++ b/simulationfactory/src/pages/Aboutpage.js @@ -0,0 +1,93 @@ +import React from "react"; +import { + Grid, + Card, + CardContent, + List, + ListItem, + ListItemText, + Typography +} from "@material-ui/core"; + +import Navigation from "../components/Navigation"; +import CreateStyles from "../util/Stylesheet"; +import { RegisterRoutes } from "../util/RouteBuilder"; + +function Aboutpage() { + + const Styles = CreateStyles(); + + return ( +
+ +
+
+ + + + + + Back-End + + + + + Github + + + + Github + + + + Github + + + + + + + + + + Front-End + + + + + Github + Linkedin + + + + Github + + + + Github + Linkedin + + + + + + + + + + About + + + Made at York College of Pennsylvania by very hard working students. + + + + + +
+
+ ); +} + +RegisterRoutes(Aboutpage, "/about", "/aboot", "/About"); +export default Aboutpage; diff --git a/simulationfactory/src/pages/Accountpage.js b/simulationfactory/src/pages/Accountpage.js new file mode 100644 index 0000000..efb02c6 --- /dev/null +++ b/simulationfactory/src/pages/Accountpage.js @@ -0,0 +1,100 @@ +import React from 'react'; +import CreateStyles from "../util/Stylesheet"; + +import { + List, + ListItem, + ListItemText, + Typography, + TextField, + Button, + CardContent, + Grid, + Card, +} from "@material-ui/core"; + +import Navigation from "../components/Navigation"; + +import { RegisterRoutes } from "../util/RouteBuilder"; + + +function Accountpage() { + + const Styles = CreateStyles(); + + return ( +
+ +
+
+ + + + + + Account Info + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Change Password + + + + To change the password you must + + + type in the original password. + + + + + + + + + + + + + + + + + +
+
+ ); +} + +RegisterRoutes(Accountpage, "/account", "/Accountpage", "/AccountPage", "/accountpage"); +export default Accountpage; diff --git a/simulationfactory/src/pages/Factorypage.js b/simulationfactory/src/pages/Factorypage.js new file mode 100644 index 0000000..a9d6a27 --- /dev/null +++ b/simulationfactory/src/pages/Factorypage.js @@ -0,0 +1,527 @@ +import React from "react"; +import FrameModification from "../simulation-schema/js/FrameModification"; + +import { + Grid, + Card, + CardActions, + Button, + CardContent, + Menu, + MenuItem, + Tabs, + Tab, + Table, + TextField, + Tooltip, + Typography, + Dialog, + DialogContent, + DialogTitle, + DialogActions, + DialogContentText, +} from "@material-ui/core"; + +import CreateStyles from "../util/Stylesheet"; +import Navigation from "../components/Navigation"; +import TabPanel from "../components/TabPanel"; +import { RegisterRoutes } from "../util/RouteBuilder"; +import { + InitializeSimulation, + InitializeFrame, + ModifyFrame, + ModifySimulation +} from "../util/Backend"; +import Close from "@material-ui/icons/Close"; +import FrameDropzone from "../util/FrameDropzone"; + +function Factorypage(props) { + + const [inputList, setInputList] = React.useState([]); + function onAddResponseClick (event) { + let frame = findActiveFrame(); + frame.responses.push(""); + setInputList(inputList.concat( { + frame.responses[inputList.length] = t.target.value; + try { + commitFrame(frame); + setError(""); + } catch (e) { + console.log(e); + setError("One of the responses is invalid. Do you have duplicates?"); + } + }} />)); + }; + + const Styles = CreateStyles(); + const [selectedFrameKey, setSelectedFrameKey] = React.useState(0); + const [anchorEl, setAnchorEl] = React.useState(null); + + const [error, setError] = React.useState(""); + + const [frameList, setframeList] = React.useState([]); + const [simulation, setSimulation] = React.useState({ + resources: {}, + response_timeout: 500000 + }); + + const [resourceName, setResourceName] = React.useState(""); + const [resourceValue, setResourceValue] = React.useState(""); + + const [tabValue, setTabValue] = React.useState(0); + const [open, setOpen] = React.useState(false); + const [openPlayerAdd, setOpenPlayerAdd] = React.useState(false); + /** + * Handles resource dialog box + */ + const [openResourceAdd, setOpenResourceAdd] = React.useState(false); + + // Information for backend + const [simulationId, setSimulationId] = React.useState(''); + const [user, setUser] = React.useState({username: '', password: ''}); + + function handleMenuClick(event) { + setAnchorEl(event.currentTarget); + }; + function handleMenuClose() { + setAnchorEl(null); + }; + function handleSheetOpen(event) { + setOpen(true); + }; + function handleSheetClose(event) { + setOpen(false); + }; + function handlePlayerAddOpen(event) { + setOpenPlayerAdd(true); + setAnchorEl(null); + }; + function handlePlayerAddClose(event) { + setOpenPlayerAdd(false); + }; + function handleResourceAddOpen(event) { + setOpenResourceAdd(true); + setAnchorEl(null); + }; + + // Returns a new frame with all of the values set to the default, and the given id and type + function getDefaultFrame(id, type) { + return { + key: id, + id: id, + type: type, + default_action: "", + responses: [], + rounds: [], + effects: [], + firstRound: "", + lastRound: "" + } + } + + function addPrompt() { + InitializeFrame({user: user, id: simulationId}, (r) => { + setframeList([...frameList, getDefaultFrame(r.id, 0)]); + }) + }; + + function addResponse() { + InitializeFrame({user: user, id: simulationId}, (r) => { + setframeList([...frameList, getDefaultFrame(r.id, 1)]); + }) + }; + + function addEvent() { + InitializeFrame({user: user, id: simulationId}, (r) => { + setframeList([...frameList, getDefaultFrame(r.id, 2)]); + }) + }; + + // Returns the frame that the user currently has selected + function findActiveFrame() { + for(let i = 0; i < frameList.length && frameList.length > 0; i++) { + if (frameList[i].key == selectedFrameKey) { + return frameList[i]; + } + } + } + + // Stores the given frame into the backend + function commitFrame(frame) { + // Validate by FrameModification, as a poor excuse for proper error checking + ModifyFrame(FrameModification.Validate({ + user: user, + frame_id: frame.id, + default_action: frame.default_action, + responses: frame.responses, + rounds: frame.rounds, + effects: frame.effects, + prompt: frame.prompt + }), () => {}); + } + + function commitSimulation() { + ModifySimulation({user: user, + simulation_id: simulationId, + resources: simulation.resources, + response_timeout: simulation.response_timeout + }, () => {}); + } + + function addResource() { + if (resourceName != "" && resourceValue != "") { + simulation.resources[resourceName] = resourceValue; + } + commitSimulation(); + } + + // Adds either the first or last round numbers to the frame + function addRounds(number, isFirst) { + let frame = findActiveFrame(); + if (isFirst) { + frame.firstRound = number; + } else { + frame.lastRound = number; + } + + frame.rounds = []; + if (frame.firstRound != "" && frame.lastRound != "") { + let firstRound = parseInt(frame.firstRound); + let lastRound = parseInt(frame.lastRound); + while (firstRound <= lastRound) { + frame.rounds.push(firstRound); + firstRound++; + } + } + commitFrame(frame); + } + + // TODO: THIS IS REALLY BROKEN + function deleteTab(e) { + e.stopPropagation(); + + if (frameList.length === 1) { + return; + } + let tabId = parseInt(e.target.id); + let tabIDIndex = 0; + + let tabs = frameList.filter((value, index) => { + if (value.id === tabId) { + tabIDIndex = index; + } + return value.id !== tabId; + }); + + let curValue = parseInt(tabValue); + if (curValue === tabId) { + if (tabIDIndex === 0) { + curValue = frameList[tabIDIndex + 1].id; + } else { + curValue = frameList[tabIDIndex - 1].id; + } + } + setFrameValue(curValue); + setFrameList(tabs); + }; + + function renderFrameCard() { + return ( + + + + Enter User Prompt: + + + +
+ { + let frame = findActiveFrame(); + frame.prompt = t.target.value; + commitFrame(frame); + }}/> + + +
+ +
+
+ {inputList} +
+
+ + addRounds(t.target.value, true)} + /> + addRounds(t.target.value, false)} + /> + + + + {error} + + +
+
+ ) + } + + //TODO: Different types of frame cards + function renderCard(tab) { + switch (tab.type) { + default: + return renderFrameCard(); + case 1: + return renderFrameCard(); + case 2: + return renderFrameCard(); + } + }; + + // Temporary code until login process is completed + function renderLogin() { + return ( +
+
{/* Why is this necessary */} + + + setUser({username: t.target.value, password: user.password})}/> + setUser({password: t.target.value, username: user.username})}/> + + + + + {renderSubmitButton()} + + +
+ ) + }; + + function renderSubmitButton() { + return ( + + ) + }; + + function renderFrames() { + return ( +
+ setSelectedFrameKey(newValue)} + className={Styles.tabs} + > + {frameList.map((tab) => ( + } + className="mytab" + /> + ))} + + {frameList.map((tab) => ( + + {renderCard(tab)} + + ))} +
+ ) + } + + function renderAddResource() { + return ( + setOpenResourceAdd(false)} aria-labelledby="form-dialog-title"> + Add a Resource + + + Enter the name of the resource + + {setResourceName(t.target.value)}} + /> + + Enter the starting amount of the resource: + + {setResourceValue(t.target.value)}} + /> + + + + + + + ) + } + + function renderFactoryPage() { + return ( +
+ + + + + Import Lookup Table + + + Lookup Table Entry + + +
+ +
+
+
+ Add Player + + Add a Player + + + Enter the name of the player. + + + + + + + + + Add Resource + {renderAddResource()} +
+
+
+
{/* Why is this necessary */} + + + + + + + + + + + + + +
Your simulation id is {simulationId}
+
+
+
+ + {renderFrames()} + +
+
+
+ ); + }; + + if (simulationId == '') { + return renderLogin(); + } else { + return renderFactoryPage(); + } +} + +RegisterRoutes( + Factorypage, + "/factory", + "/Factory", + "/factoryPage", + "/FactoryPage" +); + +export default Factorypage; diff --git a/simulationfactory/src/pages/Homepage.js b/simulationfactory/src/pages/Homepage.js new file mode 100644 index 0000000..05ca591 --- /dev/null +++ b/simulationfactory/src/pages/Homepage.js @@ -0,0 +1,94 @@ +import React from "react"; + +import { + Grid, + Card, + CardActions, + CardContent, + Button, + List, + ListItem, + ListItemText, + Typography, +} from "@material-ui/core"; + +import styles from "../util/Stylesheet"; +import Navigation from "../components/Navigation"; +import { Link } from "react-router-dom"; +import { RegisterRoutes } from "../util/RouteBuilder"; + +function Homepage(props) { + const Styles = styles(); + + return ( +
+ +
+
+ + + + + + Simulation Factory + + + + + + + + + + + + + + + + + + + + + + + + + + + Join Simulation + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ); +} + +RegisterRoutes(Homepage, "/", "/home", "/Home", "/Homepage", "/homepage", "/HomePage", "/homePage"); +export default Homepage; diff --git a/simulationfactory/src/pages/Loginsignuppage.js b/simulationfactory/src/pages/Loginsignuppage.js new file mode 100644 index 0000000..eebee71 --- /dev/null +++ b/simulationfactory/src/pages/Loginsignuppage.js @@ -0,0 +1,62 @@ +import React from 'react'; + +import { + Grid, + Card, + CardContent, + List, + ListItem, + Typography, + TextField, + Button +} from "@material-ui/core"; + +import Navigation from "../components/Navigation"; +import { RegisterRoutes } from "../util/RouteBuilder"; +import CreateStyles from "../util/Stylesheet"; + +function Loginsignuppage() { + + const Styles = CreateStyles(); + + return ( +
+ +
+
+ + + + + + Login or Signup + + + + + + + + + + + + + + + + + + + +
+
+ ); +} + +RegisterRoutes(Loginsignuppage, "/loginsignup", "/Loginsignuppage", "LoginSignUpPage", "/loginsignupPage"); +export default Loginsignuppage; diff --git a/simulationfactory/src/pages/Playerpage.js b/simulationfactory/src/pages/Playerpage.js new file mode 100644 index 0000000..a24022e --- /dev/null +++ b/simulationfactory/src/pages/Playerpage.js @@ -0,0 +1,187 @@ +import React from 'react'; + +import { + FormControl, + FormControlLabel, + Radio, + RadioGroup, + Card, + CardContent, + Typography, + Button, + TextField, +} from "@material-ui/core"; + +import Navigation from "../components/Navigation"; +import { RegisterRoutes } from "../util/RouteBuilder"; +import styles from "../util/Stylesheet"; + +import { + BeginSim, + GetState, + SubmitResponse +} from "../util/Backend"; + +import FormatString from "../util/FormatString"; + +class SimulationPlayer extends React.Component { + constructor(props) { + super(props); + this.state = { + radioValue: false, + logged_in: false, + simState: {user_waiting: true} + }; + } + render() { + if (this.state.logged_in) { + return this.renderPlayer(); + } else { + return this.renderLogin(); + } + } + + getUser() { + return {username: this.state.username, password: this.state.password}; + } + + getSimulationInstance() { + return {user: this.getUser(), id: this.state.simulation_id}; + } + + beginSim() { + BeginSim(this.getSimulationInstance(), () => { + this.setState({logged_in: true}); + this.setSimState(); + }); + } + + setSimState() { + GetState(this.getSimulationInstance(), (newState) => this.setState({simState: newState})); + } + + // Temporary code for 50% completion + renderLogin() { + let Styles = this.props.Styles; + return ( +
+
{/* Why is this necessary */} + + + this.setState({username: t.target.value})}/> + this.setState({password: t.target.value})}/> + this.setState({simulation_id: t.target.value})}/> + + + + + {this.renderSubmitButton()} + + +
+ ) + } + + renderPlayer() { + let Styles = this.props.Styles; + return ( +
+
{/* Why is this necessary */} + + + + {this.renderPrompt()} + + + + + + {this.renderResponses(Styles)} + + + + + {this.renderSubmitButton()} + + +
+ ) + } + + renderResponses() { + return ( + + {this.setState({radioValue: event.target.value})}}> + {this.renderResponseButtons()} + + + ) + } + + renderResponseButtons() { + if (!this.state.simState.user_waiting) { + let responses = this.state.simState.active_frame.responses; + return responses.map((response) => { + return } label={response} checked={this.state.radioValue===response} /> + }); + } + } + + renderSubmitButton() { + if (!this.state.logged_in) + { + return ( + + ) + } else if (!this.state.simState.user_waiting) { + return ( + + ) + } else { + return ( + + ) + } + } + + renderPrompt() { + if (this.state.simState.user_waiting) { + return "Waiting..." + } else { + return FormatString(this.state.simState.active_frame.prompt, this.state.simState) + } + } + + submitResponse() { + // Do nothing if the user has not chosen a response + if (!this.state.radioValue) { + return + } + SubmitResponse({user: this.getUser(), response: this.state.radioValue, simulation_id: this.state.simulation_id}, + () => this.setState({radioValue: false, simState: {user_waiting: true}}) + ); + } +} + +function Playerpage() { + const Styles = styles(); + return ( +
+ + {/* Not sure why, but we can't rebuild our classes inside this Class component */} +
+ ); +} + +RegisterRoutes(Playerpage, "/player", "/playerpage", "/Player", "/Playerpage", "/playerPage"); +export default Playerpage; \ No newline at end of file diff --git a/simulationfactory/src/serviceWorker.js b/simulationfactory/src/serviceWorker.js new file mode 100644 index 0000000..b04b771 --- /dev/null +++ b/simulationfactory/src/serviceWorker.js @@ -0,0 +1,141 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on subsequent visits to a page, after all the +// existing tabs open on the page have been closed, since previously cached +// resources are updated in the background. + +// To learn more about the benefits of this model and instructions on how to +// opt-in, read https://bit.ly/CRA-PWA + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.0/8 are considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export function register(config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://bit.ly/CRA-PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl, config) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl, config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl, { + headers: { 'Service-Worker': 'script' }, + }) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready + .then(registration => { + registration.unregister(); + }) + .catch(error => { + console.error(error.message); + }); + } +} diff --git a/simulationfactory/src/setupTests.js b/simulationfactory/src/setupTests.js new file mode 100644 index 0000000..74b1a27 --- /dev/null +++ b/simulationfactory/src/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom/extend-expect'; diff --git a/simulationfactory/src/simulation-schema b/simulationfactory/src/simulation-schema new file mode 160000 index 0000000..c3335c9 --- /dev/null +++ b/simulationfactory/src/simulation-schema @@ -0,0 +1 @@ +Subproject commit c3335c9418392b1be0375508d0c524d7cd61e0c0 diff --git a/simulationfactory/src/util/AddDeleteTab.js b/simulationfactory/src/util/AddDeleteTab.js new file mode 100644 index 0000000..e69de29 diff --git a/simulationfactory/src/util/Backend.js b/simulationfactory/src/util/Backend.js new file mode 100644 index 0000000..a592375 --- /dev/null +++ b/simulationfactory/src/util/Backend.js @@ -0,0 +1,112 @@ +import IdRequest from "../simulation-schema/js/IdRequest"; +import IdResponse from "../simulation-schema/js/IdResponse"; +import State from "../simulation-schema/js/State"; +import User from "../simulation-schema/js/User"; +import UserResponse from "../simulation-schema/js/UserResponse"; +import FrameModification from "../simulation-schema/js/FrameModification"; +import Effect from "../simulation-schema/js/Effect"; +import SimulationModification from "../simulation-schema/js/SimulationModification"; + +let server_url = process.env.REACT_APP_SIMULATION_FACTORY_URL; + +// Executes the BeginSim procedure on the backend +// Args: +// request (object): The request to POST to the backend +// callback (object): The callback to execute once the backend responds. +// This callback accepts no arguments. +export async function BeginSim(request, callback) { + Post(request, callback, 'BeginSim', IdRequest); +} + +// Executes the GetSimState procedure on the backend +// Args: +// request (object): The request to POST to the backend +// callback (object): The callback to execute once the backend responds. +// This callback accepts one State argument. +export async function GetState(request, callback) { + Post(request, callback, 'GetSimState', IdRequest, State); +} + +// Executes the SubmitResponse procedure on the backend +// Args: +// request (object): The request to POST to the backend +// callback (object): The callback to execute once the backend responds. +// This callback accepts no arguments. +export async function SubmitResponse(request, callback) { + Post(request, callback, 'SubmitResponse', UserResponse); +} + +// Executes the CheckCredentials procedure on the backend +// Args: +// request (object): The request to POST to the backend +// callback (object): The callback to execute once the backend responds. +// This callback accepts no arguments. +export async function CheckCredentials(request, callback) { + Post(request, callback, 'CheckCredentials', User); +} + +// Executes the SimulationInitialization procedure on the backend +// Args: +// request (object): The request to POST to the backend +// callback (object): The callback to execute once the backend responds. +// This callback accepts one IdResponse argument. +export async function InitializeSimulation(request, callback) { + Post(request, callback, 'SimulationInitialization', User, IdResponse); +} + +// Executes the FrameInitialization procedure on the backend +// Args: +// request (object): The request to POST to the backend +// callback (object): The callback to execute once the backend responds. +// This callback accepts one IdResponse argument. +export async function InitializeFrame(request, callback) { + Post(request, callback, 'FrameInitialization', IdRequest, IdResponse); +} + +/** + * Modifies a frame + * @param {object} frameModification The request to POST to the backend + * @param {object} callback The callback to execute once the backend responds. + */ +export async function ModifyFrame(frameModification, callback) { + Post(frameModification, callback, 'FrameModification', FrameModification); +} + +/** + * Modifies a simulation + * @param {object} simulationModifcation The request to POST to the backend + * @param {object} callback The callback to execute once the backend responds. + */ +export async function ModifySimulation(simulationModification, callback) { + Post(simulationModification, callback, 'SimulationModification', SimulationModification); +} + +// Private method to issue a POST request to the backend +// Args: +// request (object): The request object to post +// callback (function): The function to call once the backend responds. +// If responseValidator is null, the callback is called with no argument. +// Otherwise, the callback is called with the backend's reponse as an argument +// backendProcedure (string): The procedure to request the backend to perform +// requestValidator (class): The validator class to use on the request object +// responseValidator (class): The validator class to use on the backend's response. +// If null, the callback is called with no argument. Defaults to null. +async function Post(request, callback, backendProcedure, requestValidator, responseValidator=null) { + requestValidator.Validate(request); + let fetch_url = `${server_url}/${backendProcedure}.php`; + let fetch_body = { + method: "POST", + headers: new Headers(), + body: JSON.stringify(request) + }; + let response = await fetch(fetch_url, fetch_body); + if (response.ok) { + if (responseValidator == null) { + callback(); + } else { + callback(responseValidator.FromJSON(await response.text())); + } + } else { + throw new Error(`Error ${response.status}: ${response.statusText}`) + } +} diff --git a/simulationfactory/src/util/FormatString.js b/simulationfactory/src/util/FormatString.js new file mode 100644 index 0000000..1b7b0d5 --- /dev/null +++ b/simulationfactory/src/util/FormatString.js @@ -0,0 +1,23 @@ +// We need our own runtime templating engine. +// I'm going to use the same format as a template literal. +// The values are passed as an object, and nested objects can be retrieved with ${name.subname}f syntax +// Does not support arrays right now. We may need to change that later +export default function FormatString(template, values) { + let matches = [...template.matchAll(/\$\{(.*?)\}/g)]; + let result = template; + for (let i in matches) { + let match = matches[i]; + let value = findValue(match[1], values); + result = result.replaceAll(match[0], value) + } + return result +} + +function findValue(name, values) { + let names = name.split("."); + while (names.length > 1) { + values = values[names[0]]; + names = names.slice(1); + } + return values[names[0]]; +} diff --git a/simulationfactory/src/util/FrameDropzone.js b/simulationfactory/src/util/FrameDropzone.js new file mode 100644 index 0000000..c702420 --- /dev/null +++ b/simulationfactory/src/util/FrameDropzone.js @@ -0,0 +1,47 @@ +import React, { useCallback }from 'react'; +import { useDropzone } from 'react-dropzone'; +import * as XLSX from 'xlsx'; + +export default function FrameDropzone(props) { + const onDrop = useCallback((importingFile) => { + props.frame.effects = []; + // TODO: This is only going to work with one file + importingFile.forEach((fileName) => { + const reader = new FileReader(); + reader.onabort = () => console.log('file reading was aborted'); + reader.onerror = () => console.log('file reading has failed'); + reader.onload = (event) => { + // The event variable above is when you select the file + // parse file + const binaryString = event.target.result; + const workbook = XLSX.read(binaryString, {type:'binary'}); + // get the ith worksheet... in this case the first one. + // TODO: Change later for multiple sheets + workbook.SheetNames.forEach((sheetName) => { + const worksheet = workbook.Sheets[sheetName]; + + const data = XLSX.utils.sheet_to_json(worksheet, { header:1, blankrows:false }); + + props.frame.effects.push({ + effects: data, + operation: "*", // Unimplemented + resource: sheetName + }); + // convert to readable data + // give the data to the console log to read it + console.log("File Data for workesheet " + sheetName + ": " + JSON.stringify(data)); + }); + props.commit(props.frame); + } + reader.readAsBinaryString(fileName); + }); + }, []) + const {getRootProps, getInputProps} = useDropzone({onDrop}) + + return ( +
+ +

You may drag and drop a file here, or instead click to import the file.

+
+ ) +} \ No newline at end of file diff --git a/simulationfactory/src/util/RouteBuilder.js b/simulationfactory/src/util/RouteBuilder.js new file mode 100644 index 0000000..330ff12 --- /dev/null +++ b/simulationfactory/src/util/RouteBuilder.js @@ -0,0 +1,37 @@ +import React from "react"; +import {Route, Switch} from "react-router-dom"; + +let pages = new Map(); + +/** + * Registers all of the routes that should lead to the given page + * @param {React.Component} page The page to register routes for + * @param {String} route The route to register for this page + * @param {String} ...aliases All the other routes to register for this page +*/ +function RegisterRoutes(page, route, ...aliases) { + if (page === undefined) { + throw new Error("page in RouteBuilder.RegisterRoute has not been supplied"); + } + if (route === undefined) { + throw new Error("route in RouteBuilder.RegisterRoute has not been supplied"); + } + let newRoutes = [route, ...aliases]; + for (let newRoute of newRoutes) { + if (pages.has(newRoute)) { + throw new Error("RouteBuilder already contains route " + newRoute); + } + pages.set(newRoute, page); + } +} + +/** + * Builds all of the route tags required to link between pages +*/ +function BuildRoutes() { + let routes = []; + pages.forEach((Value, Key) => routes.push()); + return {routes}; +} + +export {RegisterRoutes, BuildRoutes} diff --git a/simulationfactory/src/util/Stylesheet.js b/simulationfactory/src/util/Stylesheet.js new file mode 100644 index 0000000..a4420af --- /dev/null +++ b/simulationfactory/src/util/Stylesheet.js @@ -0,0 +1,68 @@ +import { + createMuiTheme, + makeStyles, + responsiveFontSizes +} from "@material-ui/core/styles"; + +import palette from "./palette.json" + +const drawerWidth = 200; + +function CreateStyles() { + + const styles = makeStyles((Theme) => ({ + root: { + flexGrow: 1, + display: "flex" + }, + title: { + color: Theme.palette.primary.main, + }, + card: {// For future use since we use cards a lot + //flexGrow: 1, + //display: "flex", + //padding: Theme.spacing(2), + //textAlign: "center" + }, + drawer: { + [Theme.breakpoints.up("sm")]: { + width: drawerWidth, + flexShrink: 0 + }, + }, + drawerPaper: { + width: drawerWidth, + }, + appBar: { + [Theme.breakpoints.up("sm")]: { + width: `calc(100% - ${drawerWidth}px)`, + marginLeft: drawerWidth + }, + }, + menuButton: { + marginRight: Theme.spacing(2), + [Theme.breakpoints.up("sm")]: { + display: "none", + }, + }, + blue: { + color: "blue", + }, + // necessary for content to be below app bar + toolbar: Theme.mixins.toolbar, + content: { + flexGrow: 1, + padding: Theme.spacing(3) + }, + noDecoration: { + textDecoration: 'none' + }, + tabs: { + borderRight: `1px solid ${Theme.palette.divider}`, + }, + })) + return styles() +}; + +export const Theme = responsiveFontSizes(createMuiTheme(palette)); +export default CreateStyles diff --git a/simulationfactory/src/util/Validator.js b/simulationfactory/src/util/Validator.js new file mode 100644 index 0000000..e5a6239 --- /dev/null +++ b/simulationfactory/src/util/Validator.js @@ -0,0 +1,2 @@ +import Ajv from 'ajv'; +export default new Ajv({coerceTypes: true}); diff --git a/simulationfactory/src/util/palette.json b/simulationfactory/src/util/palette.json new file mode 100644 index 0000000..020b664 --- /dev/null +++ b/simulationfactory/src/util/palette.json @@ -0,0 +1,37 @@ +{ + "palette":{ + "common":{ + "black":"#000", + "white":"#fff", + "green":"rgba(57, 145, 67, 1)" + }, + "background":{ + "paper":"#fff", + "default":"#fafafa" + }, + "primary":{ + "light":"rgba(107, 194, 112, 1)", + "main":"rgba(57, 145, 67, 1)", + "dark":"rgba(0, 98, 24, 1)", + "contrastText":"#fff" + }, + "secondary":{ + "light":"rgba(99, 166, 94, 1)", + "main":"rgba(51, 119, 51, 1)", + "dark":"rgba(0, 74, 8, 1)", + "contrastText":"#fff" + }, + "error":{ + "light":"#e57373", + "main":"#f44336", + "dark":"#d32f2f", + "contrastText":"#fff" + }, + "text":{ + "primary":"rgba(0, 0, 0, 0.87)", + "secondary":"rgba(0, 0, 0, 0.54)", + "disabled":"rgba(0, 0, 0, 0.38)", + "hint":"rgba(0, 0, 0, 0.38)" + } + } +} \ No newline at end of file