Added the basics of keybindings + zooming
This commit is contained in:
		
					parent
					
						
							
								970d43187d
							
						
					
				
			
			
				commit
				
					
						057c2268ac
					
				
			
		
					 49 changed files with 1059 additions and 76 deletions
				
			
		.vscode
package-lock.jsonpackage.jsonsrc
common
index.htmlmain.tsxmodules
activation
core
errors/helpers
keybindings
saving
simulation
simulationRenderer
storage/classes
vector2/helpers
							
								
								
									
										9
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							|  | @ -1,6 +1,5 @@ | |||
| { | ||||
| 	"eslint.enable": true, | ||||
| 	"editor.formatOnSave": true, | ||||
| 	"prettier.eslintIntegration": true, | ||||
| 	"eslint.validate": ["typescript", "typescriptreact"] | ||||
| } | ||||
|     "editor.formatOnSave": true, | ||||
|     "prettier.eslintIntegration": true, | ||||
|     "explorer.autoReveal": false | ||||
| } | ||||
|  |  | |||
							
								
								
									
										251
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										251
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -1463,6 +1463,114 @@ | |||
|             "resolved": "https://registry.npmjs.org/@eix-js/utils/-/utils-0.0.6.tgz", | ||||
|             "integrity": "sha512-VyxwQAN5bNKmSzafo9Ma9nNDdVqxrN+ikp9SqC/OyvbAyihfZm17R8yjexXnIyfGeZstRAuUvSIw1bUzrL+RqA==" | ||||
|         }, | ||||
|         "@emotion/hash": { | ||||
|             "version": "0.7.2", | ||||
|             "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.2.tgz", | ||||
|             "integrity": "sha512-RMtr1i6E8MXaBWwhXL3yeOU8JXRnz8GNxHvaUfVvwxokvayUY0zoBeWbKw1S9XkufmGEEdQd228pSZXFkAln8Q==" | ||||
|         }, | ||||
|         "@material-ui/core": { | ||||
|             "version": "4.2.1", | ||||
|             "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.2.1.tgz", | ||||
|             "integrity": "sha512-hasPQUFAb9OxKng7UX2+SjUWtVZbnkVJ/jHZWXTivVcU+UzvNIpA9AyRRQvZ8SPV6swP/HD2VzUBzoMEeRR6wg==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.2.0", | ||||
|                 "@material-ui/styles": "^4.2.0", | ||||
|                 "@material-ui/system": "^4.3.0", | ||||
|                 "@material-ui/types": "^4.1.1", | ||||
|                 "@material-ui/utils": "^4.1.0", | ||||
|                 "@types/react-transition-group": "^2.0.16", | ||||
|                 "clsx": "^1.0.2", | ||||
|                 "convert-css-length": "^2.0.1", | ||||
|                 "deepmerge": "^4.0.0", | ||||
|                 "hoist-non-react-statics": "^3.2.1", | ||||
|                 "is-plain-object": "^3.0.0", | ||||
|                 "normalize-scroll-left": "^0.2.0", | ||||
|                 "popper.js": "^1.14.1", | ||||
|                 "prop-types": "^15.7.2", | ||||
|                 "react-transition-group": "^4.0.0", | ||||
|                 "warning": "^4.0.1" | ||||
|             }, | ||||
|             "dependencies": { | ||||
|                 "is-plain-object": { | ||||
|                     "version": "3.0.0", | ||||
|                     "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", | ||||
|                     "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", | ||||
|                     "requires": { | ||||
|                         "isobject": "^4.0.0" | ||||
|                     } | ||||
|                 }, | ||||
|                 "isobject": { | ||||
|                     "version": "4.0.0", | ||||
|                     "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", | ||||
|                     "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==" | ||||
|                 }, | ||||
|                 "react-transition-group": { | ||||
|                     "version": "4.2.1", | ||||
|                     "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.2.1.tgz", | ||||
|                     "integrity": "sha512-IXrPr93VzCPupwm2O6n6C2kJIofJ/Rp5Ltihhm9UfE8lkuVX2ng/SUUl/oWjblybK9Fq2Io7LGa6maVqPB762Q==", | ||||
|                     "requires": { | ||||
|                         "@babel/runtime": "^7.4.5", | ||||
|                         "dom-helpers": "^3.4.0", | ||||
|                         "loose-envify": "^1.4.0", | ||||
|                         "prop-types": "^15.6.2" | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "@material-ui/styles": { | ||||
|             "version": "4.2.1", | ||||
|             "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.2.1.tgz", | ||||
|             "integrity": "sha512-1KSOZ17LBWBqIyPRsEpyb4snT/wRIfQTPi0x66UvSzznVK9MPAfJx3/s5lVT4vrGFObs/nj6Pet6Nhrdl2WCrg==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.2.0", | ||||
|                 "@emotion/hash": "^0.7.1", | ||||
|                 "@material-ui/types": "^4.1.1", | ||||
|                 "@material-ui/utils": "^4.1.0", | ||||
|                 "clsx": "^1.0.2", | ||||
|                 "csstype": "^2.5.2", | ||||
|                 "deepmerge": "^4.0.0", | ||||
|                 "hoist-non-react-statics": "^3.2.1", | ||||
|                 "jss": "10.0.0-alpha.17", | ||||
|                 "jss-plugin-camel-case": "10.0.0-alpha.17", | ||||
|                 "jss-plugin-default-unit": "10.0.0-alpha.17", | ||||
|                 "jss-plugin-global": "10.0.0-alpha.17", | ||||
|                 "jss-plugin-nested": "10.0.0-alpha.17", | ||||
|                 "jss-plugin-props-sort": "10.0.0-alpha.17", | ||||
|                 "jss-plugin-rule-value-function": "10.0.0-alpha.17", | ||||
|                 "jss-plugin-vendor-prefixer": "10.0.0-alpha.17", | ||||
|                 "prop-types": "^15.7.2", | ||||
|                 "warning": "^4.0.1" | ||||
|             } | ||||
|         }, | ||||
|         "@material-ui/system": { | ||||
|             "version": "4.3.1", | ||||
|             "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.3.1.tgz", | ||||
|             "integrity": "sha512-Krrc/p/A3rod4M3FYcsWSqE5KxpoyMzYuUHhs0Pns3KH+5kcFyBU+aYbIzMfUz58rhbHkqrShf1fjj7EKcgY0g==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.2.0", | ||||
|                 "deepmerge": "^4.0.0", | ||||
|                 "prop-types": "^15.7.2", | ||||
|                 "warning": "^4.0.1" | ||||
|             } | ||||
|         }, | ||||
|         "@material-ui/types": { | ||||
|             "version": "4.1.1", | ||||
|             "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-4.1.1.tgz", | ||||
|             "integrity": "sha512-AN+GZNXytX9yxGi0JOfxHrRTbhFybjUJ05rnsBVjcB+16e466Z0Xe5IxawuOayVZgTBNDxmPKo5j4V6OnMtaSQ==", | ||||
|             "requires": { | ||||
|                 "@types/react": "*" | ||||
|             } | ||||
|         }, | ||||
|         "@material-ui/utils": { | ||||
|             "version": "4.1.0", | ||||
|             "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.1.0.tgz", | ||||
|             "integrity": "sha512-muwmVU799tzPjzb+Q5E/CTDle0rXwkCAdvMVyU0BfbJhenkUsFmuYiCmbvMVOU1m6F1S5HWfXz8EP4pXwwAvrw==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.2.0", | ||||
|                 "prop-types": "^15.7.2", | ||||
|                 "react-is": "^16.8.0" | ||||
|             } | ||||
|         }, | ||||
|         "@types/deepmerge": { | ||||
|             "version": "2.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/@types/deepmerge/-/deepmerge-2.2.0.tgz", | ||||
|  | @ -1522,8 +1630,7 @@ | |||
|         "@types/prop-types": { | ||||
|             "version": "15.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", | ||||
|             "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==", | ||||
|             "dev": true | ||||
|             "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==" | ||||
|         }, | ||||
|         "@types/q": { | ||||
|             "version": "1.5.2", | ||||
|  | @ -1535,7 +1642,6 @@ | |||
|             "version": "16.8.23", | ||||
|             "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.23.tgz", | ||||
|             "integrity": "sha512-abkEOIeljniUN9qB5onp++g0EY38h7atnDHxwKUFz1r3VH1+yG1OKi2sNPTyObL40goBmfKFpdii2lEzwLX1cA==", | ||||
|             "dev": true, | ||||
|             "requires": { | ||||
|                 "@types/prop-types": "*", | ||||
|                 "csstype": "^2.2.0" | ||||
|  | @ -1562,6 +1668,14 @@ | |||
|                 "@types/react-router": "*" | ||||
|             } | ||||
|         }, | ||||
|         "@types/react-transition-group": { | ||||
|             "version": "2.9.2", | ||||
|             "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.9.2.tgz", | ||||
|             "integrity": "sha512-5Fv2DQNO+GpdPZcxp2x/OQG/H19A01WlmpjVD9cKvVFmoVLOZ9LvBgSWG6pSXIU4og5fgbvGPaCV5+VGkWAEHA==", | ||||
|             "requires": { | ||||
|                 "@types/react": "*" | ||||
|             } | ||||
|         }, | ||||
|         "@webassemblyjs/ast": { | ||||
|             "version": "1.8.5", | ||||
|             "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", | ||||
|  | @ -2723,6 +2837,11 @@ | |||
|                 "shallow-clone": "^1.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "clsx": { | ||||
|             "version": "1.0.4", | ||||
|             "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.0.4.tgz", | ||||
|             "integrity": "sha512-1mQ557MIZTrL/140j+JVdRM6e31/OA4vTYxXgqIIZlndyfjHpyawKZia1Im05Vp9BWmImkcNrNtFYQMyFcgJDg==" | ||||
|         }, | ||||
|         "coa": { | ||||
|             "version": "2.0.2", | ||||
|             "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", | ||||
|  | @ -2939,6 +3058,11 @@ | |||
|             "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", | ||||
|             "dev": true | ||||
|         }, | ||||
|         "convert-css-length": { | ||||
|             "version": "2.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/convert-css-length/-/convert-css-length-2.0.1.tgz", | ||||
|             "integrity": "sha512-iGpbcvhLPRKUbBc0Quxx7w/bV14AC3ItuBEGMahA5WTYqB8lq9jH0kTXFheCBASsYnqeMFZhiTruNxr1N59Axg==" | ||||
|         }, | ||||
|         "convert-source-map": { | ||||
|             "version": "1.6.0", | ||||
|             "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", | ||||
|  | @ -3191,6 +3315,15 @@ | |||
|             "integrity": "sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w=", | ||||
|             "dev": true | ||||
|         }, | ||||
|         "css-vendor": { | ||||
|             "version": "2.0.5", | ||||
|             "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.5.tgz", | ||||
|             "integrity": "sha512-36w+4Cg0zqFIt5TAkaM3proB6XWh5kSGmbddRCPdrRLQiYNfHPTgaWPOlCrcuZIO0iAtrG+5wsHJZ6jj8AUULA==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.3.1", | ||||
|                 "is-in-browser": "^1.0.2" | ||||
|             } | ||||
|         }, | ||||
|         "css-what": { | ||||
|             "version": "2.1.3", | ||||
|             "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", | ||||
|  | @ -3310,8 +3443,7 @@ | |||
|         "csstype": { | ||||
|             "version": "2.6.6", | ||||
|             "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz", | ||||
|             "integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==", | ||||
|             "dev": true | ||||
|             "integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==" | ||||
|         }, | ||||
|         "currently-unhandled": { | ||||
|             "version": "0.4.1", | ||||
|  | @ -5528,6 +5660,11 @@ | |||
|             "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", | ||||
|             "dev": true | ||||
|         }, | ||||
|         "hyphenate-style-name": { | ||||
|             "version": "1.0.3", | ||||
|             "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz", | ||||
|             "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==" | ||||
|         }, | ||||
|         "iconv-lite": { | ||||
|             "version": "0.4.24", | ||||
|             "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", | ||||
|  | @ -5833,6 +5970,11 @@ | |||
|                 "is-extglob": "^2.1.1" | ||||
|             } | ||||
|         }, | ||||
|         "is-in-browser": { | ||||
|             "version": "1.1.3", | ||||
|             "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", | ||||
|             "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" | ||||
|         }, | ||||
|         "is-number": { | ||||
|             "version": "3.0.0", | ||||
|             "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", | ||||
|  | @ -6083,6 +6225,87 @@ | |||
|                 "verror": "1.10.0" | ||||
|             } | ||||
|         }, | ||||
|         "jss": { | ||||
|             "version": "10.0.0-alpha.17", | ||||
|             "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0-alpha.17.tgz", | ||||
|             "integrity": "sha512-egGIUg+YRu0+U+XXlD0gmVtU/gW5sn7+qmDv7opwK5s8emZBE/VoN55X6CaMrAa0kLeGMldnI43KOWea6M9/mA==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.3.1", | ||||
|                 "is-in-browser": "^1.1.3", | ||||
|                 "tiny-warning": "^1.0.2" | ||||
|             } | ||||
|         }, | ||||
|         "jss-plugin-camel-case": { | ||||
|             "version": "10.0.0-alpha.17", | ||||
|             "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.0-alpha.17.tgz", | ||||
|             "integrity": "sha512-aPY4kr6MwliH7KToLRzeSk1NxXUo9n7MQsAa0Hghwj01x9UnMkDkGAKENMKUtPjGkQZfiJpB9tTLFrSJ/6VrIQ==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.3.1", | ||||
|                 "hyphenate-style-name": "^1.0.3", | ||||
|                 "jss": "10.0.0-alpha.17" | ||||
|             } | ||||
|         }, | ||||
|         "jss-plugin-default-unit": { | ||||
|             "version": "10.0.0-alpha.17", | ||||
|             "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.0-alpha.17.tgz", | ||||
|             "integrity": "sha512-KQgiXczvzJ9AlFdD8NS7FZLub0NSctSrCA9Yi/GqdsfJg4ZCriU4DzIybCZBHCi/INFGJmLIESYWSxnuhAzgSQ==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.3.1", | ||||
|                 "jss": "10.0.0-alpha.17" | ||||
|             } | ||||
|         }, | ||||
|         "jss-plugin-global": { | ||||
|             "version": "10.0.0-alpha.17", | ||||
|             "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.0.0-alpha.17.tgz", | ||||
|             "integrity": "sha512-WYxiwwI+CLk0ozW8loeceqXBAZXBMsLBEZeRwVf9WX+FljdJkGwVZpRCk6LBX4aXnqAGyKqCxIAIJ3KP2yBdEg==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.3.1", | ||||
|                 "jss": "10.0.0-alpha.17" | ||||
|             } | ||||
|         }, | ||||
|         "jss-plugin-nested": { | ||||
|             "version": "10.0.0-alpha.17", | ||||
|             "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.0.0-alpha.17.tgz", | ||||
|             "integrity": "sha512-onpFqv904KCujryf2t6IIV1/QoB7cSF7ojrd4UujcN5TPvYOvXF5bchi7jnHG5U0SLlRSDGMLJ9fhtoCknhEbw==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.3.1", | ||||
|                 "jss": "10.0.0-alpha.17", | ||||
|                 "tiny-warning": "^1.0.2" | ||||
|             } | ||||
|         }, | ||||
|         "jss-plugin-props-sort": { | ||||
|             "version": "10.0.0-alpha.17", | ||||
|             "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.0-alpha.17.tgz", | ||||
|             "integrity": "sha512-KnbyrxCbtQTqpDx2mSZU/r/E5QnDPIVfIxRi8K+W/q4gZpomBvqWC+xgvAk9hbpmA6QBoQaOilV8o12w2IZ6fg==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.3.1", | ||||
|                 "jss": "10.0.0-alpha.17" | ||||
|             } | ||||
|         }, | ||||
|         "jss-plugin-rule-value-function": { | ||||
|             "version": "10.0.0-alpha.17", | ||||
|             "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.0-alpha.17.tgz", | ||||
|             "integrity": "sha512-8AuJB44Q+ehfkWVRi2XlRbUf6SrLmrHTa5EXd6dgQRCCRuvGmqX8Dl4fZvNeKRFjTLPZgzg9+31rqeOMhKa2vA==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.3.1", | ||||
|                 "jss": "10.0.0-alpha.17" | ||||
|             } | ||||
|         }, | ||||
|         "jss-plugin-vendor-prefixer": { | ||||
|             "version": "10.0.0-alpha.17", | ||||
|             "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.0-alpha.17.tgz", | ||||
|             "integrity": "sha512-wDq9EL0QaoMGSGifPEBb+/SA9LBcqPEW0jpL9ht+Z2t+lV7NNz0j7uCEOuE6FvNWqHzUKTsiATs1rTHPkzNBEQ==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.3.1", | ||||
|                 "css-vendor": "^2.0.1", | ||||
|                 "jss": "10.0.0-alpha.17" | ||||
|             } | ||||
|         }, | ||||
|         "keycode": { | ||||
|             "version": "2.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", | ||||
|             "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" | ||||
|         }, | ||||
|         "killable": { | ||||
|             "version": "1.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", | ||||
|  | @ -6847,6 +7070,11 @@ | |||
|             "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", | ||||
|             "dev": true | ||||
|         }, | ||||
|         "normalize-scroll-left": { | ||||
|             "version": "0.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.2.0.tgz", | ||||
|             "integrity": "sha512-t5oCENZJl8TGusJKoCJm7+asaSsPuNmK6+iEjrZ5TyBj2f02brCRsd4c83hwtu+e5d4LCSBZ0uoDlMjBo+A8yA==" | ||||
|         }, | ||||
|         "normalize-url": { | ||||
|             "version": "1.9.1", | ||||
|             "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", | ||||
|  | @ -7362,6 +7590,11 @@ | |||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "popper.js": { | ||||
|             "version": "1.15.0", | ||||
|             "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz", | ||||
|             "integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==" | ||||
|         }, | ||||
|         "portfinder": { | ||||
|             "version": "1.0.21", | ||||
|             "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.21.tgz", | ||||
|  | @ -10234,6 +10467,14 @@ | |||
|             "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", | ||||
|             "dev": true | ||||
|         }, | ||||
|         "warning": { | ||||
|             "version": "4.0.3", | ||||
|             "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", | ||||
|             "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", | ||||
|             "requires": { | ||||
|                 "loose-envify": "^1.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "watchpack": { | ||||
|             "version": "1.6.0", | ||||
|             "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", | ||||
|  |  | |||
|  | @ -5,7 +5,8 @@ | |||
|     "scripts": { | ||||
|         "dev": "webpack-dev-server --open --mode development", | ||||
|         "build": "cross-env NODE_ENV=production webpack", | ||||
|         "deploy": "ts-node deploy" | ||||
|         "deploy": "ts-node deploy", | ||||
|         "show": "gource -f  --start-date \"2019-07-01 12:00\" --key --hide dirnames,filenames,bloom" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@babel/core": "^7.5.5", | ||||
|  | @ -39,7 +40,9 @@ | |||
|     }, | ||||
|     "dependencies": { | ||||
|         "@eix-js/utils": "0.0.6", | ||||
|         "@material-ui/core": "^4.2.1", | ||||
|         "deepmerge": "^4.0.0", | ||||
|         "keycode": "^2.2.0", | ||||
|         "mainloop.js": "^1.0.4", | ||||
|         "react": "^16.8.6", | ||||
|         "react-dom": "^16.8.6", | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| import { SimulationRenderer } from '../../../modules/simulationRenderer/classes/SimulationRenderer' | ||||
| import { Screen } from '../../../modules/core/classes/Screen' | ||||
| 
 | ||||
| export const clearCanvas = ( | ||||
|     ctx: CanvasRenderingContext2D, | ||||
|     renderer: SimulationRenderer | ||||
| ) => { | ||||
|     ctx.clearRect(0, 0, ...renderer.camera.transform.scale) | ||||
| const screen = new Screen() | ||||
| 
 | ||||
| export const clearCanvas = (ctx: CanvasRenderingContext2D) => { | ||||
|     ctx.clearRect(0, 0, screen.x, screen.y) | ||||
| } | ||||
|  |  | |||
							
								
								
									
										2
									
								
								src/common/lang/arrays/removeDuplicates.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/common/lang/arrays/removeDuplicates.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| export const removeDuplicates = <T>(array: T[]): T[] => | ||||
|     Array.from(new Set<T>(array).values()) | ||||
|  | @ -3,9 +3,17 @@ | |||
|     <head> | ||||
|         <title>Logic gate simulator</title> | ||||
| 
 | ||||
|         <link | ||||
|             rel="stylesheet" | ||||
|             href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" | ||||
|         /> | ||||
|         <link | ||||
|             rel="stylesheet" | ||||
|             href="https://fonts.googleapis.com/icon?family=Material+Icons" | ||||
|         /> | ||||
|         <meta | ||||
|             name="viewport" | ||||
|             content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=0" | ||||
|             content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no" | ||||
|         /> | ||||
|     </head> | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										10
									
								
								src/main.tsx
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								src/main.tsx
									
										
									
									
									
								
							|  | @ -2,8 +2,12 @@ import React from 'react' | |||
| import App from './modules/core/components/App' | ||||
| 
 | ||||
| import { render } from 'react-dom' | ||||
| import { handleErrors } from './modules/errors/helpers/handlErrors' | ||||
| 
 | ||||
| render(<App />, document.getElementById('app')) | ||||
| import { handleErrors } from './modules/errors/helpers/handleErrors' | ||||
| import { initKeyBindings } from './modules/keybindings/helpers/initialiseKeyBindings' | ||||
| import { initBaseTemplates } from './modules/saving/helpers/initBaseTemplates' | ||||
| 
 | ||||
| handleErrors() | ||||
| initKeyBindings() | ||||
| initBaseTemplates() | ||||
| 
 | ||||
| render(<App />, document.getElementById('app')) | ||||
|  |  | |||
							
								
								
									
										8
									
								
								src/modules/activation/helpers/toFunction.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/modules/activation/helpers/toFunction.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| export const toFunction = <T extends unknown[]>( | ||||
|     source: string, | ||||
|     ...args: string[] | ||||
| ): ((...args: T) => void) => { | ||||
|     return new Function(`return (${args.join(',')}) => {
 | ||||
|         ${source} | ||||
|     }`)()
 | ||||
| } | ||||
							
								
								
									
										5
									
								
								src/modules/activation/types/Context.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/modules/activation/types/Context.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| export interface Context { | ||||
|     memory: Record<string, unknown> | ||||
|     get: (index: number) => boolean | ||||
|     set: (index: number, state: boolean) => void | ||||
| } | ||||
							
								
								
									
										11
									
								
								src/modules/core/QuestionModalSubjects.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/modules/core/QuestionModalSubjects.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| import { BehaviorSubject } from 'rxjs' | ||||
| 
 | ||||
| export type Question = null | { | ||||
|     text: string | ||||
|     options: { | ||||
|         text: string | ||||
|         icon: string | ||||
|     }[] | ||||
| } | ||||
| 
 | ||||
| export const QuestionSubject = new BehaviorSubject<Question>(null) | ||||
|  | @ -1,13 +1,27 @@ | |||
| import React from 'react' | ||||
| import '../styles/reset' | ||||
| import './App.scss' | ||||
| import 'react-toastify/dist/ReactToastify.css' | ||||
| import Canvas from './Canvas' | ||||
| 
 | ||||
| import { ToastContainer } from 'react-toastify' | ||||
| import { theme as muiTheme } from '../constants' | ||||
| 
 | ||||
| import React from 'react' | ||||
| import Canvas from './Canvas' | ||||
| import CssBaseline from '@material-ui/core/CssBaseline' | ||||
| import Theme from '@material-ui/styles/ThemeProvider' | ||||
| import Sidebar from './Sidebar' | ||||
| import QuestionModal from './QuestionModal' | ||||
| 
 | ||||
| const App = () => { | ||||
|     return ( | ||||
|         <> | ||||
|             <Theme theme={muiTheme}> | ||||
|                 <CssBaseline /> | ||||
|                 <Canvas /> | ||||
|                 <Sidebar /> | ||||
|                 <QuestionModal /> | ||||
|             </Theme> | ||||
|             <CssBaseline /> | ||||
|             <ToastContainer | ||||
|                 position="top-left" | ||||
|                 autoClose={5000} | ||||
|  | @ -18,7 +32,6 @@ const App = () => { | |||
|                 draggable | ||||
|                 pauseOnHover | ||||
|             /> | ||||
|             <Canvas /> | ||||
|         </> | ||||
|     ) | ||||
| } | ||||
|  |  | |||
|  | @ -5,33 +5,17 @@ import { Gate } from '../../simulation/classes/Gate' | |||
| import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer' | ||||
| import { renderSimulation } from '../../simulationRenderer/helpers/renderSimulation' | ||||
| import { updateSimulation } from '../../simulationRenderer/helpers/updateSimulation' | ||||
| import { addGate } from '../../simulation/helpers/addGate' | ||||
| 
 | ||||
| class Canvas extends Component { | ||||
|     private canvasRef: RefObject<HTMLCanvasElement> = createRef() | ||||
|     private renderingContext: CanvasRenderingContext2D | null | ||||
|     private renderer = new SimulationRenderer() | ||||
|     private renderer = new SimulationRenderer(this.canvasRef) | ||||
| 
 | ||||
|     public constructor(props: {}) { | ||||
|         super(props) | ||||
| 
 | ||||
|         const foo = new Gate({ | ||||
|             material: { | ||||
|                 value: 'blue' | ||||
|             } | ||||
|         }) | ||||
|         const bar = new Gate({ | ||||
|             material: { | ||||
|                 value: 'green' | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         foo.transform.position = [100, 100] | ||||
|         foo.transform.scale = [100, 100] | ||||
| 
 | ||||
|         bar.transform.position = [400, 200] | ||||
|         bar.transform.scale = [100, 100] | ||||
| 
 | ||||
|         this.renderer.simulation.push(foo, bar) | ||||
|         addGate(this.renderer.simulation, 'not') | ||||
| 
 | ||||
|         loop.setDraw(() => { | ||||
|             if (this.renderingContext) { | ||||
|  | @ -41,8 +25,10 @@ class Canvas extends Component { | |||
|     } | ||||
| 
 | ||||
|     public componentDidMount() { | ||||
|         if (this.canvasRef.current) | ||||
|         if (this.canvasRef.current) { | ||||
|             this.renderingContext = this.canvasRef.current.getContext('2d') | ||||
|             this.renderer.updateWheelListener() | ||||
|         } | ||||
| 
 | ||||
|         loop.start() | ||||
|     } | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import React, { RefObject, forwardRef, MouseEvent } from 'react' | ||||
| import React, { RefObject, forwardRef, MouseEvent, WheelEvent } from 'react' | ||||
| import { useObservable } from 'rxjs-hooks' | ||||
| import { Screen } from '../classes/Screen' | ||||
| import { Subject } from 'rxjs' | ||||
|  |  | |||
							
								
								
									
										7
									
								
								src/modules/core/components/QuestionModal.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/modules/core/components/QuestionModal.scss
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| .questionModal { | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/modules/core/components/QuestionModal.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/modules/core/components/QuestionModal.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| import './QuestionModal.scss' | ||||
| import { useObservable } from 'rxjs-hooks' | ||||
| import { QuestionSubject } from '../QuestionModalSubjects' | ||||
| import React from 'react' | ||||
| 
 | ||||
| const QuestionModal = () => { | ||||
|     const question = useObservable(() => QuestionSubject) | ||||
| 
 | ||||
|     if (!question) return <></> | ||||
| 
 | ||||
|     return <div className="questionModal">{question.text}</div> | ||||
| } | ||||
| 
 | ||||
| export default QuestionModal | ||||
							
								
								
									
										53
									
								
								src/modules/core/components/Sidebar.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/modules/core/components/Sidebar.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | |||
| import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' | ||||
| import React from 'react' | ||||
| import Drawer from '@material-ui/core/Drawer' | ||||
| import Button from '@material-ui/core/Button' | ||||
| 
 | ||||
| const drawerWidth = 240 | ||||
| const useStyles = makeStyles((theme: Theme) => | ||||
|     createStyles({ | ||||
|         root: { | ||||
|             display: 'flex' | ||||
|         }, | ||||
|         drawer: { | ||||
|             width: drawerWidth, | ||||
|             flexShrink: 0 | ||||
|         }, | ||||
|         drawerPaper: { | ||||
|             padding: '4px', | ||||
|             width: drawerWidth, | ||||
|             background: `#111111` | ||||
|         }, | ||||
|         drawerHeader: { | ||||
|             display: 'flex', | ||||
|             alignItems: 'center', | ||||
|             padding: '0 8px', | ||||
|             ...theme.mixins.toolbar, | ||||
|             justifyContent: 'flex-start' | ||||
|         } | ||||
|     }) | ||||
| ) | ||||
| 
 | ||||
| const Sidebar = () => { | ||||
|     const classes = useStyles() | ||||
| 
 | ||||
|     return ( | ||||
|         <div className={classes.root}> | ||||
|             <Drawer | ||||
|                 className={classes.drawer} | ||||
|                 variant="persistent" | ||||
|                 anchor="right" | ||||
|                 open={true} | ||||
|                 classes={{ | ||||
|                     paper: classes.drawerPaper | ||||
|                 }} | ||||
|             > | ||||
|                 <Button variant={'contained'} color="primary"> | ||||
|                     New Simulation | ||||
|                 </Button> | ||||
|             </Drawer> | ||||
|         </div> | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| export default Sidebar | ||||
							
								
								
									
										10
									
								
								src/modules/core/constants.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/modules/core/constants.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| import { createMuiTheme } from '@material-ui/core/styles' | ||||
| import { red, deepPurple } from '@material-ui/core/colors' | ||||
| 
 | ||||
| export const theme = createMuiTheme({ | ||||
|     palette: { | ||||
|         type: 'dark', | ||||
|         primary: deepPurple, | ||||
|         secondary: red | ||||
|     } | ||||
| }) | ||||
|  | @ -8,7 +8,5 @@ export const handleErrors = () => { | |||
| 
 | ||||
|             toast.error(...args) | ||||
|         } | ||||
| 
 | ||||
|         console.log(a) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										94
									
								
								src/modules/keybindings/classes/KeyboardInput.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/modules/keybindings/classes/KeyboardInput.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,94 @@ | |||
| import { fromEvent, Subject, Subscription } from 'rxjs' | ||||
| import keycode from 'keycode' | ||||
| 
 | ||||
| export class KeyboardInput { | ||||
|     /** | ||||
|      * boolean showing the state of the event | ||||
|      */ | ||||
|     public value = false | ||||
| 
 | ||||
|     /** | ||||
|      * array with all the pressed keys | ||||
|      */ | ||||
|     private pressed: Array<string> = [] | ||||
| 
 | ||||
|     /** | ||||
|      * the keys to listen for events to | ||||
|      */ | ||||
|     private keys: Array<string> | ||||
| 
 | ||||
|     /** | ||||
|      * an observable of the state o the event | ||||
|      */ | ||||
|     public valueChanges = new Subject<boolean>() | ||||
| 
 | ||||
|     /** | ||||
|      * keeps track of the subscriptions for disposing | ||||
|      */ | ||||
|     private subscription: Array<Subscription> = [] | ||||
| 
 | ||||
|     /** | ||||
|      * use for keyboard events | ||||
|      * @param params the keys to listen to | ||||
|      */ | ||||
|     public constructor(...params: string[]) { | ||||
|         //save the keys
 | ||||
|         this.keys = params | ||||
| 
 | ||||
|         //push a new subscription to the subscriptions array
 | ||||
|         this.subscription.push( | ||||
|             fromEvent(document, 'keydown').subscribe(e => { | ||||
|                 //remember the length of the pressed array
 | ||||
|                 //used to see if anything changed
 | ||||
|                 const last = this.pressed.length | ||||
|                 //iterate over the keys it listens to
 | ||||
|                 //if the key is pressed and it isnt already pressed,
 | ||||
|                 //then add it to the pressed array
 | ||||
|                 for (let i of this.keys) | ||||
|                     if (i == keycode(e) && this.pressed.indexOf(i) == -1) | ||||
|                         this.pressed.push(i) | ||||
| 
 | ||||
|                 //if there was no key pressd before, and now there is
 | ||||
|                 //then change the state of the event and emit it
 | ||||
|                 if (last == 0 && this.pressed.length != 0) { | ||||
|                     this.value = true | ||||
|                     this.valueChanges.next(this.value) | ||||
|                 } | ||||
|             }) | ||||
|         ) | ||||
| 
 | ||||
|         //push a new subscription to the subscriptions array
 | ||||
|         this.subscription.push( | ||||
|             fromEvent(document, 'keyup').subscribe(e => { | ||||
|                 //remember the length of the pressed array
 | ||||
|                 //used to see if anything changed
 | ||||
|                 const last = this.pressed.length | ||||
| 
 | ||||
|                 //iterate over the keys it listens to
 | ||||
|                 //if the key is released and it was pressed,
 | ||||
|                 //then remove it from the pressed array
 | ||||
|                 for (let i of this.keys) | ||||
|                     if (i == keycode(e) && this.pressed.indexOf(i) != -1) | ||||
|                         this.pressed.splice(this.pressed.indexOf(i), 1) | ||||
| 
 | ||||
|                 //if there was at least a key pressd before, and now there isnt
 | ||||
|                 //also, if the state was true
 | ||||
|                 //then change the state of the event and emit it
 | ||||
|                 if (this.value && last > 0 && this.pressed.length == 0) { | ||||
|                     this.value = false | ||||
|                     this.valueChanges.next(this.value) | ||||
|                 } | ||||
|             }) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * ends the listening | ||||
|      */ | ||||
|     public dispose() { | ||||
|         this.subscription.forEach(e => e.unsubscribe()) | ||||
|         this.value = false | ||||
|         this.valueChanges.next(false) | ||||
|         this.valueChanges.complete() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										4
									
								
								src/modules/keybindings/constants.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/modules/keybindings/constants.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| import { KeyBindingMap } from './types/KeyBindingMap' | ||||
| import { save } from '../saving/helpers/save' | ||||
| 
 | ||||
| export const keyBindings: KeyBindingMap = [] | ||||
							
								
								
									
										43
									
								
								src/modules/keybindings/helpers/initialiseKeyBindings.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/modules/keybindings/helpers/initialiseKeyBindings.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| import { keyBindings } from '../constants' | ||||
| import { KeyboardInput } from '../classes/KeyboardInput' | ||||
| import { KeyBindingMap } from '../types/KeyBindingMap' | ||||
| 
 | ||||
| export const listeners: Record<string, KeyboardInput> = {} | ||||
| 
 | ||||
| export const initKeyBindings = (bindings: KeyBindingMap = keyBindings) => { | ||||
|     const allKeys = new Set<string>() | ||||
| 
 | ||||
|     for (const binding of bindings) { | ||||
|         for (const key of binding.keys) { | ||||
|             allKeys.add(key) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     for (const key of allKeys.values()) { | ||||
|         listeners[key] = new KeyboardInput(key) | ||||
|     } | ||||
| 
 | ||||
|     window.addEventListener('keydown', e => { | ||||
|         for (const keyBinding of bindings) { | ||||
|             let done = false | ||||
| 
 | ||||
|             for (const key of keyBinding.keys) { | ||||
|                 if (!(done || listeners[key].value)) { | ||||
|                     done = true | ||||
|                     break | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (done) { | ||||
|                 continue | ||||
|             } | ||||
| 
 | ||||
|             for (const action of keyBinding.actions) { | ||||
|                 action() | ||||
|             } | ||||
| 
 | ||||
|             e.preventDefault() | ||||
|             e.stopPropagation() | ||||
|         } | ||||
|     }) | ||||
| } | ||||
							
								
								
									
										6
									
								
								src/modules/keybindings/types/KeyBindingMap.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/modules/keybindings/types/KeyBindingMap.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| export interface KeyBinding { | ||||
|     keys: string[] | ||||
|     actions: Function[] | ||||
| } | ||||
| 
 | ||||
| export type KeyBindingMap = KeyBinding[] | ||||
							
								
								
									
										27
									
								
								src/modules/saving/constants.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/modules/saving/constants.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| import { GateTemplate } from '../simulation/types/GateTemplate' | ||||
| 
 | ||||
| export const defaultSimulationName = 'default' | ||||
| export const baseTemplates: DeepPartial<GateTemplate>[] = [ | ||||
|     { | ||||
|         metadata: { | ||||
|             name: 'not' | ||||
|         }, | ||||
|         material: { | ||||
|             value: 'red', | ||||
|             type: 'color' | ||||
|         }, | ||||
|         code: { | ||||
|             activation: `context.set(0, !context.get(0))` | ||||
|         }, | ||||
|         pins: { | ||||
|             inputs: { | ||||
|                 count: 1, | ||||
|                 variable: false | ||||
|             }, | ||||
|             outputs: { | ||||
|                 count: 1, | ||||
|                 variable: false | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| ] | ||||
							
								
								
									
										68
									
								
								src/modules/saving/helpers/fromState.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/modules/saving/helpers/fromState.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | |||
| import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer' | ||||
| import { Gate, PinWrapper } from '../../simulation/classes/Gate' | ||||
| import { | ||||
|     TransformState, | ||||
|     RendererState, | ||||
|     CameraState, | ||||
|     SimulationState | ||||
| } from '../types/SimulationSave' | ||||
| import { Transform } from '../../../common/math/classes/Transform' | ||||
| import { Camera } from '../../simulationRenderer/classes/Camera' | ||||
| import { Simulation } from '../../simulation/classes/Simulation' | ||||
| import { Wire } from '../../simulation/classes/Wire' | ||||
| import { templateStore } from '../stores/templateStore' | ||||
| 
 | ||||
| export const fromTransformState = (state: TransformState): Transform => { | ||||
|     return new Transform(state.position, state.scale, state.rotation) | ||||
| } | ||||
| 
 | ||||
| export const fromCameraState = (state: CameraState): Camera => { | ||||
|     const camera = new Camera() | ||||
| 
 | ||||
|     camera.transform = fromTransformState(state.transform) | ||||
| 
 | ||||
|     return camera | ||||
| } | ||||
| 
 | ||||
| export const fromSimulationState = (state: SimulationState): Simulation => { | ||||
|     const simulation = new Simulation(state.mode) | ||||
| 
 | ||||
|     for (const gateState of state.gates) { | ||||
|         const gate = new Gate( | ||||
|             templateStore.get(gateState.template), | ||||
|             gateState.id | ||||
|         ) | ||||
|         gate.transform = fromTransformState(gateState.transform) | ||||
| 
 | ||||
|         simulation.push(gate) | ||||
|     } | ||||
| 
 | ||||
|     for (const wireState of state.wires) { | ||||
|         const startGateNode = simulation.gates.get(wireState.from.id) | ||||
|         const endGateNode = simulation.gates.get(wireState.to.id) | ||||
| 
 | ||||
|         if ( | ||||
|             startGateNode && | ||||
|             endGateNode && | ||||
|             startGateNode.data && | ||||
|             endGateNode.data | ||||
|         ) { | ||||
|             const start: PinWrapper = { | ||||
|                 index: wireState.from.index, | ||||
|                 total: wireState.from.total, | ||||
|                 value: startGateNode.data._pins.outputs[wireState.from.index] | ||||
|             } | ||||
|             const end: PinWrapper = { | ||||
|                 index: wireState.to.index, | ||||
|                 total: wireState.to.total, | ||||
|                 value: endGateNode.data._pins.inputs[wireState.to.index] | ||||
|             } | ||||
| 
 | ||||
|             const wire = new Wire(start, end, wireState.id) | ||||
| 
 | ||||
|             simulation.wires.push(wire) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return simulation | ||||
| } | ||||
|  | @ -31,7 +31,8 @@ export const getCameraState = (camera: Camera): CameraState => { | |||
| export const getWireLimit = (pin: PinWrapper): WireLimit => { | ||||
|     return { | ||||
|         id: pin.value.gate.id, | ||||
|         index: pin.index | ||||
|         index: pin.index, | ||||
|         total: pin.total | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										10
									
								
								src/modules/saving/helpers/initBaseTemplates.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/modules/saving/helpers/initBaseTemplates.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| import { baseTemplates } from '../constants' | ||||
| import { templateStore } from '../stores/templateStore' | ||||
| 
 | ||||
| export const initBaseTemplates = () => { | ||||
|     for (const template of baseTemplates) { | ||||
|         if (template.metadata && template.metadata.name) { | ||||
|             templateStore.set(template.metadata.name, template) | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/modules/saving/helpers/save.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/modules/saving/helpers/save.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer' | ||||
| import { currentStore } from '../stores/currentStore' | ||||
| import { SimulationError } from '../../errors/classes/SimulationError' | ||||
| import { getRendererState } from './getState' | ||||
| import { saveStore } from '../stores/saveStore' | ||||
| import { toast } from 'react-toastify' | ||||
| import { createToastArguments } from '../../toasts/helpers/createToastArguments' | ||||
| 
 | ||||
| export const save = (renderer: SimulationRenderer) => { | ||||
|     const current = currentStore.get() | ||||
| 
 | ||||
|     if (current) { | ||||
|         const state = getRendererState(renderer) | ||||
| 
 | ||||
|         saveStore.set(current, state) | ||||
| 
 | ||||
|         toast.info(...createToastArguments(`Succesfully saved ${current}`)) | ||||
|     } else { | ||||
|         throw new SimulationError( | ||||
|             'Cannot save without knowing the name of the active simulation' | ||||
|         ) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/modules/saving/stores/currentStore.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/modules/saving/stores/currentStore.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| import { LocalStore } from '../../storage/classes/LocalStore' | ||||
| import { defaultSimulationName } from '../constants' | ||||
| 
 | ||||
| const currentStore = new LocalStore<string>('currentSave') | ||||
| 
 | ||||
| if (!currentStore.get()) { | ||||
|     currentStore.set(defaultSimulationName) | ||||
| } | ||||
| 
 | ||||
| export { currentStore } | ||||
							
								
								
									
										4
									
								
								src/modules/saving/stores/saveStore.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/modules/saving/stores/saveStore.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| import { LocalStore } from '../../storage/classes/LocalStore' | ||||
| import { RendererState } from '../types/SimulationSave' | ||||
| 
 | ||||
| export const saveStore = new LocalStore<RendererState>('saves') | ||||
							
								
								
									
										6
									
								
								src/modules/saving/stores/templateStore.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/modules/saving/stores/templateStore.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| import { LocalStore } from '../../storage/classes/LocalStore' | ||||
| import { GateTemplate } from '../../simulation/types/GateTemplate' | ||||
| 
 | ||||
| export const templateStore = new LocalStore<DeepPartial<GateTemplate>>( | ||||
|     'templates' | ||||
| ) | ||||
|  | @ -21,6 +21,7 @@ export interface CameraState { | |||
| export interface WireLimit { | ||||
|     id: number | ||||
|     index: number | ||||
|     total: number | ||||
| } | ||||
| 
 | ||||
| export interface WireState { | ||||
|  |  | |||
|  | @ -4,6 +4,12 @@ import merge from 'deepmerge' | |||
| import { GateTemplate, PinCount } from '../types/GateTemplate' | ||||
| import { DefaultGateTemplate } from '../constants' | ||||
| import { idStore } from '../stores/idStore' | ||||
| import { Context } from '../../activation/types/Context' | ||||
| import { toFunction } from '../../activation/helpers/toFunction' | ||||
| import { Subscription, combineLatest } from 'rxjs' | ||||
| import { SimulationError } from '../../errors/classes/SimulationError' | ||||
| import { throttleTime, debounce, debounceTime } from 'rxjs/operators' | ||||
| import { getGateTimePipes } from '../helpers/getGateTimePipes' | ||||
| 
 | ||||
| export interface GatePins { | ||||
|     inputs: Pin[] | ||||
|  | @ -16,6 +22,10 @@ export interface PinWrapper { | |||
|     value: Pin | ||||
| } | ||||
| 
 | ||||
| export interface GateFunctions { | ||||
|     activation: null | ((ctx: Context) => void) | ||||
| } | ||||
| 
 | ||||
| export class Gate { | ||||
|     public transform = new Transform() | ||||
|     public _pins: GatePins = { | ||||
|  | @ -26,9 +36,23 @@ export class Gate { | |||
|     public id: number | ||||
|     public template: GateTemplate | ||||
| 
 | ||||
|     private functions: GateFunctions = { | ||||
|         activation: null | ||||
|     } | ||||
| 
 | ||||
|     private subscriptions: Subscription[] = [] | ||||
|     private memory: Record<string, unknown> = {} | ||||
| 
 | ||||
|     public constructor(template: DeepPartial<GateTemplate> = {}, id?: number) { | ||||
|         this.template = merge(DefaultGateTemplate, template) as GateTemplate | ||||
| 
 | ||||
|         this.transform.scale = this.template.shape.scale | ||||
| 
 | ||||
|         this.functions.activation = toFunction( | ||||
|             this.template.code.activation, | ||||
|             'context' | ||||
|         ) | ||||
| 
 | ||||
|         this._pins.inputs = Gate.generatePins( | ||||
|             this.template.pins.inputs, | ||||
|             1, | ||||
|  | @ -41,6 +65,51 @@ export class Gate { | |||
|         ) | ||||
| 
 | ||||
|         this.id = id !== undefined ? id : idStore.generate() | ||||
| 
 | ||||
|         for (const pin of this._pins.inputs) { | ||||
|             const pipes = getGateTimePipes(this.template) | ||||
| 
 | ||||
|             const subscription = pin.state.pipe(...pipes).subscribe(() => { | ||||
|                 this.update() | ||||
|             }) | ||||
| 
 | ||||
|             this.subscriptions.push(subscription) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public dispose() { | ||||
|         for (const pin of this.pins) { | ||||
|             pin.value.dispose() | ||||
|         } | ||||
| 
 | ||||
|         for (const subscription of this.subscriptions) { | ||||
|             subscription.unsubscribe() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public update() { | ||||
|         const context = this.getContext() | ||||
| 
 | ||||
|         if (!this.functions.activation) | ||||
|             throw new SimulationError('Activation function is missing') | ||||
| 
 | ||||
|         this.functions.activation(context) | ||||
|     } | ||||
| 
 | ||||
|     public getContext(): Context { | ||||
|         return { | ||||
|             get: (index: number) => { | ||||
|                 return this._pins.inputs[index].state.value | ||||
|             }, | ||||
|             set: (index: number, state: boolean = false) => { | ||||
|                 return this._pins.outputs[index].state.next(state) | ||||
|             }, | ||||
|             memory: this.memory | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private getInputsStates() { | ||||
|         return this._pins.inputs.map(pin => pin.state) | ||||
|     } | ||||
| 
 | ||||
|     private wrapPins(pins: Pin[]) { | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ export class GateStorage { | |||
|         this.tail.previous = this.head | ||||
|     } | ||||
| 
 | ||||
|     private delete(node: GateNode) { | ||||
|     public delete(node: GateNode) { | ||||
|         node.previous.next = node.next | ||||
|         node.next.previous = node.previous | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,4 +17,10 @@ export class Simulation { | |||
|             this.gates.set(gate.id, node) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public dispose() { | ||||
|         for (const gate of this.gates) { | ||||
|             gate.dispose() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { SimulationError } from '../../errors/classes/SimulationError' | |||
| 
 | ||||
| export class Wire { | ||||
|     public id: number | ||||
|     public active = true | ||||
| 
 | ||||
|     public constructor( | ||||
|         public start: PinWrapper, | ||||
|  | @ -23,5 +24,7 @@ export class Wire { | |||
|     public dispose() { | ||||
|         this.end.value.removePair(this.start.value) | ||||
|         this.start.value.removePair(this.end.value) | ||||
| 
 | ||||
|         this.active = false | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -20,6 +20,21 @@ export const DefaultGateTemplate: GateTemplate = { | |||
|     }, | ||||
|     shape: { | ||||
|         radius: 10, | ||||
|         rounded: true | ||||
|         rounded: true, | ||||
|         scale: [100, 100] | ||||
|     }, | ||||
|     code: { | ||||
|         activation: 'context.set(0,true)', | ||||
|         start: '', | ||||
|         stop: '' | ||||
|     }, | ||||
|     simulation: { | ||||
|         debounce: { | ||||
|             enabled: true, | ||||
|             time: 1000 / 60 | ||||
|         }, | ||||
|         throttle: { | ||||
|             enabled: false | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										13
									
								
								src/modules/simulation/helpers/addGate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/modules/simulation/helpers/addGate.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| import { templateStore } from '../../saving/stores/templateStore' | ||||
| import { SimulationError } from '../../errors/classes/SimulationError' | ||||
| import { Simulation } from '../classes/Simulation' | ||||
| import { Gate } from '../classes/Gate' | ||||
| 
 | ||||
| export const addGate = (simulation: Simulation, templateName: string) => { | ||||
|     const template = templateStore.get(templateName) | ||||
| 
 | ||||
|     if (!template) | ||||
|         throw new SimulationError(`Cannot find template ${templateName}`) | ||||
| 
 | ||||
|     simulation.push(new Gate(template)) | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/modules/simulation/helpers/getGateTimePipes.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/modules/simulation/helpers/getGateTimePipes.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| import { GateTemplate } from '../types/GateTemplate' | ||||
| import { debounceTime, throttleTime } from 'rxjs/operators' | ||||
| import { MonoTypeOperatorFunction, pipe } from 'rxjs' | ||||
| 
 | ||||
| export type TimePipe = MonoTypeOperatorFunction<boolean> | ||||
| 
 | ||||
| export const getGateTimePipes = (template: GateTemplate) => { | ||||
|     const pipes: TimePipe[] = [] | ||||
| 
 | ||||
|     if (template.simulation.debounce.enabled) { | ||||
|         pipes.push(debounceTime(template.simulation.debounce.time)) | ||||
|     } | ||||
| 
 | ||||
|     if (template.simulation.throttle.enabled) { | ||||
|         pipes.push(throttleTime(template.simulation.throttle.time)) | ||||
|     } | ||||
| 
 | ||||
|     return pipes as [TimePipe] | ||||
| } | ||||
|  | @ -1,3 +1,5 @@ | |||
| import { vector2 } from '../../../common/math/classes/Transform' | ||||
| 
 | ||||
| export interface PinCount { | ||||
|     variable: boolean | ||||
|     count: number | ||||
|  | @ -11,8 +13,21 @@ export interface Material { | |||
| export interface Shape { | ||||
|     rounded: boolean | ||||
|     radius: number | ||||
|     scale: vector2 | ||||
| } | ||||
| 
 | ||||
| export type Enabled<T> = | ||||
|     | { | ||||
|           enabled: false | ||||
|       } | ||||
|     | ({ | ||||
|           enabled: true | ||||
|       } & T) | ||||
| 
 | ||||
| export type TimePipe = Enabled<{ | ||||
|     time: number | ||||
| }> | ||||
| 
 | ||||
| export interface GateTemplate { | ||||
|     material: Material | ||||
|     shape: Shape | ||||
|  | @ -23,4 +38,13 @@ export interface GateTemplate { | |||
|     metadata: { | ||||
|         name: string | ||||
|     } | ||||
|     code: { | ||||
|         start: string | ||||
|         activation: string | ||||
|         stop: string | ||||
|     } | ||||
|     simulation: { | ||||
|         throttle: TimePipe | ||||
|         debounce: TimePipe | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -4,19 +4,22 @@ import { Screen } from '../../core/classes/Screen' | |||
| import { relativeTo } from '../../vector2/helpers/basic' | ||||
| 
 | ||||
| export class Camera { | ||||
|     private screen = new Screen() | ||||
|     public transform = new Transform([0, 0], [this.screen.x, this.screen.y]) | ||||
|     public transform = new Transform([0, 0]) | ||||
| 
 | ||||
|     public constructor() { | ||||
|         this.screen.height.subscribe(value => { | ||||
|             this.transform.height = value | ||||
|         }) | ||||
|         this.screen.width.subscribe(value => { | ||||
|             this.transform.width = value | ||||
|         }) | ||||
|         // this.screen.height.subscribe(value => {
 | ||||
|         //     this.transform.height = value
 | ||||
|         // })
 | ||||
|         // this.screen.width.subscribe(value => {
 | ||||
|         //     this.transform.width = value
 | ||||
|         // })
 | ||||
|     } | ||||
| 
 | ||||
|     public toWordPostition(position: vector2) { | ||||
|         return relativeTo(this.transform.position, position) | ||||
|         return [ | ||||
|             (position[0] - this.transform.position[0]) / | ||||
|                 this.transform.scale[0], | ||||
|             (position[1] - this.transform.position[1]) / this.transform.scale[1] | ||||
|         ] as vector2 | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import { Camera } from './Camera' | ||||
| import { Simulation } from '../../simulation/classes/Simulation' | ||||
| import { Subject } from 'rxjs' | ||||
| import { Subject, fromEvent } from 'rxjs' | ||||
| import { MouseEventInfo } from '../../core/components/FluidCanvas' | ||||
| import { pointInSquare } from '../../../common/math/helpers/pointInSquare' | ||||
| import { vector2 } from '../../../common/math/types/vector2' | ||||
|  | @ -9,25 +9,34 @@ import { Screen } from '../../core/classes/Screen' | |||
| import { relativeTo, add, invert } from '../../vector2/helpers/basic' | ||||
| import { SimulationRendererOptions } from '../types/SimulationRendererOptions' | ||||
| import { defaultSimulationRendererOptions } from '../constants' | ||||
| import merge from 'deepmerge' | ||||
| import { getPinPosition } from '../helpers/pinPosition' | ||||
| import { pointInCircle } from '../../../common/math/helpers/pointInCircle' | ||||
| import { SelectedPins } from '../types/SelectedPins' | ||||
| import { getRendererState } from '../../saving/helpers/getState' | ||||
| import { Wire } from '../../simulation/classes/Wire' | ||||
| import { KeyBindingMap } from '../../keybindings/types/KeyBindingMap' | ||||
| import { save } from '../../saving/helpers/save' | ||||
| import { initKeyBindings } from '../../keybindings/helpers/initialiseKeyBindings' | ||||
| import { currentStore } from '../../saving/stores/currentStore' | ||||
| import { saveStore } from '../../saving/stores/saveStore' | ||||
| import { SimulationError } from '../../errors/classes/SimulationError' | ||||
| import { | ||||
|     fromSimulationState, | ||||
|     fromCameraState | ||||
| } from '../../saving/helpers/fromState' | ||||
| import merge from 'deepmerge' | ||||
| import { wireConnectedToGate } from '../helpers/wireConnectedToGate' | ||||
| import { updateMouse, handleScroll } from '../helpers/scaleCanvas' | ||||
| import { RefObject } from 'react' | ||||
| // import { WheelEvent } from 'react'
 | ||||
| 
 | ||||
| export class SimulationRenderer { | ||||
|     public mouseDownOutput = new Subject<MouseEventInfo>() | ||||
|     public mouseUpOutput = new Subject<MouseEventInfo>() | ||||
|     public mouseMoveOutput = new Subject<MouseEventInfo>() | ||||
|     public wheelOutput = new Subject<unknown>() | ||||
| 
 | ||||
|     // first bit = dragging
 | ||||
|     // second bit = moving around
 | ||||
|     private mouseState = 0b00 | ||||
| 
 | ||||
|     private selectedGate: number | null = null | ||||
|     private gateSelectionOffset: vector2 = [0, 0] | ||||
| 
 | ||||
|     public selectedGate: number | null = null | ||||
|     public lastMousePosition: vector2 = [0, 0] | ||||
|     public movedSelection = false | ||||
|     public options: SimulationRendererOptions | ||||
|  | @ -35,12 +44,18 @@ export class SimulationRenderer { | |||
|     public screen = new Screen() | ||||
|     public camera = new Camera() | ||||
| 
 | ||||
|     // first bit = dragging
 | ||||
|     // second bit = moving around
 | ||||
|     private mouseState = 0b00 | ||||
|     private gateSelectionOffset: vector2 = [0, 0] | ||||
| 
 | ||||
|     public selectedPins: SelectedPins = { | ||||
|         start: null, | ||||
|         end: null | ||||
|     } | ||||
| 
 | ||||
|     public constructor( | ||||
|         public ref: RefObject<HTMLCanvasElement>, | ||||
|         options: Partial<SimulationRendererOptions> = {}, | ||||
|         public simulation = new Simulation() | ||||
|     ) { | ||||
|  | @ -93,7 +108,17 @@ export class SimulationRenderer { | |||
|                             this.options.gates.pinRadius | ||||
|                         ) | ||||
|                     ) { | ||||
|                         if ((pin.value.type & 0b10) >> 1) { | ||||
|                         if ( | ||||
|                             this.selectedPins.start && | ||||
|                             pin.value === this.selectedPins.start.wrapper.value | ||||
|                         ) { | ||||
|                             this.selectedPins.start = null | ||||
|                         } else if ( | ||||
|                             this.selectedPins.end && | ||||
|                             pin.value === this.selectedPins.end.wrapper.value | ||||
|                         ) { | ||||
|                             this.selectedPins.end = null | ||||
|                         } else if ((pin.value.type & 0b10) >> 1) { | ||||
|                             this.selectedPins.start = { | ||||
|                                 wrapper: pin, | ||||
|                                 transform | ||||
|  | @ -118,8 +143,6 @@ export class SimulationRenderer { | |||
|                             ) | ||||
|                             this.selectedPins.start = null | ||||
|                             this.selectedPins.end = null | ||||
| 
 | ||||
|                             console.log(getRendererState(this)) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | @ -144,6 +167,8 @@ export class SimulationRenderer { | |||
|         }) | ||||
| 
 | ||||
|         this.mouseMoveOutput.subscribe(event => { | ||||
|             updateMouse(event) | ||||
| 
 | ||||
|             const worldPosition = this.camera.toWordPostition(event.position) | ||||
| 
 | ||||
|             if (this.mouseState & 1 && this.selectedGate !== null) { | ||||
|  | @ -164,7 +189,9 @@ export class SimulationRenderer { | |||
|             if ((this.mouseState >> 1) & 1) { | ||||
|                 const offset = invert( | ||||
|                     relativeTo(this.lastMousePosition, worldPosition) | ||||
|                 ) | ||||
|                 ).map( | ||||
|                     (value, index) => value * this.camera.transform.scale[index] | ||||
|                 ) as vector2 | ||||
| 
 | ||||
|                 this.camera.transform.position = add( | ||||
|                     this.camera.transform.position, | ||||
|  | @ -174,6 +201,81 @@ export class SimulationRenderer { | |||
| 
 | ||||
|             this.lastMousePosition = this.camera.toWordPostition(event.position) | ||||
|         }) | ||||
| 
 | ||||
|         this.reloadSave() | ||||
|         this.initKeyBindings() | ||||
|     } | ||||
| 
 | ||||
|     public updateWheelListener() { | ||||
|         if (this.ref.current) { | ||||
|             this.ref.current.addEventListener('wheel', event => { | ||||
|                 event.preventDefault() | ||||
| 
 | ||||
|                 handleScroll(event, this.camera) | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public reloadSave() { | ||||
|         try { | ||||
|             const current = currentStore.get() | ||||
|             const save = saveStore.get(current) | ||||
| 
 | ||||
|             if (!save) return | ||||
|             if (!(save.simulation || save.camera)) return | ||||
| 
 | ||||
|             this.simulation.dispose() | ||||
|             this.simulation = fromSimulationState(save.simulation) | ||||
|             this.camera = fromCameraState(save.camera) | ||||
|         } catch (e) { | ||||
|             throw new Error( | ||||
|                 `An error occured while loading the save: ${ | ||||
|                     (e as Error).message | ||||
|                 }` | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private initKeyBindings() { | ||||
|         const bindings: KeyBindingMap = [ | ||||
|             { | ||||
|                 keys: ['ctrl', 's'], | ||||
|                 actions: [() => save(this)] | ||||
|             }, | ||||
|             { | ||||
|                 keys: ['delete'], | ||||
|                 actions: [ | ||||
|                     () => { | ||||
|                         const selected = this.getSelected() | ||||
| 
 | ||||
|                         if (!selected) { | ||||
|                             return | ||||
|                         } | ||||
| 
 | ||||
|                         const node = this.simulation.gates.get(selected.id) | ||||
| 
 | ||||
|                         if (!node) { | ||||
|                             return | ||||
|                         } | ||||
| 
 | ||||
|                         for (const wire of this.simulation.wires) { | ||||
|                             if (wireConnectedToGate(selected, wire)) { | ||||
|                                 wire.dispose() | ||||
|                             } | ||||
|                         } | ||||
| 
 | ||||
|                         this.simulation.wires = this.simulation.wires.filter( | ||||
|                             wire => wire.active | ||||
|                         ) | ||||
| 
 | ||||
|                         selected.dispose() | ||||
|                         this.simulation.gates.delete(node) | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         ] | ||||
| 
 | ||||
|         initKeyBindings(bindings) | ||||
|     } | ||||
| 
 | ||||
|     public getGateById(id: number) { | ||||
|  |  | |||
|  | @ -12,6 +12,11 @@ export const defaultSimulationRendererOptions: SimulationRendererOptions = { | |||
|         pinFill: { | ||||
|             open: 'rgb(255,216,20)', | ||||
|             closed: 'rgb(90,90,90)' | ||||
|         }, | ||||
|         gateStroke: { | ||||
|             active: 'yellow', | ||||
|             normal: 'black', | ||||
|             width: 4 | ||||
|         } | ||||
|     }, | ||||
|     wires: { | ||||
|  |  | |||
|  | @ -10,8 +10,17 @@ export const renderGate = ( | |||
| ) => { | ||||
|     renderPins(ctx, renderer, gate) | ||||
| 
 | ||||
|     if (renderer.selectedGate === gate.id) { | ||||
|         ctx.strokeStyle = renderer.options.gates.gateStroke.active | ||||
|     } else { | ||||
|         ctx.strokeStyle = renderer.options.gates.gateStroke.normal | ||||
|     } | ||||
| 
 | ||||
|     ctx.lineWidth = renderer.options.gates.gateStroke.width | ||||
| 
 | ||||
|     if (gate.template.material.type === 'color') { | ||||
|         ctx.fillStyle = gate.template.material.value | ||||
|         drawRotatedSquare(ctx, gate.transform, gate.template.shape) | ||||
|         ctx.stroke() | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,17 +1,21 @@ | |||
| import { SimulationRenderer } from '../classes/SimulationRenderer' | ||||
| import { invert } from '../../vector2/helpers/basic' | ||||
| import { invert, inverse } from '../../vector2/helpers/basic' | ||||
| import { renderGate } from './renderGate' | ||||
| import { clearCanvas } from '../../../common/canvas/helpers/clearCanvas' | ||||
| import { renderClickedPins } from './renderClickedPins' | ||||
| import { renderWires } from './renderWires' | ||||
| import { vector2 } from '../../../common/math/classes/Transform' | ||||
| 
 | ||||
| export const renderSimulation = ( | ||||
|     ctx: CanvasRenderingContext2D, | ||||
|     renderer: SimulationRenderer | ||||
| ) => { | ||||
|     clearCanvas(ctx, renderer) | ||||
|     clearCanvas(ctx) | ||||
| 
 | ||||
|     ctx.translate(...renderer.camera.transform.position) | ||||
|     const transform = renderer.camera.transform | ||||
| 
 | ||||
|     ctx.translate(...transform.position) | ||||
|     ctx.scale(...transform.scale) | ||||
| 
 | ||||
|     for (const wire of renderer.simulation.wires) { | ||||
|         renderWires(ctx, renderer, wire) | ||||
|  | @ -23,5 +27,6 @@ export const renderSimulation = ( | |||
| 
 | ||||
|     renderClickedPins(ctx, renderer) | ||||
| 
 | ||||
|     ctx.translate(...invert(renderer.camera.transform.position)) | ||||
|     ctx.scale(...inverse(transform.scale)) | ||||
|     ctx.translate(...invert(transform.position)) | ||||
| } | ||||
|  |  | |||
							
								
								
									
										39
									
								
								src/modules/simulationRenderer/helpers/scaleCanvas.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/modules/simulationRenderer/helpers/scaleCanvas.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| import { Screen } from '../../core/classes/Screen' | ||||
| import { clamp } from '../../simulation/helpers/clamp' | ||||
| import { Camera } from '../classes/Camera' | ||||
| import { vector2 } from '../../../common/math/classes/Transform' | ||||
| import { MouseEventInfo } from '../../core/components/FluidCanvas' | ||||
| // import { WheelEvent } from 'react'
 | ||||
| 
 | ||||
| const screen = new Screen() | ||||
| 
 | ||||
| const scrollStep = 1.3 | ||||
| const zoomLimits = [0.1, 10] | ||||
| 
 | ||||
| let absoluteMousePosition = [screen.x / 2, screen.y / 2] | ||||
| 
 | ||||
| export const updateMouse = (e: MouseEventInfo) => { | ||||
|     absoluteMousePosition = e.position | ||||
| } | ||||
| 
 | ||||
| export const handleScroll = (e: WheelEvent, camera: Camera) => { | ||||
|     const sign = e.deltaY / Math.abs(e.deltaY) | ||||
|     const zoom = scrollStep ** sign | ||||
| 
 | ||||
|     const size = [screen.width.value, screen.height.value] | ||||
|     const mouseFraction = size.map( | ||||
|         (value, index) => absoluteMousePosition[index] / value | ||||
|     ) | ||||
|     const newScale = camera.transform.scale.map(value => | ||||
|         clamp(zoomLimits[0], zoomLimits[1], value * zoom) | ||||
|     ) | ||||
|     const delta = camera.transform.scale.map( | ||||
|         (value, index) => | ||||
|             size[index] * (newScale[index] - value) * mouseFraction[index] | ||||
|     ) | ||||
| 
 | ||||
|     camera.transform.scale = newScale as vector2 | ||||
|     camera.transform.position = camera.transform.position.map( | ||||
|         (value, index) => value - delta[index] | ||||
|     ) as vector2 | ||||
| } | ||||
|  | @ -0,0 +1,5 @@ | |||
| import { Gate } from '../../simulation/classes/Gate' | ||||
| import { Wire } from '../../simulation/classes/Wire' | ||||
| 
 | ||||
| export const wireConnectedToGate = (gate: Gate, wire: Wire) => | ||||
|     wire.end.value.gate === gate || wire.start.value.gate === gate | ||||
|  | @ -11,6 +11,11 @@ export interface SimulationRendererOptions { | |||
|             open: string | ||||
|             closed: string | ||||
|         } | ||||
|         gateStroke: { | ||||
|             active: string | ||||
|             normal: string | ||||
|             width: number | ||||
|         } | ||||
|     } | ||||
|     wires: { | ||||
|         temporaryWireColor: string | ||||
|  |  | |||
|  | @ -32,7 +32,7 @@ export class LocalStore<T> { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public get(key = 'index') { | ||||
|     public get(key = 'index'): T | undefined { | ||||
|         return this.getAll()[key] | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -38,3 +38,5 @@ export const ofLength = (vector: vector2, l: number) => { | |||
| // This returns a vector relative to the other
 | ||||
| export const relativeTo = (vector: vector2, other: vector2) => | ||||
|     add(other, invert(vector)) | ||||
| 
 | ||||
| export const inverse = (vector: vector2) => vector.map(a => 1 / a) as vector2 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Matei Adriel
				Matei Adriel