can define multiple properties. can see detailed view of chosen property.
This commit is contained in:
parent
1660b06064
commit
c7e661eb61
31 changed files with 502 additions and 307 deletions
120
bun.lock
120
bun.lock
|
|
@ -6,75 +6,37 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/roboto": "^5.2.8",
|
"@fontsource/roboto": "^5.2.8",
|
||||||
"@mui/icons-material": "^7.3.4",
|
"@mui/icons-material": "^7.3.4",
|
||||||
"react": "^19",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19",
|
"react-dom": "^19.2.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "1.3.1",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19.2.2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
|
||||||
|
|
||||||
"@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="],
|
|
||||||
|
|
||||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
|
||||||
|
|
||||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
|
|
||||||
|
|
||||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
|
||||||
|
|
||||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
|
||||||
|
|
||||||
"@babel/parser": ["@babel/parser@7.28.4", "", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": "./bin/babel-parser.js" }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="],
|
|
||||||
|
|
||||||
"@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="],
|
"@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="],
|
||||||
|
|
||||||
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
|
||||||
|
|
||||||
"@babel/traverse": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="],
|
|
||||||
|
|
||||||
"@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="],
|
|
||||||
|
|
||||||
"@emotion/babel-plugin": ["@emotion/babel-plugin@11.13.5", "", { "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", "stylis": "4.2.0" } }, "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ=="],
|
|
||||||
|
|
||||||
"@emotion/cache": ["@emotion/cache@11.14.0", "", { "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA=="],
|
"@emotion/cache": ["@emotion/cache@11.14.0", "", { "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA=="],
|
||||||
|
|
||||||
"@emotion/hash": ["@emotion/hash@0.9.2", "", {}, "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="],
|
"@emotion/hash": ["@emotion/hash@0.9.2", "", {}, "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="],
|
||||||
|
|
||||||
"@emotion/is-prop-valid": ["@emotion/is-prop-valid@1.4.0", "", { "dependencies": { "@emotion/memoize": "^0.9.0" } }, "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw=="],
|
|
||||||
|
|
||||||
"@emotion/memoize": ["@emotion/memoize@0.9.0", "", {}, "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="],
|
"@emotion/memoize": ["@emotion/memoize@0.9.0", "", {}, "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="],
|
||||||
|
|
||||||
"@emotion/react": ["@emotion/react@11.14.0", "", { "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", "@emotion/cache": "^11.14.0", "@emotion/serialize": "^1.3.3", "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA=="],
|
|
||||||
|
|
||||||
"@emotion/serialize": ["@emotion/serialize@1.3.3", "", { "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/unitless": "^0.10.0", "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA=="],
|
"@emotion/serialize": ["@emotion/serialize@1.3.3", "", { "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/unitless": "^0.10.0", "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA=="],
|
||||||
|
|
||||||
"@emotion/sheet": ["@emotion/sheet@1.4.0", "", {}, "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="],
|
"@emotion/sheet": ["@emotion/sheet@1.4.0", "", {}, "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="],
|
||||||
|
|
||||||
"@emotion/styled": ["@emotion/styled@11.14.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", "@emotion/is-prop-valid": "^1.3.0", "@emotion/serialize": "^1.3.3", "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", "@emotion/utils": "^1.4.2" }, "peerDependencies": { "@emotion/react": "^11.0.0-rc.0", "react": ">=16.8.0" } }, "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw=="],
|
|
||||||
|
|
||||||
"@emotion/unitless": ["@emotion/unitless@0.10.0", "", {}, "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="],
|
"@emotion/unitless": ["@emotion/unitless@0.10.0", "", {}, "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="],
|
||||||
|
|
||||||
"@emotion/use-insertion-effect-with-fallbacks": ["@emotion/use-insertion-effect-with-fallbacks@1.2.0", "", { "peerDependencies": { "react": ">=16.8.0" } }, "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg=="],
|
|
||||||
|
|
||||||
"@emotion/utils": ["@emotion/utils@1.4.2", "", {}, "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA=="],
|
"@emotion/utils": ["@emotion/utils@1.4.2", "", {}, "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA=="],
|
||||||
|
|
||||||
"@emotion/weak-memoize": ["@emotion/weak-memoize@0.4.0", "", {}, "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="],
|
"@emotion/weak-memoize": ["@emotion/weak-memoize@0.4.0", "", {}, "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="],
|
||||||
|
|
||||||
"@fontsource/roboto": ["@fontsource/roboto@5.2.8", "", {}, "sha512-oh9g4Cg3loVMz9MWeKWfDI+ooxxG1aRVetkiKIb2ESS2rrryGecQ/y4pAj4z5A5ebyw450dYRi/c4k/I3UBhHA=="],
|
"@fontsource/roboto": ["@fontsource/roboto@5.2.8", "", {}, "sha512-oh9g4Cg3loVMz9MWeKWfDI+ooxxG1aRVetkiKIb2ESS2rrryGecQ/y4pAj4z5A5ebyw450dYRi/c4k/I3UBhHA=="],
|
||||||
|
|
||||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
|
||||||
|
|
||||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
|
||||||
|
|
||||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
|
||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
|
||||||
|
|
||||||
"@mui/core-downloads-tracker": ["@mui/core-downloads-tracker@7.3.4", "", {}, "sha512-BIktMapG3r4iXwIhYNpvk97ZfYWTreBBQTWjQKbNbzI64+ULHfYavQEX2w99aSWHS58DvXESWIgbD9adKcUOBw=="],
|
"@mui/core-downloads-tracker": ["@mui/core-downloads-tracker@7.3.4", "", {}, "sha512-BIktMapG3r4iXwIhYNpvk97ZfYWTreBBQTWjQKbNbzI64+ULHfYavQEX2w99aSWHS58DvXESWIgbD9adKcUOBw=="],
|
||||||
|
|
||||||
"@mui/icons-material": ["@mui/icons-material@7.3.4", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "@mui/material": "^7.3.4", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9n6Xcq7molXWYb680N2Qx+FRW8oT6j/LXF5PZFH3ph9X/Rct0B/BlLAsFI7iL9ySI6LVLuQIVtrLiPT82R7OZw=="],
|
"@mui/icons-material": ["@mui/icons-material@7.3.4", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "@mui/material": "^7.3.4", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9n6Xcq7molXWYb680N2Qx+FRW8oT6j/LXF5PZFH3ph9X/Rct0B/BlLAsFI7iL9ySI6LVLuQIVtrLiPT82R7OZw=="],
|
||||||
|
|
@ -93,80 +55,32 @@
|
||||||
|
|
||||||
"@popperjs/core": ["@popperjs/core@2.11.8", "", {}, "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="],
|
"@popperjs/core": ["@popperjs/core@2.11.8", "", {}, "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.2.23", "", { "dependencies": { "bun-types": "1.2.23" } }, "sha512-le8ueOY5b6VKYf19xT3McVbXqLqmxzPXHsQT/q9JHgikJ2X22wyTW3g3ohz2ZMnp7dod6aduIiq8A14Xyimm0A=="],
|
"@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.6.2", "", { "dependencies": { "undici-types": "~7.13.0" } }, "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang=="],
|
"@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="],
|
||||||
|
|
||||||
"@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
|
|
||||||
|
|
||||||
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
|
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
|
||||||
|
|
||||||
"@types/react": ["@types/react@19.2.0", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA=="],
|
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
|
||||||
|
|
||||||
"@types/react-dom": ["@types/react-dom@19.2.0", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg=="],
|
"@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
|
||||||
|
|
||||||
"@types/react-transition-group": ["@types/react-transition-group@4.4.12", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w=="],
|
"@types/react-transition-group": ["@types/react-transition-group@4.4.12", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w=="],
|
||||||
|
|
||||||
"babel-plugin-macros": ["babel-plugin-macros@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
|
"bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.2.23", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-R9f0hKAZXgFU3mlrA0YpE/fiDvwV0FT9rORApt2aQVWSuJDzZOyB5QLc0N/4HF57CS8IXJ6+L5E4W1bW6NS2Aw=="],
|
|
||||||
|
|
||||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
|
||||||
|
|
||||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
"convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
|
||||||
|
|
||||||
"cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="],
|
|
||||||
|
|
||||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||||
|
|
||||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
|
||||||
|
|
||||||
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
|
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
|
||||||
|
|
||||||
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
|
|
||||||
|
|
||||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
|
||||||
|
|
||||||
"find-root": ["find-root@1.1.0", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="],
|
|
||||||
|
|
||||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
|
||||||
|
|
||||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
|
||||||
|
|
||||||
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
|
|
||||||
|
|
||||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
|
||||||
|
|
||||||
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
|
||||||
|
|
||||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
|
||||||
|
|
||||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||||
|
|
||||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
|
||||||
|
|
||||||
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
|
|
||||||
|
|
||||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
|
||||||
|
|
||||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
|
||||||
|
|
||||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||||
|
|
||||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
|
||||||
|
|
||||||
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
|
|
||||||
|
|
||||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
|
||||||
|
|
||||||
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
|
|
||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
|
||||||
|
|
||||||
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
||||||
|
|
||||||
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
|
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
|
||||||
|
|
@ -177,23 +91,11 @@
|
||||||
|
|
||||||
"react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],
|
"react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],
|
||||||
|
|
||||||
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
|
||||||
|
|
||||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
|
||||||
|
|
||||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||||
|
|
||||||
"source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
|
|
||||||
|
|
||||||
"stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],
|
"stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],
|
||||||
|
|
||||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.13.0", "", {}, "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ=="],
|
|
||||||
|
|
||||||
"yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
|
|
||||||
|
|
||||||
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
|
||||||
|
|
||||||
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
global.d.ts
vendored
1
global.d.ts
vendored
|
|
@ -5,3 +5,4 @@ declare module '*.ttf';
|
||||||
declare module '*.wav';
|
declare module '*.wav';
|
||||||
declare module '*.opus';
|
declare module '*.opus';
|
||||||
declare module '*.webp';
|
declare module '*.webp';
|
||||||
|
declare module '*.wasm';
|
||||||
11
package.json
11
package.json
|
|
@ -13,12 +13,13 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/roboto": "^5.2.8",
|
"@fontsource/roboto": "^5.2.8",
|
||||||
"@mui/icons-material": "^7.3.4",
|
"@mui/icons-material": "^7.3.4",
|
||||||
"react": "^19",
|
// "argus-wasm": "git+https://deemz.org/git/joeri/argus-wasm.git#a4491b3433d48aa1f941bd5ad37b36f819d3b2ac",
|
||||||
"react-dom": "^19"
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^19",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19.2.2",
|
||||||
"@types/bun": "latest"
|
"@types/bun": "1.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,9 +74,20 @@ details:has(+ details) {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #fcfcfc;
|
||||||
|
border: 1px lightgrey solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:not(:disabled):hover {
|
||||||
|
background-color: rgba(0, 0, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
button.active {
|
button.active {
|
||||||
border: solid blue 2px;
|
border: solid blue 1px;
|
||||||
background-color: rgba(0,0,255,0.2);
|
background-color: rgba(0,0,255,0.2);
|
||||||
|
/* margin-right: 1px; */
|
||||||
|
/* margin-left: 0; */
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,3 +121,21 @@ div.stackHorizontal {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.status {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
background-color: grey;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 12px;
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.status.violated {
|
||||||
|
background-color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.status.satisfied {
|
||||||
|
background-color: forestgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
154
src/App/App.tsx
154
src/App/App.tsx
|
|
@ -3,26 +3,33 @@ import "./App.css";
|
||||||
|
|
||||||
import { Dispatch, ReactElement, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { Dispatch, ReactElement, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
|
||||||
|
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||||
|
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||||
|
|
||||||
|
import { Statechart } from "@/statecharts/abstract_syntax";
|
||||||
import { detectConnections } from "@/statecharts/detect_connections";
|
import { detectConnections } from "@/statecharts/detect_connections";
|
||||||
import { Conns, coupledExecution, EventDestination, statechartExecution, TimedReactive } from "@/statecharts/timed_reactive";
|
import { Conns, coupledExecution, statechartExecution } from "@/statecharts/timed_reactive";
|
||||||
import { RuntimeError } from "../statecharts/interpreter";
|
import { RuntimeError } from "../statecharts/interpreter";
|
||||||
import { parseStatechart } from "../statecharts/parser";
|
import { parseStatechart } from "../statecharts/parser";
|
||||||
import { BigStep, RaisedEvent } from "../statecharts/runtime_types";
|
import { BigStep, RaisedEvent } from "../statecharts/runtime_types";
|
||||||
import { getSimTime, getWallClkDelay, TimeMode } from "../statecharts/time";
|
import { getSimTime, getWallClkDelay, TimeMode } from "../statecharts/time";
|
||||||
import { BottomPanel } from "./BottomPanel";
|
import { BottomPanel } from "./BottomPanel";
|
||||||
import { usePersistentState } from "./persistent_state";
|
|
||||||
import { PersistentDetails } from "./PersistentDetails";
|
import { PersistentDetails } from "./PersistentDetails";
|
||||||
|
import { digitalWatchPlant } from "./Plant/DigitalWatch/DigitalWatch";
|
||||||
import { dummyPlant } from "./Plant/Dummy/Dummy";
|
import { dummyPlant } from "./Plant/Dummy/Dummy";
|
||||||
import { microwavePlant } from "./Plant/Microwave/Microwave";
|
import { microwavePlant } from "./Plant/Microwave/Microwave";
|
||||||
import { Plant } from "./Plant/Plant";
|
import { Plant } from "./Plant/Plant";
|
||||||
|
import { trafficLightPlant } from "./Plant/TrafficLight/TrafficLight";
|
||||||
import { RTHistory } from "./RTHistory";
|
import { RTHistory } from "./RTHistory";
|
||||||
import { ShowAST, ShowInputEvents, ShowInternalEvents, ShowOutputEvents } from "./ShowAST";
|
import { ShowAST, ShowInputEvents, ShowInternalEvents, ShowOutputEvents } from "./ShowAST";
|
||||||
|
import { InsertMode } from "./TopPanel/InsertModes";
|
||||||
import { TopPanel } from "./TopPanel/TopPanel";
|
import { TopPanel } from "./TopPanel/TopPanel";
|
||||||
import { VisualEditor, VisualEditorState } from "./VisualEditor/VisualEditor";
|
import { VisualEditor, VisualEditorState } from "./VisualEditor/VisualEditor";
|
||||||
import { digitalWatchPlant } from "./Plant/DigitalWatch/DigitalWatch";
|
import { checkProperty, PropertyCheckResult } from "./check_property";
|
||||||
import { useEditor as useEditor } from "./useEditor";
|
import { usePersistentState } from "./persistent_state";
|
||||||
import { InsertMode } from "./TopPanel/InsertModes";
|
import { useEditor } from "./useEditor";
|
||||||
import { Statechart } from "@/statecharts/abstract_syntax";
|
|
||||||
|
|
||||||
export type EditHistory = {
|
export type EditHistory = {
|
||||||
current: VisualEditorState,
|
current: VisualEditorState,
|
||||||
|
|
@ -30,11 +37,13 @@ export type EditHistory = {
|
||||||
future: VisualEditorState[],
|
future: VisualEditorState[],
|
||||||
}
|
}
|
||||||
|
|
||||||
const plants: [string, Plant<any>][] = [
|
type UniversalPlantState = {[property: string]: boolean|number};
|
||||||
|
|
||||||
|
const plants: [string, Plant<any, UniversalPlantState>][] = [
|
||||||
["dummy", dummyPlant],
|
["dummy", dummyPlant],
|
||||||
["microwave", microwavePlant],
|
["microwave", microwavePlant as unknown as Plant<any, UniversalPlantState>],
|
||||||
["digital watch", digitalWatchPlant],
|
["digital watch", digitalWatchPlant as unknown as Plant<any, UniversalPlantState>],
|
||||||
["traffic light", trafficLightPlant],
|
["traffic light", trafficLightPlant as unknown as Plant<any, UniversalPlantState>],
|
||||||
]
|
]
|
||||||
|
|
||||||
export type TraceItemError = {
|
export type TraceItemError = {
|
||||||
|
|
@ -46,6 +55,7 @@ export type TraceItemError = {
|
||||||
type CoupledState = {
|
type CoupledState = {
|
||||||
sc: BigStep,
|
sc: BigStep,
|
||||||
plant: BigStep,
|
plant: BigStep,
|
||||||
|
// plantCleanState: {[prop: string]: boolean|number},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TraceItem =
|
export type TraceItem =
|
||||||
|
|
@ -53,10 +63,9 @@ export type TraceItem =
|
||||||
| { kind: "bigstep", simtime: number, cause: string, state: CoupledState, outputEvents: RaisedEvent[] };
|
| { kind: "bigstep", simtime: number, cause: string, state: CoupledState, outputEvents: RaisedEvent[] };
|
||||||
|
|
||||||
export type TraceState = {
|
export type TraceState = {
|
||||||
// executor: TimedReactive<CoupledState>,
|
|
||||||
trace: [TraceItem, ...TraceItem[]], // non-empty
|
trace: [TraceItem, ...TraceItem[]], // non-empty
|
||||||
idx: number,
|
idx: number,
|
||||||
}; // <-- null if there is no trace
|
};
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const [insertMode, setInsertMode] = usePersistentState<InsertMode>("insertMode", "and");
|
const [insertMode, setInsertMode] = usePersistentState<InsertMode>("insertMode", "and");
|
||||||
|
|
@ -94,7 +103,7 @@ export function App() {
|
||||||
message: currentTraceItem.error.message,
|
message: currentTraceItem.error.message,
|
||||||
shapeUid: currentTraceItem.error.highlight[0],
|
shapeUid: currentTraceItem.error.highlight[0],
|
||||||
}] : [],
|
}] : [],
|
||||||
]
|
];
|
||||||
|
|
||||||
const {makeCheckPoint, onRedo, onUndo, onRotate} = useEditor(editorState, setEditHistory);
|
const {makeCheckPoint, onRedo, onUndo, onRotate} = useEditor(editorState, setEditHistory);
|
||||||
|
|
||||||
|
|
@ -108,23 +117,7 @@ export function App() {
|
||||||
}
|
}
|
||||||
}, [refRightSideBar.current, autoScroll]);
|
}, [refRightSideBar.current, autoScroll]);
|
||||||
|
|
||||||
// const plantConns = ast && ({
|
// coupled execution
|
||||||
// inputEvents: {
|
|
||||||
// // all SC inputs are directly triggerable from outside
|
|
||||||
// ...exposeStatechartInputs(ast, "sc", (eventName: string) => "debug."+eventName),
|
|
||||||
|
|
||||||
// ...Object.fromEntries(plant.uiEvents.map(e => {
|
|
||||||
// const globalName = "PLANT_UI_"+e.event;
|
|
||||||
// if (plant.inputEvents.some(f => f.event === e.event)) {
|
|
||||||
// return [globalName, {kind: "model", model: 'plant', eventName: e.event}];
|
|
||||||
// }
|
|
||||||
// if (ast.inputEvents.some(f => f.event === e.event)) {
|
|
||||||
// return [globalName, {kind: "model", model: 'sc', eventName: e.event}];
|
|
||||||
// }
|
|
||||||
// }).filter(entry => entry !== undefined)),
|
|
||||||
// },
|
|
||||||
// outputEvents: {}, //autoConnect(ast, "sc", plant, "plant"),
|
|
||||||
// }) as Conns;
|
|
||||||
const cE = useMemo(() => ast && coupledExecution({
|
const cE = useMemo(() => ast && coupledExecution({
|
||||||
sc: statechartExecution(ast),
|
sc: statechartExecution(ast),
|
||||||
plant: plant.execution,
|
plant: plant.execution,
|
||||||
|
|
@ -276,6 +269,27 @@ export function App() {
|
||||||
ast && autoConnect && autoDetectConns(ast, plant, setPlantConns);
|
ast && autoConnect && autoDetectConns(ast, plant, setPlantConns);
|
||||||
}, [ast, plant, autoConnect]);
|
}, [ast, plant, autoConnect]);
|
||||||
|
|
||||||
|
const [properties, setProperties] = usePersistentState<string[]>("properties", []);
|
||||||
|
const [propertyResults, setPropertyResults] = useState<PropertyCheckResult[] | null>(null);
|
||||||
|
const [activeProperty, setActiveProperty] = usePersistentState<number>("activeProperty", 0);
|
||||||
|
|
||||||
|
// if some properties change, re-evaluate them:
|
||||||
|
useEffect(() => {
|
||||||
|
let timeout: NodeJS.Timeout;
|
||||||
|
if (trace) {
|
||||||
|
setPropertyResults(null);
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
Promise.all(properties.map((property, i) => {
|
||||||
|
return checkProperty(plant, property, trace.trace);
|
||||||
|
}))
|
||||||
|
.then(results => {
|
||||||
|
setPropertyResults(results);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}, [properties, trace, plant]);
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
|
|
||||||
{/* Modal dialog */}
|
{/* Modal dialog */}
|
||||||
|
|
@ -326,12 +340,14 @@ export function App() {
|
||||||
className={showExecutionTrace ? "shadowBelow" : ""}
|
className={showExecutionTrace ? "shadowBelow" : ""}
|
||||||
style={{flex: '0 0 content', backgroundColor: ''}}
|
style={{flex: '0 0 content', backgroundColor: ''}}
|
||||||
>
|
>
|
||||||
|
{/* State tree */}
|
||||||
<PersistentDetails localStorageKey="showStateTree" initiallyOpen={true}>
|
<PersistentDetails localStorageKey="showStateTree" initiallyOpen={true}>
|
||||||
<summary>state tree</summary>
|
<summary>state tree</summary>
|
||||||
<ul>
|
<ul>
|
||||||
{ast && <ShowAST {...{...ast, trace, highlightActive}}/>}
|
{ast && <ShowAST {...{...ast, trace, highlightActive}}/>}
|
||||||
</ul>
|
</ul>
|
||||||
</PersistentDetails>
|
</PersistentDetails>
|
||||||
|
{/* Input events */}
|
||||||
<PersistentDetails localStorageKey="showInputEvents" initiallyOpen={true}>
|
<PersistentDetails localStorageKey="showInputEvents" initiallyOpen={true}>
|
||||||
<summary>input events</summary>
|
<summary>input events</summary>
|
||||||
{ast && <ShowInputEvents
|
{ast && <ShowInputEvents
|
||||||
|
|
@ -340,14 +356,17 @@ export function App() {
|
||||||
disabled={trace===null || trace.trace[trace.idx].kind === "error"}
|
disabled={trace===null || trace.trace[trace.idx].kind === "error"}
|
||||||
showKeys={showKeys}/>}
|
showKeys={showKeys}/>}
|
||||||
</PersistentDetails>
|
</PersistentDetails>
|
||||||
|
{/* Internal events */}
|
||||||
<PersistentDetails localStorageKey="showInternalEvents" initiallyOpen={true}>
|
<PersistentDetails localStorageKey="showInternalEvents" initiallyOpen={true}>
|
||||||
<summary>internal events</summary>
|
<summary>internal events</summary>
|
||||||
{ast && <ShowInternalEvents internalEvents={ast.internalEvents}/>}
|
{ast && <ShowInternalEvents internalEvents={ast.internalEvents}/>}
|
||||||
</PersistentDetails>
|
</PersistentDetails>
|
||||||
|
{/* Output events */}
|
||||||
<PersistentDetails localStorageKey="showOutputEvents" initiallyOpen={true}>
|
<PersistentDetails localStorageKey="showOutputEvents" initiallyOpen={true}>
|
||||||
<summary>output events</summary>
|
<summary>output events</summary>
|
||||||
{ast && <ShowOutputEvents outputEvents={ast.outputEvents}/>}
|
{ast && <ShowOutputEvents outputEvents={ast.outputEvents}/>}
|
||||||
</PersistentDetails>
|
</PersistentDetails>
|
||||||
|
{/* Plant */}
|
||||||
<PersistentDetails localStorageKey="showPlant" initiallyOpen={true}>
|
<PersistentDetails localStorageKey="showPlant" initiallyOpen={true}>
|
||||||
<summary>plant</summary>
|
<summary>plant</summary>
|
||||||
<select
|
<select
|
||||||
|
|
@ -360,10 +379,11 @@ export function App() {
|
||||||
</select>
|
</select>
|
||||||
<br/>
|
<br/>
|
||||||
{/* Render plant */}
|
{/* Render plant */}
|
||||||
{<plant.render state={plantState} speed={speed}
|
{<plant.render state={plant.cleanupState(plantState)} speed={speed}
|
||||||
raiseUIEvent={e => onRaise("plant.ui."+e.name, e.param)}
|
raiseUIEvent={e => onRaise("plant.ui."+e.name, e.param)}
|
||||||
/>}
|
/>}
|
||||||
</PersistentDetails>
|
</PersistentDetails>
|
||||||
|
{/* Connections */}
|
||||||
<PersistentDetails localStorageKey="showConnEditor" initiallyOpen={false}>
|
<PersistentDetails localStorageKey="showConnEditor" initiallyOpen={false}>
|
||||||
<summary>connections</summary>
|
<summary>connections</summary>
|
||||||
<button title="auto-connect (name-based)" className={autoConnect?"active":""}
|
<button title="auto-connect (name-based)" className={autoConnect?"active":""}
|
||||||
|
|
@ -372,6 +392,36 @@ export function App() {
|
||||||
</button>
|
</button>
|
||||||
{ast && ConnEditor(ast, plant, plantConns, setPlantConns)}
|
{ast && ConnEditor(ast, plant, plantConns, setPlantConns)}
|
||||||
</PersistentDetails>
|
</PersistentDetails>
|
||||||
|
{/* Properties */}
|
||||||
|
<PersistentDetails localStorageKey="showProperty" initiallyOpen={false}>
|
||||||
|
<summary>properties</summary>
|
||||||
|
<div className="toolbar">
|
||||||
|
<button title="add property" onClick={() => setProperties(properties => [...properties, ""])}>
|
||||||
|
<AddIcon fontSize="small"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{properties.map((property, i) => {
|
||||||
|
const result = propertyResults && propertyResults[i];
|
||||||
|
let violated = null, propertyError = null;
|
||||||
|
if (result) {
|
||||||
|
violated = result[0] && !result[0][0].satisfied;
|
||||||
|
propertyError = result[1];
|
||||||
|
}
|
||||||
|
return <div style={{width:'100%'}} key={i} className="toolbar">
|
||||||
|
<div className={"status" + (violated === null ? "" : (violated ? " violated" : " satisfied"))}></div>
|
||||||
|
|
||||||
|
<button title="see in trace (below)" className={activeProperty === i ? "active" : ""} onClick={() => setActiveProperty(i)}>
|
||||||
|
<VisibilityIcon fontSize="small"/>
|
||||||
|
</button>
|
||||||
|
<input type="text" style={{width:'calc(97% - 70px)'}} value={property} onChange={e => setProperties(properties => properties.toSpliced(i, 1, e.target.value))}/>
|
||||||
|
<button title="delete this property" onClick={() => setProperties(properties => properties.toSpliced(i, 1))}>
|
||||||
|
<DeleteOutlineIcon fontSize="small"/>
|
||||||
|
</button>
|
||||||
|
{propertyError && <div style={{color: 'var(--error-color)'}}>{propertyError}</div>}
|
||||||
|
</div>;
|
||||||
|
})}
|
||||||
|
</PersistentDetails>
|
||||||
|
{/* Traces */}
|
||||||
|
|
||||||
<details open={showExecutionTrace} onToggle={e => setShowExecutionTrace(e.newState === "open")}><summary>execution trace</summary>
|
<details open={showExecutionTrace} onToggle={e => setShowExecutionTrace(e.newState === "open")}><summary>execution trace</summary>
|
||||||
<input id="checkbox-show-plant-items" type="checkbox" checked={showPlantTrace} onChange={e => setShowPlantTrace(e.target.checked)}/>
|
<input id="checkbox-show-plant-items" type="checkbox" checked={showPlantTrace} onChange={e => setShowPlantTrace(e.target.checked)}/>
|
||||||
|
|
@ -390,7 +440,8 @@ export function App() {
|
||||||
// minHeight: '75%', // <-- allows us to always scroll down the sidebar far enough such that the execution history is enough in view
|
// minHeight: '75%', // <-- allows us to always scroll down the sidebar far enough such that the execution history is enough in view
|
||||||
}}>
|
}}>
|
||||||
<div ref={refRightSideBar}>
|
<div ref={refRightSideBar}>
|
||||||
{ast && <RTHistory {...{ast, trace, setTrace, setTime, showPlantTrace}}/>}
|
{ast && <RTHistory {...{ast, trace, setTrace, setTime, showPlantTrace,
|
||||||
|
propertyTrace: propertyResults && propertyResults[activeProperty] && propertyResults[activeProperty][0] || []}}/>}
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
<div style={{flex: '0 0 content'}}>
|
<div style={{flex: '0 0 content'}}>
|
||||||
|
|
@ -407,32 +458,7 @@ export function App() {
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ShowEventDestination(dst: EventDestination) {
|
function autoDetectConns(ast: Statechart, plant: Plant<any, any>, setPlantConns: Dispatch<SetStateAction<Conns>>) {
|
||||||
if (dst.kind === "model") {
|
|
||||||
return <>{dst.model}.{dst.eventName}</>;
|
|
||||||
}
|
|
||||||
else if (dst.kind === "output") {
|
|
||||||
return <>{dst.eventName}</>;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return <>🗑</>; // <-- garbage can icon
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ShowConns({inputEvents, outputEvents}: Conns) {
|
|
||||||
return <div>
|
|
||||||
{/* <div style={{color: "grey"}}>
|
|
||||||
{Object.entries(inputEvents).map(([eventName, destination]) => <div>{eventName} → <ShowEventDestination {...destination}/></div>)}
|
|
||||||
</div>
|
|
||||||
{Object.entries(outputEvents).map(([modelName, mapping]) => <>{Object.entries(mapping).map(([eventName, destination]) => <div>{modelName}.{eventName} → <ShowEventDestination {...destination}/></div>)}</>)} */}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
|
|
||||||
import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
|
|
||||||
import { trafficLightPlant } from "./Plant/TrafficLight/TrafficLight";
|
|
||||||
|
|
||||||
function autoDetectConns(ast: Statechart, plant: Plant<any>, setPlantConns: Dispatch<SetStateAction<Conns>>) {
|
|
||||||
for (const {event: a} of plant.uiEvents) {
|
for (const {event: a} of plant.uiEvents) {
|
||||||
for (const {event: b} of plant.inputEvents) {
|
for (const {event: b} of plant.inputEvents) {
|
||||||
if (a === b) {
|
if (a === b) {
|
||||||
|
|
@ -462,7 +488,8 @@ function autoDetectConns(ast: Statechart, plant: Plant<any>, setPlantConns: Disp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConnEditor(ast: Statechart, plant: Plant<any>, plantConns: Conns, setPlantConns: Dispatch<SetStateAction<Conns>>) {
|
|
||||||
|
function ConnEditor(ast: Statechart, plant: Plant<any, any>, plantConns: Conns, setPlantConns: Dispatch<SetStateAction<Conns>>) {
|
||||||
const plantInputs = <>{plant.inputEvents.map(e => <option key={'plant.'+e.event} value={'plant.'+e.event}>plant.{e.event}</option>)}</>
|
const plantInputs = <>{plant.inputEvents.map(e => <option key={'plant.'+e.event} value={'plant.'+e.event}>plant.{e.event}</option>)}</>
|
||||||
const scInputs = <>{ast.inputEvents.map(e => <option key={'sc.'+e.event} value={'sc.'+e.event}>sc.{e.event}</option>)}</>;
|
const scInputs = <>{ast.inputEvents.map(e => <option key={'sc.'+e.event} value={'sc.'+e.event}>sc.{e.event}</option>)}</>;
|
||||||
return <>
|
return <>
|
||||||
|
|
@ -473,7 +500,8 @@ function ConnEditor(ast: Statechart, plant: Plant<any>, plantConns: Conns, setPl
|
||||||
<select id={`select-dst-sc-${e}`}
|
<select id={`select-dst-sc-${e}`}
|
||||||
style={{width:'50%'}}
|
style={{width:'50%'}}
|
||||||
value={plantConns['sc.'+e]?.join('.')}
|
value={plantConns['sc.'+e]?.join('.')}
|
||||||
onChange={domEvent => setPlantConns(conns => ({...conns, [`sc.${e}`]: domEvent.target.value.split('.') as [string,string]}))}>
|
// @ts-ignore
|
||||||
|
onChange={domEvent => setPlantConns(conns => ({...conns, [`sc.${e}`]: (domEvent.target.value === "" ? undefined : (domEvent.target.value.split('.') as [string,string]))}))}>
|
||||||
<option key="none" value=""></option>
|
<option key="none" value=""></option>
|
||||||
{plantInputs}
|
{plantInputs}
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -485,7 +513,8 @@ function ConnEditor(ast: Statechart, plant: Plant<any>, plantConns: Conns, setPl
|
||||||
<select id={`select-dst-plant-${e.event}`}
|
<select id={`select-dst-plant-${e.event}`}
|
||||||
style={{width:'50%'}}
|
style={{width:'50%'}}
|
||||||
value={plantConns['plant.'+e.event]?.join('.')}
|
value={plantConns['plant.'+e.event]?.join('.')}
|
||||||
onChange={(domEvent => setPlantConns(conns => ({...conns, [`plant.${e.event}`]: domEvent.target.value.split('.') as [string,string]})))}>
|
// @ts-ignore
|
||||||
|
onChange={(domEvent => setPlantConns(conns => ({...conns, [`plant.${e.event}`]: (domEvent.target.value === "" ? undefined : (domEvent.target.value.split('.') as [string,string]))})))}>
|
||||||
<option key="none" value=""></option>
|
<option key="none" value=""></option>
|
||||||
{scInputs}
|
{scInputs}
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -497,7 +526,8 @@ function ConnEditor(ast: Statechart, plant: Plant<any>, plantConns: Conns, setPl
|
||||||
<select id={`select-dst-plant-ui-${e.event}`}
|
<select id={`select-dst-plant-ui-${e.event}`}
|
||||||
style={{width:'50%'}}
|
style={{width:'50%'}}
|
||||||
value={plantConns['plant.ui.'+e.event]?.join('.')}
|
value={plantConns['plant.ui.'+e.event]?.join('.')}
|
||||||
onChange={domEvent => setPlantConns(conns => ({...conns, [`plant.ui.${e.event}`]: domEvent.target.value.split('.') as [string,string]}))}>
|
// @ts-ignore
|
||||||
|
onChange={domEvent => setPlantConns(conns => ({...conns, [`plant.ui.${e.event}`]: (domEvent.target.value === "" ? undefined : (domEvent.target.value.split('.') as [string,string]))}))}>
|
||||||
<option key="none" value=""></option>
|
<option key="none" value=""></option>
|
||||||
{scInputs}
|
{scInputs}
|
||||||
{plantInputs}
|
{plantInputs}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
||||||
import { detectConnections } from "@/statecharts/detect_connections";
|
import { detectConnections } from "@/statecharts/detect_connections";
|
||||||
import { parseStatechart } from "@/statecharts/parser";
|
import { parseStatechart } from "@/statecharts/parser";
|
||||||
import { RT_Statechart } from "@/statecharts/runtime_types";
|
import { RT_Statechart } from "@/statecharts/runtime_types";
|
||||||
import { useEffect } from "react";
|
import { memo, useEffect } from "react";
|
||||||
import { makeStatechartPlant, PlantRenderProps } from "../Plant";
|
import { makeStatechartPlant, PlantRenderProps } from "../Plant";
|
||||||
|
|
||||||
import dwatchConcreteSyntax from "./model.json";
|
import dwatchConcreteSyntax from "./model.json";
|
||||||
|
|
@ -12,6 +12,7 @@ import digitalFont from "./digital-font.ttf";
|
||||||
import "./DigitalWatch.css";
|
import "./DigitalWatch.css";
|
||||||
import imgNote from "./noteSmall.png";
|
import imgNote from "./noteSmall.png";
|
||||||
import imgWatch from "./watch.png";
|
import imgWatch from "./watch.png";
|
||||||
|
import { objectsEqual } from "@/util/util";
|
||||||
|
|
||||||
export const [dwatchAbstractSyntax, dwatchErrors] = parseStatechart(dwatchConcreteSyntax as ConcreteSyntax, detectConnections(dwatchConcreteSyntax as ConcreteSyntax));
|
export const [dwatchAbstractSyntax, dwatchErrors] = parseStatechart(dwatchConcreteSyntax as ConcreteSyntax, detectConnections(dwatchConcreteSyntax as ConcreteSyntax));
|
||||||
|
|
||||||
|
|
@ -20,33 +21,66 @@ if (dwatchErrors.length > 0) {
|
||||||
throw new Error("there were errors parsing dwatch plant model. see console.")
|
throw new Error("there were errors parsing dwatch plant model. see console.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DigitalWatchPlantState = {
|
||||||
|
lightOn: boolean,
|
||||||
|
beep: boolean,
|
||||||
|
alarmOn: boolean,
|
||||||
|
displayingTime: boolean,
|
||||||
|
displayingAlarm: boolean,
|
||||||
|
displayingChrono: boolean,
|
||||||
|
hideH: boolean,
|
||||||
|
hideM: boolean,
|
||||||
|
hideS: boolean,
|
||||||
|
h: number,
|
||||||
|
m: number,
|
||||||
|
s: number,
|
||||||
|
ah: number,
|
||||||
|
am: number,
|
||||||
|
as: number,
|
||||||
|
cm: number,
|
||||||
|
cs: number,
|
||||||
|
chs: number,
|
||||||
|
|
||||||
|
// these properties are true for as long as the mouse button is down:
|
||||||
|
topLeftPressed: boolean,
|
||||||
|
topRightPressed: boolean,
|
||||||
|
bottomRightPressed: boolean,
|
||||||
|
bottomLeftPressed: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
function dwatchConfigToState(rtConfig: RT_Statechart): DigitalWatchPlantState {
|
||||||
|
return {
|
||||||
|
lightOn: rtConfig.mode.has(dwatchAbstractSyntax.label2State.get("lightOn")!.uid),
|
||||||
|
beep: rtConfig.mode.has(dwatchAbstractSyntax.label2State.get("beep")!.uid),
|
||||||
|
alarmOn: rtConfig.environment.get("alarm"),
|
||||||
|
displayingTime: rtConfig.mode.has(dwatchAbstractSyntax.label2State.get("displayingTime")!.uid),
|
||||||
|
displayingAlarm: rtConfig.mode.has(dwatchAbstractSyntax.label2State.get("displayingAlarm")!.uid),
|
||||||
|
displayingChrono: rtConfig.mode.has(dwatchAbstractSyntax.label2State.get("displayingChrono")!.uid),
|
||||||
|
hideH: rtConfig.mode.has(dwatchAbstractSyntax.label2State.get("hideH")!.uid),
|
||||||
|
hideM: rtConfig.mode.has(dwatchAbstractSyntax.label2State.get("hideM")!.uid),
|
||||||
|
hideS: rtConfig.mode.has(dwatchAbstractSyntax.label2State.get("hideS")!.uid),
|
||||||
|
h: rtConfig.environment.get("h"),
|
||||||
|
m: rtConfig.environment.get("m"),
|
||||||
|
s: rtConfig.environment.get("s"),
|
||||||
|
ah: rtConfig.environment.get("ah"),
|
||||||
|
am: rtConfig.environment.get("am"),
|
||||||
|
as: rtConfig.environment.get("as"),
|
||||||
|
cm: rtConfig.environment.get("cm"),
|
||||||
|
cs: rtConfig.environment.get("cs"),
|
||||||
|
chs: rtConfig.environment.get("chs"),
|
||||||
|
|
||||||
|
topLeftPressed: rtConfig.mode.has(dwatchAbstractSyntax.label2State.get("topLeftPressed")!.uid),
|
||||||
|
topRightPressed: rtConfig.mode.has(dwatchAbstractSyntax.label2State.get("topRightPressed")!.uid),
|
||||||
|
bottomRightPressed: rtConfig.mode.has(dwatchAbstractSyntax.label2State.get("bottomRightPressed")!.uid),
|
||||||
|
bottomLeftPressed: rtConfig.mode.has(dwatchAbstractSyntax.label2State.get("bottomLeftPressed")!.uid),
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const twoDigits = (n: number) => ("0"+n.toString()).slice(-2);
|
const twoDigits = (n: number) => ("0"+n.toString()).slice(-2);
|
||||||
|
|
||||||
export function DigitalWatch({state, speed, raiseUIEvent}: PlantRenderProps<RT_Statechart>) {
|
export const DigitalWatch = memo(function DigitalWatch({state: {displayingTime, displayingAlarm, displayingChrono, lightOn, alarmOn, beep, h, m, s, ah, am, as, cm, cs, chs, hideH, hideM, hideS}, speed, raiseUIEvent}: PlantRenderProps<DigitalWatchPlantState>) {
|
||||||
const displayingTime = state.mode.has("625");
|
|
||||||
const displayingAlarm = state.mode.has("626");
|
|
||||||
const displayingChrono = state.mode.has("624");
|
|
||||||
|
|
||||||
const lightOn = state.mode.has("630");
|
|
||||||
|
|
||||||
const alarm = state.environment.get("alarm");
|
|
||||||
|
|
||||||
const h = state.environment.get("h");
|
|
||||||
const m = state.environment.get("m");
|
|
||||||
const s = state.environment.get("s");
|
|
||||||
const ah = state.environment.get("ah");
|
|
||||||
const am = state.environment.get("am");
|
|
||||||
const as = state.environment.get("as");
|
|
||||||
const cm = state.environment.get("cm");
|
|
||||||
const cs = state.environment.get("cs");
|
|
||||||
const chs = state.environment.get("chs");
|
|
||||||
|
|
||||||
const hideH = state.mode.has("628");
|
|
||||||
const hideM = state.mode.has("633");
|
|
||||||
const hideS = state.mode.has("627");
|
|
||||||
|
|
||||||
// console.log({cm,cs,chs});
|
|
||||||
|
|
||||||
let hhmmss;
|
let hhmmss;
|
||||||
if (displayingTime) {
|
if (displayingTime) {
|
||||||
|
|
@ -63,8 +97,6 @@ export function DigitalWatch({state, speed, raiseUIEvent}: PlantRenderProps<RT_S
|
||||||
|
|
||||||
preloadAudio(sndBeep);
|
preloadAudio(sndBeep);
|
||||||
|
|
||||||
const beep = state.mode.has("632");
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (beep) {
|
if (beep) {
|
||||||
playSound(sndBeep, false);
|
playSound(sndBeep, false);
|
||||||
|
|
@ -87,40 +119,41 @@ export function DigitalWatch({state, speed, raiseUIEvent}: PlantRenderProps<RT_S
|
||||||
<text x="111" y="126" dominantBaseline="middle" textAnchor="middle" fontFamily="digital-font" fontSize={28} style={{whiteSpace:'preserve'}}>{hhmmss}</text>
|
<text x="111" y="126" dominantBaseline="middle" textAnchor="middle" fontFamily="digital-font" fontSize={28} style={{whiteSpace:'preserve'}}>{hhmmss}</text>
|
||||||
|
|
||||||
<rect className="watchButtonHelper" x={0} y={54} width={24} height={24}
|
<rect className="watchButtonHelper" x={0} y={54} width={24} height={24}
|
||||||
onMouseDown={() => raiseUIEvent({name: "topLeftPressed"})}
|
onMouseDown={() => raiseUIEvent({name: "topLeftMouseDown"})}
|
||||||
onMouseUp={() => raiseUIEvent({name: "topLeftReleased"})}
|
onMouseUp={() => raiseUIEvent({name: "topLeftMouseUp"})}
|
||||||
/>
|
/>
|
||||||
<rect className="watchButtonHelper" x={198} y={54} width={24} height={24}
|
<rect className="watchButtonHelper" x={198} y={54} width={24} height={24}
|
||||||
onMouseDown={() => raiseUIEvent({name: "topRightPressed"})}
|
onMouseDown={() => raiseUIEvent({name: "topRightMouseDown"})}
|
||||||
onMouseUp={() => raiseUIEvent({name: "topRightReleased"})}
|
onMouseUp={() => raiseUIEvent({name: "topRightMouseUp"})}
|
||||||
/>
|
/>
|
||||||
<rect className="watchButtonHelper" x={0} y={154} width={24} height={24}
|
<rect className="watchButtonHelper" x={0} y={154} width={24} height={24}
|
||||||
onMouseDown={() => raiseUIEvent({name: "bottomLeftPressed"})}
|
onMouseDown={() => raiseUIEvent({name: "bottomLeftMouseDown"})}
|
||||||
onMouseUp={() => raiseUIEvent({name: "bottomLeftReleased"})}
|
onMouseUp={() => raiseUIEvent({name: "bottomLeftMouseUp"})}
|
||||||
/>
|
/>
|
||||||
<rect className="watchButtonHelper" x={198} y={154} width={24} height={24}
|
<rect className="watchButtonHelper" x={198} y={154} width={24} height={24}
|
||||||
onMouseDown={() => raiseUIEvent({name: "bottomRightPressed"})}
|
onMouseDown={() => raiseUIEvent({name: "bottomRightMouseDown"})}
|
||||||
onMouseUp={() => raiseUIEvent({name: "bottomRightReleased"})}
|
onMouseUp={() => raiseUIEvent({name: "bottomRightMouseUp"})}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{alarm &&
|
{alarmOn &&
|
||||||
<image x="54" y="98" xlinkHref={imgNote} />
|
<image x="54" y="98" xlinkHref={imgNote} />
|
||||||
}
|
}
|
||||||
</svg>
|
</svg>
|
||||||
</>;
|
</>;
|
||||||
}
|
}, objectsEqual);
|
||||||
|
|
||||||
export const digitalWatchPlant = makeStatechartPlant({
|
export const digitalWatchPlant = makeStatechartPlant({
|
||||||
ast: dwatchAbstractSyntax,
|
ast: dwatchAbstractSyntax,
|
||||||
|
cleanupState: dwatchConfigToState,
|
||||||
render: DigitalWatch,
|
render: DigitalWatch,
|
||||||
uiEvents: [
|
uiEvents: [
|
||||||
{ kind: "event", event: "topLeftPressed" },
|
{ kind: "event", event: "topLeftMouseDown" },
|
||||||
{ kind: "event", event: "topRightPressed" },
|
{ kind: "event", event: "topRightMouseDown" },
|
||||||
{ kind: "event", event: "bottomRightPressed" },
|
{ kind: "event", event: "bottomRightMouseDown" },
|
||||||
{ kind: "event", event: "bottomLeftPressed" },
|
{ kind: "event", event: "bottomLeftMouseDown" },
|
||||||
{ kind: "event", event: "topLeftReleased" },
|
{ kind: "event", event: "topLeftMouseUp" },
|
||||||
{ kind: "event", event: "topRightReleased" },
|
{ kind: "event", event: "topRightMouseUp" },
|
||||||
{ kind: "event", event: "bottomRightReleased" },
|
{ kind: "event", event: "bottomRightMouseUp" },
|
||||||
{ kind: "event", event: "bottomLeftReleased" },
|
{ kind: "event", event: "bottomLeftMouseUp" },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,17 +1,18 @@
|
||||||
import { Plant } from "../Plant";
|
import { Plant } from "../Plant";
|
||||||
import { TimedReactive } from "@/statecharts/timed_reactive";
|
import { TimedReactive } from "@/statecharts/timed_reactive";
|
||||||
|
|
||||||
export const dummyExecution: TimedReactive<null> = {
|
export const dummyExecution: TimedReactive<{}> = {
|
||||||
initial: () => [[], null],
|
initial: () => [[], {}],
|
||||||
timeAdvance: () => Infinity,
|
timeAdvance: () => Infinity,
|
||||||
intTransition: () => { throw new Error("dummy never makes intTransition"); },
|
intTransition: () => { throw new Error("dummy never makes intTransition"); },
|
||||||
extTransition: () => [[], null],
|
extTransition: () => [[], {}],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dummyPlant: Plant<null> = {
|
export const dummyPlant: Plant<{}, {}> = {
|
||||||
uiEvents: [],
|
uiEvents: [],
|
||||||
inputEvents: [],
|
inputEvents: [],
|
||||||
outputEvents: [],
|
outputEvents: [],
|
||||||
execution: dummyExecution,
|
execution: dummyExecution,
|
||||||
render: (props) => <></>,
|
cleanupState: ({}) => ({}),
|
||||||
}
|
render: ({}) => <></>,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,13 @@ import { memo, useEffect } from "react";
|
||||||
|
|
||||||
import "./Microwave.css";
|
import "./Microwave.css";
|
||||||
import { useAudioContext } from "../../useAudioContext";
|
import { useAudioContext } from "../../useAudioContext";
|
||||||
import { comparePlantRenderProps, makeStatechartPlant, PlantRenderProps, StatechartPlantSpec } from "../Plant";
|
import { makeStatechartPlant, PlantRenderProps, StatechartPlantSpec } from "../Plant";
|
||||||
import { detectConnections } from "@/statecharts/detect_connections";
|
import { detectConnections } from "@/statecharts/detect_connections";
|
||||||
import { parseStatechart } from "@/statecharts/parser";
|
import { parseStatechart } from "@/statecharts/parser";
|
||||||
|
|
||||||
import microwaveConcreteSyntax from "./model.json";
|
import microwaveConcreteSyntax from "./model.json";
|
||||||
import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
||||||
|
import { objectsEqual } from "@/util/util";
|
||||||
|
|
||||||
export const [microwaveAbstractSyntax, microwaveErrors] = parseStatechart(microwaveConcreteSyntax as ConcreteSyntax, detectConnections(microwaveConcreteSyntax as ConcreteSyntax));
|
export const [microwaveAbstractSyntax, microwaveErrors] = parseStatechart(microwaveConcreteSyntax as ConcreteSyntax, detectConnections(microwaveConcreteSyntax as ConcreteSyntax));
|
||||||
|
|
||||||
|
|
@ -27,7 +28,6 @@ if (microwaveErrors.length > 0) {
|
||||||
throw new Error("there were errors parsing microwave plant model. see console.")
|
throw new Error("there were errors parsing microwave plant model. see console.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const imgs = {
|
const imgs = {
|
||||||
"false": { "false": imgSmallClosedOff, "true": imgSmallClosedOn },
|
"false": { "false": imgSmallClosedOff, "true": imgSmallClosedOn },
|
||||||
"true": { "false": imgSmallOpenedOff, "true": imgSmallOpenedOn },
|
"true": { "false": imgSmallOpenedOff, "true": imgSmallOpenedOn },
|
||||||
|
|
@ -47,7 +47,19 @@ const DOOR_Y0 = 68;
|
||||||
const DOOR_WIDTH = 353;
|
const DOOR_WIDTH = 353;
|
||||||
const DOOR_HEIGHT = 217;
|
const DOOR_HEIGHT = 217;
|
||||||
|
|
||||||
export const Microwave = memo(function Microwave({state, speed, raiseUIEvent}: PlantRenderProps<RT_Statechart>) {
|
type MicrowaveState = {
|
||||||
|
bellRinging: boolean,
|
||||||
|
magnetronRunning: boolean,
|
||||||
|
doorOpen: boolean,
|
||||||
|
timeDisplay: number,
|
||||||
|
|
||||||
|
// these booleans are true for as long as the respective button is pressed (i.e., mouse button is down)
|
||||||
|
startPressed: boolean,
|
||||||
|
stopPressed: boolean,
|
||||||
|
incTimePressed: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Microwave = memo(function Microwave({state: {bellRinging, magnetronRunning, doorOpen, timeDisplay}, speed, raiseUIEvent}: PlantRenderProps<MicrowaveState>) {
|
||||||
const [playSound, preloadAudio] = useAudioContext(speed);
|
const [playSound, preloadAudio] = useAudioContext(speed);
|
||||||
|
|
||||||
// preload(imgSmallClosedOff, {as: "image"});
|
// preload(imgSmallClosedOff, {as: "image"});
|
||||||
|
|
@ -58,11 +70,6 @@ export const Microwave = memo(function Microwave({state, speed, raiseUIEvent}: P
|
||||||
preloadAudio(sndRunning);
|
preloadAudio(sndRunning);
|
||||||
preloadAudio(sndBell);
|
preloadAudio(sndBell);
|
||||||
|
|
||||||
const bellRinging = state.mode.has("12");
|
|
||||||
const magnetronRunning = state.mode.has("8");
|
|
||||||
const doorOpen = state.mode.has("7");
|
|
||||||
const timeDisplay = state.environment.get("timeDisplay");
|
|
||||||
|
|
||||||
// a bit hacky: when the bell-state changes to true, we play the bell sound...
|
// a bit hacky: when the bell-state changes to true, we play the bell sound...
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (bellRinging) {
|
if (bellRinging) {
|
||||||
|
|
@ -90,16 +97,16 @@ export const Microwave = memo(function Microwave({state, speed, raiseUIEvent}: P
|
||||||
<image xlinkHref={imgs[doorOpen][magnetronRunning]} width={520} height={348}/>
|
<image xlinkHref={imgs[doorOpen][magnetronRunning]} width={520} height={348}/>
|
||||||
|
|
||||||
<rect className="microwaveButtonHelper" x={START_X0} y={START_Y0} width={BUTTON_WIDTH} height={BUTTON_HEIGHT}
|
<rect className="microwaveButtonHelper" x={START_X0} y={START_Y0} width={BUTTON_WIDTH} height={BUTTON_HEIGHT}
|
||||||
onMouseDown={() => raiseUIEvent({name: "startPressed"})}
|
onMouseDown={() => raiseUIEvent({name: "startMouseDown"})}
|
||||||
onMouseUp={() => raiseUIEvent({name: "startReleased"})}
|
onMouseUp={() => raiseUIEvent({name: "startMouseUp"})}
|
||||||
/>
|
/>
|
||||||
<rect className="microwaveButtonHelper" x={STOP_X0} y={STOP_Y0} width={BUTTON_WIDTH} height={BUTTON_HEIGHT}
|
<rect className="microwaveButtonHelper" x={STOP_X0} y={STOP_Y0} width={BUTTON_WIDTH} height={BUTTON_HEIGHT}
|
||||||
onMouseDown={() => raiseUIEvent({name: "stopPressed"})}
|
onMouseDown={() => raiseUIEvent({name: "stopMouseDown"})}
|
||||||
onMouseUp={() => raiseUIEvent({name: "stopReleased"})}
|
onMouseUp={() => raiseUIEvent({name: "stopMouseUp"})}
|
||||||
/>
|
/>
|
||||||
<rect className="microwaveButtonHelper" x={INCTIME_X0} y={INCTIME_Y0} width={BUTTON_WIDTH} height={BUTTON_HEIGHT}
|
<rect className="microwaveButtonHelper" x={INCTIME_X0} y={INCTIME_Y0} width={BUTTON_WIDTH} height={BUTTON_HEIGHT}
|
||||||
onMouseDown={() => raiseUIEvent({name: "incTimePressed"})}
|
onMouseDown={() => raiseUIEvent({name: "incTimeMouseDown"})}
|
||||||
onMouseUp={() => raiseUIEvent({name: "incTimeReleased"})}
|
onMouseUp={() => raiseUIEvent({name: "incTimeMouseUp"})}
|
||||||
/>
|
/>
|
||||||
<rect className="microwaveDoorHelper"
|
<rect className="microwaveDoorHelper"
|
||||||
x={DOOR_X0} y={DOOR_Y0} width={DOOR_WIDTH} height={DOOR_HEIGHT}
|
x={DOOR_X0} y={DOOR_Y0} width={DOOR_WIDTH} height={DOOR_HEIGHT}
|
||||||
|
|
@ -110,20 +117,31 @@ export const Microwave = memo(function Microwave({state, speed, raiseUIEvent}: P
|
||||||
<text x={472} y={106} textAnchor="end" fontFamily="digital-font" fontSize={24} fill="lightgreen">{timeDisplay}</text>
|
<text x={472} y={106} textAnchor="end" fontFamily="digital-font" fontSize={24} fill="lightgreen">{timeDisplay}</text>
|
||||||
</svg>
|
</svg>
|
||||||
</>;
|
</>;
|
||||||
}, comparePlantRenderProps);
|
}, objectsEqual);
|
||||||
|
|
||||||
const microwavePlantSpec: StatechartPlantSpec = {
|
const microwavePlantSpec: StatechartPlantSpec<MicrowaveState> = {
|
||||||
ast: microwaveAbstractSyntax,
|
ast: microwaveAbstractSyntax,
|
||||||
|
cleanupState: (state: RT_Statechart) => {
|
||||||
|
const bellRinging = state.mode.has(microwaveAbstractSyntax.label2State.get("bell")!.uid);
|
||||||
|
const magnetronRunning = state.mode.has(microwaveAbstractSyntax.label2State.get("Magnetron on")!.uid);
|
||||||
|
const doorOpen = state.mode.has(microwaveAbstractSyntax.label2State.get("Door opened")!.uid);
|
||||||
|
const startPressed = state.mode.has(microwaveAbstractSyntax.label2State.get("startPressed")!.uid);
|
||||||
|
const stopPressed = state.mode.has(microwaveAbstractSyntax.label2State.get("stopPressed")!.uid);
|
||||||
|
const incTimePressed = state.mode.has(microwaveAbstractSyntax.label2State.get("incTimePressed")!.uid);
|
||||||
|
// let startPressed, stopPressed, incTimePressed;
|
||||||
|
const timeDisplay = state.environment.get("timeDisplay");
|
||||||
|
return {bellRinging, magnetronRunning, doorOpen, timeDisplay, startPressed, stopPressed, incTimePressed};
|
||||||
|
},
|
||||||
render: Microwave,
|
render: Microwave,
|
||||||
uiEvents: [
|
uiEvents: [
|
||||||
{kind: "event", event: "doorMouseDown"},
|
{kind: "event", event: "doorMouseDown"},
|
||||||
{kind: "event", event: "doorMouseUp"},
|
{kind: "event", event: "doorMouseUp"},
|
||||||
{kind: "event", event: "startPressed"},
|
{kind: "event", event: "startMouseDown"},
|
||||||
{kind: "event", event: "startReleased"},
|
{kind: "event", event: "startMouseUp"},
|
||||||
{kind: "event", event: "stopPressed"},
|
{kind: "event", event: "stopMouseDown"},
|
||||||
{kind: "event", event: "stopReleased"},
|
{kind: "event", event: "stopMouseUp"},
|
||||||
{kind: "event", event: "incTimePressed"},
|
{kind: "event", event: "incTimeMouseDown"},
|
||||||
{kind: "event", event: "incTimeReleased"},
|
{kind: "event", event: "incTimeMouseUp"},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
BIN
src/App/Plant/Microwave/pictures/microwave.xcf
Normal file
BIN
src/App/Plant/Microwave/pictures/microwave.xcf
Normal file
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 221 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 215 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 235 KiB |
|
|
@ -1,9 +1,9 @@
|
||||||
import { ReactElement, ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { Statechart } from "@/statecharts/abstract_syntax";
|
import { Statechart } from "@/statecharts/abstract_syntax";
|
||||||
import { EventTrigger } from "@/statecharts/label_ast";
|
import { EventTrigger } from "@/statecharts/label_ast";
|
||||||
import { BigStep, RaisedEvent, RT_Statechart } from "@/statecharts/runtime_types";
|
import { BigStep, RaisedEvent, RT_Statechart } from "@/statecharts/runtime_types";
|
||||||
import { statechartExecution, TimedReactive } from "@/statecharts/timed_reactive";
|
import { statechartExecution, TimedReactive } from "@/statecharts/timed_reactive";
|
||||||
import { mapsEqual, setsEqual } from "@/util/util";
|
import { setsEqual } from "@/util/util";
|
||||||
|
|
||||||
export type PlantRenderProps<StateType> = {
|
export type PlantRenderProps<StateType> = {
|
||||||
state: StateType,
|
state: StateType,
|
||||||
|
|
@ -11,18 +11,19 @@ export type PlantRenderProps<StateType> = {
|
||||||
raiseUIEvent: (e: RaisedEvent) => void,
|
raiseUIEvent: (e: RaisedEvent) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Plant<StateType> = {
|
export type Plant<StateType, CleanStateType> = {
|
||||||
uiEvents: EventTrigger[];
|
uiEvents: EventTrigger[];
|
||||||
|
|
||||||
inputEvents: EventTrigger[];
|
inputEvents: EventTrigger[];
|
||||||
outputEvents: EventTrigger[];
|
outputEvents: EventTrigger[];
|
||||||
|
|
||||||
execution: TimedReactive<StateType>;
|
execution: TimedReactive<StateType>;
|
||||||
render: (props: PlantRenderProps<StateType>) => ReactNode;
|
cleanupState: (state: StateType) => CleanStateType;
|
||||||
|
render: (props: PlantRenderProps<CleanStateType>) => ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatically connect Statechart and Plant inputs/outputs if their event names match.
|
// Automatically connect Statechart and Plant inputs/outputs if their event names match.
|
||||||
export function autoConnect(ast: Statechart, scName: string, plant: Plant<any>, plantName: string) {
|
export function autoConnect(ast: Statechart, scName: string, plant: Plant<any, any>, plantName: string) {
|
||||||
const outputs = {
|
const outputs = {
|
||||||
[scName]: {},
|
[scName]: {},
|
||||||
[plantName]: {},
|
[plantName]: {},
|
||||||
|
|
@ -44,7 +45,7 @@ export function autoConnect(ast: Statechart, scName: string, plant: Plant<any>,
|
||||||
return outputs;
|
return outputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function exposePlantInputs(plant: Plant<any>, plantName: string, tfm = (s: string) => s) {
|
export function exposePlantInputs(plant: Plant<any, any>, plantName: string, tfm = (s: string) => s) {
|
||||||
const inputs = {};
|
const inputs = {};
|
||||||
for (const i of plant.inputEvents) {
|
for (const i of plant.inputEvents) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -53,25 +54,27 @@ export function exposePlantInputs(plant: Plant<any>, plantName: string, tfm = (s
|
||||||
return inputs
|
return inputs
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StatechartPlantSpec = {
|
export type StatechartPlantSpec<CleanStateType> = {
|
||||||
uiEvents: EventTrigger[],
|
uiEvents: EventTrigger[],
|
||||||
ast: Statechart,
|
ast: Statechart,
|
||||||
render: (props: PlantRenderProps<RT_Statechart>) => ReactNode,
|
cleanupState: (rtConfig: RT_Statechart) => CleanStateType,
|
||||||
|
render: (props: PlantRenderProps<CleanStateType>) => ReactNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeStatechartPlant({uiEvents, ast, render}: StatechartPlantSpec): Plant<BigStep> {
|
export function makeStatechartPlant<CleanStateType>({uiEvents, ast, cleanupState, render}: StatechartPlantSpec<CleanStateType>): Plant<BigStep, CleanStateType> {
|
||||||
return {
|
return {
|
||||||
uiEvents,
|
uiEvents,
|
||||||
inputEvents: ast.inputEvents,
|
inputEvents: ast.inputEvents,
|
||||||
outputEvents: [...ast.outputEvents].map(e => ({kind: "event" as const, event: e})),
|
outputEvents: [...ast.outputEvents].map(e => ({kind: "event" as const, event: e})),
|
||||||
execution: statechartExecution(ast),
|
execution: statechartExecution(ast),
|
||||||
|
cleanupState,
|
||||||
render,
|
render,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function comparePlantRenderProps(oldProps: PlantRenderProps<RT_Statechart>, newProps: PlantRenderProps<RT_Statechart>) {
|
// export function comparePlantRenderProps(oldProps: PlantRenderProps<RT_Statechart>, newProps: PlantRenderProps<RT_Statechart>) {
|
||||||
return setsEqual(oldProps.state.mode, newProps.state.mode)
|
// return setsEqual(oldProps.state.mode, newProps.state.mode)
|
||||||
&& oldProps.state.environment === newProps.state.environment // <-- could optimize this further
|
// && oldProps.state.environment === newProps.state.environment // <-- could optimize this further
|
||||||
&& oldProps.speed === newProps.speed
|
// && oldProps.speed === newProps.speed
|
||||||
&& oldProps.raiseUIEvent === newProps.raiseUIEvent
|
// && oldProps.raiseUIEvent === newProps.raiseUIEvent
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,11 @@ import trafficLightConcreteSyntax from "./model.json";
|
||||||
import { parseStatechart } from "@/statecharts/parser";
|
import { parseStatechart } from "@/statecharts/parser";
|
||||||
import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
||||||
import { detectConnections } from "@/statecharts/detect_connections";
|
import { detectConnections } from "@/statecharts/detect_connections";
|
||||||
import { comparePlantRenderProps, makeStatechartPlant, PlantRenderProps, StatechartPlantSpec } from "../Plant";
|
import { makeStatechartPlant, PlantRenderProps, StatechartPlantSpec } from "../Plant";
|
||||||
import { RT_Statechart } from "@/statecharts/runtime_types";
|
import { RT_Statechart } from "@/statecharts/runtime_types";
|
||||||
import { useAudioContext } from "@/App/useAudioContext";
|
import { useAudioContext } from "@/App/useAudioContext";
|
||||||
import { memo, useEffect } from "react";
|
import { memo, useEffect } from "react";
|
||||||
|
import { objectsEqual } from "@/util/util";
|
||||||
|
|
||||||
const [trafficLightAbstractSyntax, trafficLightErrors] = parseStatechart(trafficLightConcreteSyntax as ConcreteSyntax, detectConnections(trafficLightConcreteSyntax as ConcreteSyntax));
|
const [trafficLightAbstractSyntax, trafficLightErrors] = parseStatechart(trafficLightConcreteSyntax as ConcreteSyntax, detectConnections(trafficLightConcreteSyntax as ConcreteSyntax));
|
||||||
|
|
||||||
|
|
@ -23,19 +24,20 @@ if (trafficLightErrors.length > 0) {
|
||||||
throw new Error("there were errors parsing traffic light plant model. see console.")
|
throw new Error("there were errors parsing traffic light plant model. see console.")
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TrafficLight = memo(function TrafficLight({state, speed, raiseUIEvent}: PlantRenderProps<RT_Statechart>) {
|
type TrafficLightState = {
|
||||||
|
redOn: boolean,
|
||||||
|
yellowOn: boolean,
|
||||||
|
greenOn: boolean,
|
||||||
|
timerGreen: boolean,
|
||||||
|
timerValue: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TrafficLight = memo(function TrafficLight({state: {redOn, yellowOn, greenOn, timerGreen, timerValue}, speed, raiseUIEvent}: PlantRenderProps<TrafficLightState>) {
|
||||||
// preload(imgBackground, {as: "image"});
|
// preload(imgBackground, {as: "image"});
|
||||||
preload(imgRedOverlay, {as: "image"});
|
preload(imgRedOverlay, {as: "image"});
|
||||||
preload(imgYellowOverlay, {as: "image"});
|
preload(imgYellowOverlay, {as: "image"});
|
||||||
preload(imgGreenOverlay, {as: "image"});
|
preload(imgGreenOverlay, {as: "image"});
|
||||||
|
|
||||||
const redOn = state.mode.has("85");
|
|
||||||
const yellowOn = state.mode.has("87");
|
|
||||||
const greenOn = state.mode.has("89");
|
|
||||||
|
|
||||||
const timerGreen = state.mode.has("137");
|
|
||||||
const timerValue = state.environment.get("t");
|
|
||||||
|
|
||||||
const [playURL, preloadAudio] = useAudioContext(speed);
|
const [playURL, preloadAudio] = useAudioContext(speed);
|
||||||
|
|
||||||
// preloadAudio(sndAtmosphere);
|
// preloadAudio(sndAtmosphere);
|
||||||
|
|
@ -89,10 +91,20 @@ export const TrafficLight = memo(function TrafficLight({state, speed, raiseUIEve
|
||||||
<br/>
|
<br/>
|
||||||
<button onClick={() => raiseUIEvent({name: "policeInterrupt"})}>POLICE INTERRUPT</button>
|
<button onClick={() => raiseUIEvent({name: "policeInterrupt"})}>POLICE INTERRUPT</button>
|
||||||
</>;
|
</>;
|
||||||
}, comparePlantRenderProps);
|
}, (oldProps, newProps) => {
|
||||||
|
return objectsEqual(oldProps, newProps);
|
||||||
|
});
|
||||||
|
|
||||||
const trafficLightPlantSpec: StatechartPlantSpec = {
|
const trafficLightPlantSpec: StatechartPlantSpec<TrafficLightState> = {
|
||||||
ast: trafficLightAbstractSyntax,
|
ast: trafficLightAbstractSyntax,
|
||||||
|
cleanupState: (state: RT_Statechart) => {
|
||||||
|
const redOn = state.mode.has("85");
|
||||||
|
const yellowOn = state.mode.has("87");
|
||||||
|
const greenOn = state.mode.has("89");
|
||||||
|
const timerGreen = state.mode.has("137");
|
||||||
|
const timerValue = state.environment.get("t");
|
||||||
|
return { redOn, yellowOn, greenOn, timerGreen, timerValue };
|
||||||
|
},
|
||||||
render: TrafficLight,
|
render: TrafficLight,
|
||||||
uiEvents: [
|
uiEvents: [
|
||||||
{kind: "event", event: "policeInterrupt"},
|
{kind: "event", event: "policeInterrupt"},
|
||||||
|
|
|
||||||
BIN
src/App/Plant/TrafficLight/close.wav
Normal file
BIN
src/App/Plant/TrafficLight/close.wav
Normal file
Binary file not shown.
BIN
src/App/Plant/TrafficLight/open.wav
Normal file
BIN
src/App/Plant/TrafficLight/open.wav
Normal file
Binary file not shown.
|
|
@ -12,9 +12,30 @@ type RTHistoryProps = {
|
||||||
ast: Statechart,
|
ast: Statechart,
|
||||||
setTime: Dispatch<SetStateAction<TimeMode>>,
|
setTime: Dispatch<SetStateAction<TimeMode>>,
|
||||||
showPlantTrace: boolean,
|
showPlantTrace: boolean,
|
||||||
|
propertyTrace: {timestamp: number, satisfied: boolean}[] | null,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RTHistory({trace, setTrace, ast, setTime, showPlantTrace}: RTHistoryProps) {
|
function lookupPropertyStatus(simtime: number, propertyTrace: {timestamp: number, satisfied: boolean}[], startAt=0): [number, boolean | undefined] {
|
||||||
|
let i = startAt;
|
||||||
|
while (i >= 0 && i < propertyTrace.length) {
|
||||||
|
const {timestamp} = propertyTrace[i];
|
||||||
|
if (timestamp === simtime) {
|
||||||
|
// exact match
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (timestamp > simtime) {
|
||||||
|
i--;
|
||||||
|
// too far
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// continue...
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
i = Math.min(i, propertyTrace.length-1);
|
||||||
|
return [i, propertyTrace[i] && propertyTrace[i].satisfied];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RTHistory({trace, setTrace, ast, setTime, showPlantTrace, propertyTrace}: RTHistoryProps) {
|
||||||
const onMouseDown = useCallback((idx: number, timestamp: number) => {
|
const onMouseDown = useCallback((idx: number, timestamp: number) => {
|
||||||
setTrace(trace => trace && {
|
setTrace(trace => trace && {
|
||||||
...trace,
|
...trace,
|
||||||
|
|
@ -26,6 +47,7 @@ export function RTHistory({trace, setTrace, ast, setTime, showPlantTrace}: RTHis
|
||||||
if (trace === null) {
|
if (trace === null) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
let j = 0;
|
||||||
return trace.trace.map((item, i) => {
|
return trace.trace.map((item, i) => {
|
||||||
const prevItem = trace.trace[i-1];
|
const prevItem = trace.trace[i-1];
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -33,7 +55,16 @@ export function RTHistory({trace, setTrace, ast, setTime, showPlantTrace}: RTHis
|
||||||
if (!showPlantTrace && isPlantStep) {
|
if (!showPlantTrace && isPlantStep) {
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
return <RTHistoryItem ast={ast} idx={i} item={item} prevItem={prevItem} isPlantStep={isPlantStep} active={i === trace.idx} onMouseDown={onMouseDown}/>;
|
let propertyClasses = "status";
|
||||||
|
if (propertyTrace !== null) {
|
||||||
|
let satisfied;
|
||||||
|
[j, satisfied] = lookupPropertyStatus(item.simtime, propertyTrace, j);
|
||||||
|
// console.log(item.simtime, j, propertyTrace[j]);
|
||||||
|
if (satisfied !== null && satisfied !== undefined) {
|
||||||
|
propertyClasses += (satisfied ? " satisfied" : " violated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <RTHistoryItem ast={ast} idx={i} item={item} prevItem={prevItem} isPlantStep={isPlantStep} active={i === trace.idx} onMouseDown={onMouseDown} propertyClasses={propertyClasses} />;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,7 +86,7 @@ function RTEventParam(props: {param?: any}) {
|
||||||
return <>{props.param !== undefined && <>({JSON.stringify(props.param)})</>}</>;
|
return <>{props.param !== undefined && <>({JSON.stringify(props.param)})</>}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RTHistoryItem = memo(function RTHistoryItem({ast, idx, item, prevItem, isPlantStep, active, onMouseDown}: {idx: number, ast: Statechart, item: TraceItem, prevItem?: TraceItem, isPlantStep: boolean, active: boolean, onMouseDown: (idx: number, timestamp: number) => void}) {
|
export const RTHistoryItem = memo(function RTHistoryItem({ast, idx, item, prevItem, isPlantStep, active, onMouseDown, propertyClasses}: {idx: number, ast: Statechart, item: TraceItem, prevItem?: TraceItem, isPlantStep: boolean, active: boolean, onMouseDown: (idx: number, timestamp: number) => void, propertyClasses: string}) {
|
||||||
if (item.kind === "bigstep") {
|
if (item.kind === "bigstep") {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const newStates = item.state.sc.mode.difference(prevItem?.state.sc.mode || new Set());
|
const newStates = item.state.sc.mode.difference(prevItem?.state.sc.mode || new Set());
|
||||||
|
|
@ -63,6 +94,8 @@ export const RTHistoryItem = memo(function RTHistoryItem({ast, idx, item, prevIt
|
||||||
className={"runtimeState" + (active ? " active" : "") + (isPlantStep ? " plantStep" : "")}
|
className={"runtimeState" + (active ? " active" : "") + (isPlantStep ? " plantStep" : "")}
|
||||||
onMouseDown={useCallback(() => onMouseDown(idx, item.simtime), [idx, item.simtime])}>
|
onMouseDown={useCallback(() => onMouseDown(idx, item.simtime), [idx, item.simtime])}>
|
||||||
<div>
|
<div>
|
||||||
|
<div className={propertyClasses}/>
|
||||||
|
 
|
||||||
{formatTime(item.simtime)}
|
{formatTime(item.simtime)}
|
||||||
 
|
 
|
||||||
<div className="inputEvent"><RTCause cause={isPlantStep ? item.state.plant.inputEvent : item.state.sc.inputEvent}/></div>
|
<div className="inputEvent"><RTCause cause={isPlantStep ? item.state.plant.inputEvent : item.state.sc.inputEvent}/></div>
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ export function ShowInputEvents({inputEvents, onRaise, disabled, showKeys}: {inp
|
||||||
const value = inputParams[key] || "";
|
const value = inputParams[key] || "";
|
||||||
const width = Math.max(value.length, (paramName||"").length)*6;
|
const width = Math.max(value.length, (paramName||"").length)*6;
|
||||||
const shortcut = (i+1)%10;
|
const shortcut = (i+1)%10;
|
||||||
const KI = (i <= 10) ? KeyInfo : KeyInfoHidden;
|
const KI = (i < 10) ? KeyInfo : KeyInfoHidden;
|
||||||
return <div key={key} className="toolbarGroup">
|
return <div key={key} className="toolbarGroup">
|
||||||
<KI keyInfo={<kbd>{shortcut}</kbd>} horizontal={true}>
|
<KI keyInfo={<kbd>{shortcut}</kbd>} horizontal={true}>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ export const SpeedControl = memo(function SpeedControl({showKeys, timescale, set
|
||||||
}, [onTimeScaleChange, timescale]);
|
}, [onTimeScaleChange, timescale]);
|
||||||
|
|
||||||
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (["INPUT", "TEXTAREA", "SELECT"].includes(e.target?.tagName)) return;
|
||||||
if (!e.ctrlKey) {
|
if (!e.ctrlKey) {
|
||||||
if (e.key === "s") {
|
if (e.key === "s") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,8 @@ export function useCopyPaste(makeCheckPoint: () => void, state: VisualEditorStat
|
||||||
}, [setState]);
|
}, [setState]);
|
||||||
|
|
||||||
const onKeyDown = (e: KeyboardEvent) => {
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (["INPUT", "TEXTAREA", "SELECT"].includes(e.target?.tagName)) return;
|
||||||
if (e.key === "Delete") {
|
if (e.key === "Delete") {
|
||||||
// delete selection
|
// delete selection
|
||||||
makeCheckPoint();
|
makeCheckPoint();
|
||||||
|
|
|
||||||
79
src/App/check_property.ts
Normal file
79
src/App/check_property.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { RT_Statechart } from "@/statecharts/runtime_types";
|
||||||
|
import { TraceItem } from "./App";
|
||||||
|
import { Plant } from "./Plant/Plant";
|
||||||
|
|
||||||
|
// const endpoint = "http://localhost:15478/check_property";
|
||||||
|
const endpoint = "https://deemz.org/apis/mtl-aas/check_property";
|
||||||
|
|
||||||
|
export type PropertyTrace = {
|
||||||
|
timestamp: number,
|
||||||
|
satisfied: boolean,
|
||||||
|
}[];
|
||||||
|
|
||||||
|
export type PropertyCheckResult = [null|PropertyTrace, null|string];
|
||||||
|
|
||||||
|
export async function checkProperty(plant: Plant<RT_Statechart, any>, property: string, trace: [TraceItem, ...TraceItem[]]): Promise<PropertyCheckResult> {
|
||||||
|
// pre-process data...
|
||||||
|
|
||||||
|
const cleanPlantStates0 = trace
|
||||||
|
.map(v => {
|
||||||
|
return {
|
||||||
|
simtime: v.simtime,
|
||||||
|
state: Object.fromEntries(Object.entries(v.kind === "bigstep" && plant.cleanupState(v.state.plant) || {}).map(([prop, val]) => [prop, Boolean(val)])),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const cleanPlantStates = cleanPlantStates0 && cleanPlantStates0
|
||||||
|
// we can never have multiple states at the same point in simtime or Argus will panic
|
||||||
|
.reduce((trace, entry, i) => {
|
||||||
|
const prevEntry = cleanPlantStates0[i-1];
|
||||||
|
if (prevEntry !== undefined) {
|
||||||
|
if (entry.simtime > prevEntry.simtime) {
|
||||||
|
return [...trace, entry]; // ok
|
||||||
|
}
|
||||||
|
return [...trace.slice(0,-1), entry]; // current entry has same simtime and thus replaces previous entry
|
||||||
|
}
|
||||||
|
return [entry];
|
||||||
|
}, [] as {simtime: number, state: any}[]);
|
||||||
|
|
||||||
|
let traces = {} as {[key: string]: [number, any][]};
|
||||||
|
for (const {simtime, state} of cleanPlantStates) {
|
||||||
|
for (const [key, value] of Object.entries(state)) {
|
||||||
|
// just append
|
||||||
|
traces[key] = traces[key] || [];
|
||||||
|
const prevSample = traces[key].at(-1);
|
||||||
|
// only append sample if value changed:
|
||||||
|
if (!prevSample || prevSample[1] !== value) {
|
||||||
|
traces[key].push([simtime, value]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log({cleanPlantStates, traces});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
property,
|
||||||
|
traces,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
// console.log('backend result:', json);
|
||||||
|
|
||||||
|
if (typeof json === 'string') {
|
||||||
|
return [null, json];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return [json.map(([timestamp, satisfied]) => ({timestamp, satisfied})), null];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return [null, e.message];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,9 +7,9 @@ import { emptyState } from "@/statecharts/concrete_syntax";
|
||||||
|
|
||||||
export function useEditor(editorState: VisualEditorState | null, setEditHistory: Dispatch<SetStateAction<EditHistory|null>>) {
|
export function useEditor(editorState: VisualEditorState | null, setEditHistory: Dispatch<SetStateAction<EditHistory|null>>) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("Welcome to StateBuddy!");
|
console.info("Welcome to StateBuddy!");
|
||||||
() => {
|
() => {
|
||||||
console.log("Goodbye!");
|
console.info("Goodbye!");
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-family: Roboto, sans-serif;
|
font-family: Roboto, sans-serif;
|
||||||
|
font-size: 10pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
@ -33,8 +34,6 @@ kbd:active { transform: translateY(1px); }
|
||||||
input {
|
input {
|
||||||
/* border: solid blue 2px; */
|
/* border: solid blue 2px; */
|
||||||
accent-color: rgba(0,0,255,0.2);
|
accent-color: rgba(0,0,255,0.2);
|
||||||
|
|
||||||
/* accent-color: blue; */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
|
|
@ -44,3 +43,4 @@ input {
|
||||||
label {
|
label {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,8 @@ export type Statechart = {
|
||||||
|
|
||||||
uid2State: Map<string, ConcreteState|UnstableState>;
|
uid2State: Map<string, ConcreteState|UnstableState>;
|
||||||
|
|
||||||
|
label2State: Map<string, ConcreteState>;
|
||||||
|
|
||||||
historyStates: HistoryState[];
|
historyStates: HistoryState[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,6 +90,7 @@ export const emptyStatechart: Statechart = {
|
||||||
internalEvents: [],
|
internalEvents: [],
|
||||||
outputEvents: new Set(),
|
outputEvents: new Set(),
|
||||||
uid2State: new Map([["root", emptyRoot]]),
|
uid2State: new Map([["root", emptyRoot]]),
|
||||||
|
label2State: new Map([]),
|
||||||
historyStates: [],
|
historyStates: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { Action, EventTrigger, Expression, ParsedText } from "./label_ast";
|
||||||
import { parse as parseLabel, SyntaxError } from "./label_parser";
|
import { parse as parseLabel, SyntaxError } from "./label_parser";
|
||||||
import { Connections } from "./detect_connections";
|
import { Connections } from "./detect_connections";
|
||||||
import { HISTORY_RADIUS } from "../App/parameters";
|
import { HISTORY_RADIUS } from "../App/parameters";
|
||||||
import { ConcreteSyntax, VisualEditorState } from "@/App/VisualEditor/VisualEditor";
|
import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
||||||
import { memoize } from "@/util/util";
|
import { memoize } from "@/util/util";
|
||||||
|
|
||||||
export type TraceableError = {
|
export type TraceableError = {
|
||||||
|
|
@ -52,6 +52,7 @@ export function parseStatechart(state: ConcreteSyntax, conns: Connections): [Sta
|
||||||
}
|
}
|
||||||
|
|
||||||
const uid2State = new Map<string, ConcreteState|UnstableState>([["root", root]]);
|
const uid2State = new Map<string, ConcreteState|UnstableState>([["root", root]]);
|
||||||
|
const label2State = new Map<string, ConcreteState>();
|
||||||
const historyStates: HistoryState[] = [];
|
const historyStates: HistoryState[] = [];
|
||||||
|
|
||||||
// we will always look for the smallest parent rountangle
|
// we will always look for the smallest parent rountangle
|
||||||
|
|
@ -329,6 +330,9 @@ export function parseStatechart(state: ConcreteSyntax, conns: Connections): [Sta
|
||||||
}
|
}
|
||||||
else if (parsed.kind === "comment") {
|
else if (parsed.kind === "comment") {
|
||||||
// just append comments to their respective states
|
// just append comments to their respective states
|
||||||
|
if (!label2State.has(parsed.text)) {
|
||||||
|
label2State.set(parsed.text, belongsToState);
|
||||||
|
}
|
||||||
belongsToState.comments.push([text.uid, parsed.text]);
|
belongsToState.comments.push([text.uid, parsed.text]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -384,6 +388,7 @@ export function parseStatechart(state: ConcreteSyntax, conns: Connections): [Sta
|
||||||
internalEvents,
|
internalEvents,
|
||||||
outputEvents,
|
outputEvents,
|
||||||
uid2State,
|
uid2State,
|
||||||
|
label2State,
|
||||||
historyStates,
|
historyStates,
|
||||||
}, errors];
|
}, errors];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -194,12 +194,13 @@ export function coupledExecution<T extends {[name: string]: any}>(models: {[name
|
||||||
throw new Error("cannot make intTransition - timeAdvance is infinity");
|
throw new Error("cannot make intTransition - timeAdvance is infinity");
|
||||||
},
|
},
|
||||||
extTransition: (simtime, c, e) => {
|
extTransition: (simtime, c, e) => {
|
||||||
if (!Object.hasOwn(conns, e.name)) {
|
if (conns[e.name] === undefined) {
|
||||||
console.warn('input event', e.name, 'goes to nowhere');
|
console.warn('input event', e.name, 'goes to nowhere');
|
||||||
return [[], c];
|
return [[], c];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const [model, eventName] = conns[e.name];
|
const [model, eventName] = conns[e.name];
|
||||||
|
// console.log(conns);
|
||||||
if (model !== null) {
|
if (model !== null) {
|
||||||
console.log('input event', e.name, 'goes to', `${model}.${eventName}`);
|
console.log('input event', e.name, 'goes to', `${model}.${eventName}`);
|
||||||
const inputEvent: InputEvent = {
|
const inputEvent: InputEvent = {
|
||||||
|
|
|
||||||
|
|
@ -84,3 +84,11 @@ export function mapsEqual<K,V>(a: Map<K,V>, b: Map<K,V>, cmp: (a: V, b: V) => bo
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function withGrow<T>(arr: T[], i: number, value: T, fill: T) {
|
||||||
|
if (i >= arr.length) {
|
||||||
|
arr = [...arr, ...new Array(i - arr.length + 1).map(_ => fill)];
|
||||||
|
}
|
||||||
|
return arr.with(i, value);
|
||||||
|
}
|
||||||
|
|
|
||||||
13
todo.txt
13
todo.txt
|
|
@ -28,7 +28,7 @@
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
- bugs
|
- bugs
|
||||||
interpreter: pseudo-state semantics is broken
|
editing SC <-> Plant connections at runtime doesn't seem to work
|
||||||
|
|
||||||
- testing
|
- testing
|
||||||
use STL for testing
|
use STL for testing
|
||||||
|
|
@ -51,15 +51,8 @@ TODO
|
||||||
- hovering over event in side panel should highlight all occurrences of the event in the SC
|
- hovering over event in side panel should highlight all occurrences of the event in the SC
|
||||||
- hovering over error in bottom panel should highlight that rror in the SC
|
- hovering over error in bottom panel should highlight that rror in the SC
|
||||||
- highlight selected shapes while making a selection
|
- highlight selected shapes while making a selection
|
||||||
|
|
||||||
- highlight about-to-fire transitions
|
- highlight about-to-fire transitions
|
||||||
|
- integrate undo-history with browser history (back/forward buttons)
|
||||||
- when there is a runtime error, e.g.,
|
|
||||||
- variable not found
|
|
||||||
- stuck in pseudo-state
|
|
||||||
- ???
|
|
||||||
don't crash and show the error
|
|
||||||
- buttons to rotate selection 90 degrees
|
|
||||||
|
|
||||||
- performance:
|
- performance:
|
||||||
maybe try this for rendering the execution trace:
|
maybe try this for rendering the execution trace:
|
||||||
|
|
@ -69,7 +62,7 @@ TODO
|
||||||
- multiverse execution history
|
- multiverse execution history
|
||||||
stable tree layout?
|
stable tree layout?
|
||||||
https://pub.dev/packages/ploeg_tree_layout
|
https://pub.dev/packages/ploeg_tree_layout
|
||||||
- local scopes
|
- local variable scopes
|
||||||
|
|
||||||
for the assignment:
|
for the assignment:
|
||||||
*ALL* features
|
*ALL* features
|
||||||
|
|
|
||||||
39
trash/argus.ts
Normal file
39
trash/argus.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
// Some deleted code for calling argus...
|
||||||
|
|
||||||
|
// // Dynamically import the JS loader
|
||||||
|
// const wasm = await import("argus-wasm/pkg/argus_wasm.js");
|
||||||
|
|
||||||
|
// // @ts-ignore
|
||||||
|
// import wasmfile from "../../node_modules/argus-wasm/pkg/argus_wasm_bg.wasm";
|
||||||
|
|
||||||
|
// async function initWasm() {
|
||||||
|
// // Initialize the module with the URL to the .wasm
|
||||||
|
// await wasm.default(wasmfile);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// window.initWasm = initWasm;
|
||||||
|
|
||||||
|
// initWasm();
|
||||||
|
|
||||||
|
|
||||||
|
// let evaluation = null;
|
||||||
|
// let propertyError: null | string = null;
|
||||||
|
// try {
|
||||||
|
// if (cleanPlantStates) {
|
||||||
|
// // throws runtime error if Rust panics:
|
||||||
|
// evaluation = wasm.eval_boolean(property, {entries: cleanPlantStates});
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// catch (e) {
|
||||||
|
// propertyError = "property evaluation panic'ed: " + e.message;
|
||||||
|
// initWasm();
|
||||||
|
// }
|
||||||
|
// let propertyTrace: null | {timestamp: number, satisfied: boolean}[] = null;
|
||||||
|
// if (typeof evaluation === 'string') {
|
||||||
|
// propertyError = evaluation;
|
||||||
|
// }
|
||||||
|
// else if (evaluation !== null && Array.isArray(evaluation.entries)) {
|
||||||
|
// // propertyTrace = evaluation.entries.map(({satisfied}) => satisfied);
|
||||||
|
// propertyTrace = evaluation.entries;
|
||||||
|
// }
|
||||||
Loading…
Add table
Add a link
Reference in a new issue