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": {
|
||||
"@fontsource/roboto": "^5.2.8",
|
||||
"@mui/icons-material": "^7.3.4",
|
||||
"react": "^19",
|
||||
"react-dom": "^19",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@types/bun": "1.3.1",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"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/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/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/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/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/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/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=="],
|
||||
|
||||
"@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/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=="],
|
||||
|
||||
"@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/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
|
||||
"@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"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.2.23", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-R9f0hKAZXgFU3mlrA0YpE/fiDvwV0FT9rORApt2aQVWSuJDzZOyB5QLc0N/4HF57CS8IXJ6+L5E4W1bW6NS2Aw=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
"bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
|
||||
|
||||
"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.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=="],
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||
}
|
||||
|
|
|
|||
3
global.d.ts
vendored
3
global.d.ts
vendored
|
|
@ -4,4 +4,5 @@ declare module '*.png';
|
|||
declare module '*.ttf';
|
||||
declare module '*.wav';
|
||||
declare module '*.opus';
|
||||
declare module '*.webp';
|
||||
declare module '*.webp';
|
||||
declare module '*.wasm';
|
||||
11
package.json
11
package.json
|
|
@ -13,12 +13,13 @@
|
|||
"dependencies": {
|
||||
"@fontsource/roboto": "^5.2.8",
|
||||
"@mui/icons-material": "^7.3.4",
|
||||
"react": "^19",
|
||||
"react-dom": "^19"
|
||||
// "argus-wasm": "git+https://deemz.org/git/joeri/argus-wasm.git#a4491b3433d48aa1f941bd5ad37b36f819d3b2ac",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@types/bun": "latest"
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@types/bun": "1.3.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,9 +74,20 @@ details:has(+ details) {
|
|||
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 {
|
||||
border: solid blue 2px;
|
||||
border: solid blue 1px;
|
||||
background-color: rgba(0,0,255,0.2);
|
||||
/* margin-right: 1px; */
|
||||
/* margin-left: 0; */
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
|
@ -110,3 +121,21 @@ div.stackHorizontal {
|
|||
display: flex;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
168
src/App/App.tsx
168
src/App/App.tsx
|
|
@ -3,26 +3,33 @@ import "./App.css";
|
|||
|
||||
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 { Conns, coupledExecution, EventDestination, statechartExecution, TimedReactive } from "@/statecharts/timed_reactive";
|
||||
import { Conns, coupledExecution, statechartExecution } from "@/statecharts/timed_reactive";
|
||||
import { RuntimeError } from "../statecharts/interpreter";
|
||||
import { parseStatechart } from "../statecharts/parser";
|
||||
import { BigStep, RaisedEvent } from "../statecharts/runtime_types";
|
||||
import { getSimTime, getWallClkDelay, TimeMode } from "../statecharts/time";
|
||||
import { BottomPanel } from "./BottomPanel";
|
||||
import { usePersistentState } from "./persistent_state";
|
||||
import { PersistentDetails } from "./PersistentDetails";
|
||||
import { digitalWatchPlant } from "./Plant/DigitalWatch/DigitalWatch";
|
||||
import { dummyPlant } from "./Plant/Dummy/Dummy";
|
||||
import { microwavePlant } from "./Plant/Microwave/Microwave";
|
||||
import { Plant } from "./Plant/Plant";
|
||||
import { trafficLightPlant } from "./Plant/TrafficLight/TrafficLight";
|
||||
import { RTHistory } from "./RTHistory";
|
||||
import { ShowAST, ShowInputEvents, ShowInternalEvents, ShowOutputEvents } from "./ShowAST";
|
||||
import { InsertMode } from "./TopPanel/InsertModes";
|
||||
import { TopPanel } from "./TopPanel/TopPanel";
|
||||
import { VisualEditor, VisualEditorState } from "./VisualEditor/VisualEditor";
|
||||
import { digitalWatchPlant } from "./Plant/DigitalWatch/DigitalWatch";
|
||||
import { useEditor as useEditor } from "./useEditor";
|
||||
import { InsertMode } from "./TopPanel/InsertModes";
|
||||
import { Statechart } from "@/statecharts/abstract_syntax";
|
||||
import { checkProperty, PropertyCheckResult } from "./check_property";
|
||||
import { usePersistentState } from "./persistent_state";
|
||||
import { useEditor } from "./useEditor";
|
||||
|
||||
export type EditHistory = {
|
||||
current: VisualEditorState,
|
||||
|
|
@ -30,11 +37,13 @@ export type EditHistory = {
|
|||
future: VisualEditorState[],
|
||||
}
|
||||
|
||||
const plants: [string, Plant<any>][] = [
|
||||
type UniversalPlantState = {[property: string]: boolean|number};
|
||||
|
||||
const plants: [string, Plant<any, UniversalPlantState>][] = [
|
||||
["dummy", dummyPlant],
|
||||
["microwave", microwavePlant],
|
||||
["digital watch", digitalWatchPlant],
|
||||
["traffic light", trafficLightPlant],
|
||||
["microwave", microwavePlant as unknown as Plant<any, UniversalPlantState>],
|
||||
["digital watch", digitalWatchPlant as unknown as Plant<any, UniversalPlantState>],
|
||||
["traffic light", trafficLightPlant as unknown as Plant<any, UniversalPlantState>],
|
||||
]
|
||||
|
||||
export type TraceItemError = {
|
||||
|
|
@ -46,6 +55,7 @@ export type TraceItemError = {
|
|||
type CoupledState = {
|
||||
sc: BigStep,
|
||||
plant: BigStep,
|
||||
// plantCleanState: {[prop: string]: boolean|number},
|
||||
};
|
||||
|
||||
export type TraceItem =
|
||||
|
|
@ -53,10 +63,9 @@ export type TraceItem =
|
|||
| { kind: "bigstep", simtime: number, cause: string, state: CoupledState, outputEvents: RaisedEvent[] };
|
||||
|
||||
export type TraceState = {
|
||||
// executor: TimedReactive<CoupledState>,
|
||||
trace: [TraceItem, ...TraceItem[]], // non-empty
|
||||
idx: number,
|
||||
}; // <-- null if there is no trace
|
||||
};
|
||||
|
||||
export function App() {
|
||||
const [insertMode, setInsertMode] = usePersistentState<InsertMode>("insertMode", "and");
|
||||
|
|
@ -94,7 +103,7 @@ export function App() {
|
|||
message: currentTraceItem.error.message,
|
||||
shapeUid: currentTraceItem.error.highlight[0],
|
||||
}] : [],
|
||||
]
|
||||
];
|
||||
|
||||
const {makeCheckPoint, onRedo, onUndo, onRotate} = useEditor(editorState, setEditHistory);
|
||||
|
||||
|
|
@ -108,23 +117,7 @@ export function App() {
|
|||
}
|
||||
}, [refRightSideBar.current, autoScroll]);
|
||||
|
||||
// const plantConns = ast && ({
|
||||
// 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;
|
||||
// coupled execution
|
||||
const cE = useMemo(() => ast && coupledExecution({
|
||||
sc: statechartExecution(ast),
|
||||
plant: plant.execution,
|
||||
|
|
@ -276,6 +269,27 @@ export function App() {
|
|||
ast && autoConnect && autoDetectConns(ast, plant, setPlantConns);
|
||||
}, [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 <>
|
||||
|
||||
{/* Modal dialog */}
|
||||
|
|
@ -326,12 +340,14 @@ export function App() {
|
|||
className={showExecutionTrace ? "shadowBelow" : ""}
|
||||
style={{flex: '0 0 content', backgroundColor: ''}}
|
||||
>
|
||||
{/* State tree */}
|
||||
<PersistentDetails localStorageKey="showStateTree" initiallyOpen={true}>
|
||||
<summary>state tree</summary>
|
||||
<ul>
|
||||
{ast && <ShowAST {...{...ast, trace, highlightActive}}/>}
|
||||
</ul>
|
||||
</PersistentDetails>
|
||||
{/* Input events */}
|
||||
<PersistentDetails localStorageKey="showInputEvents" initiallyOpen={true}>
|
||||
<summary>input events</summary>
|
||||
{ast && <ShowInputEvents
|
||||
|
|
@ -340,14 +356,17 @@ export function App() {
|
|||
disabled={trace===null || trace.trace[trace.idx].kind === "error"}
|
||||
showKeys={showKeys}/>}
|
||||
</PersistentDetails>
|
||||
{/* Internal events */}
|
||||
<PersistentDetails localStorageKey="showInternalEvents" initiallyOpen={true}>
|
||||
<summary>internal events</summary>
|
||||
{ast && <ShowInternalEvents internalEvents={ast.internalEvents}/>}
|
||||
</PersistentDetails>
|
||||
{/* Output events */}
|
||||
<PersistentDetails localStorageKey="showOutputEvents" initiallyOpen={true}>
|
||||
<summary>output events</summary>
|
||||
{ast && <ShowOutputEvents outputEvents={ast.outputEvents}/>}
|
||||
</PersistentDetails>
|
||||
{/* Plant */}
|
||||
<PersistentDetails localStorageKey="showPlant" initiallyOpen={true}>
|
||||
<summary>plant</summary>
|
||||
<select
|
||||
|
|
@ -360,18 +379,49 @@ export function App() {
|
|||
</select>
|
||||
<br/>
|
||||
{/* 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)}
|
||||
/>}
|
||||
</PersistentDetails>
|
||||
<PersistentDetails localStorageKey="showConnEditor" initiallyOpen={false}>
|
||||
<summary>connections</summary>
|
||||
<button title="auto-connect (name-based)" className={autoConnect?"active":""}
|
||||
onClick={() => setAutoConnect(c => !c)}>
|
||||
<AutoAwesomeIcon fontSize="small"/>
|
||||
{/* Connections */}
|
||||
<PersistentDetails localStorageKey="showConnEditor" initiallyOpen={false}>
|
||||
<summary>connections</summary>
|
||||
<button title="auto-connect (name-based)" className={autoConnect?"active":""}
|
||||
onClick={() => setAutoConnect(c => !c)}>
|
||||
<AutoAwesomeIcon fontSize="small"/>
|
||||
</button>
|
||||
{ast && ConnEditor(ast, plant, plantConns, setPlantConns)}
|
||||
</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>
|
||||
{ast && ConnEditor(ast, plant, plantConns, setPlantConns)}
|
||||
</PersistentDetails>
|
||||
</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>
|
||||
<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
|
||||
}}>
|
||||
<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 style={{flex: '0 0 content'}}>
|
||||
|
|
@ -407,32 +458,7 @@ export function App() {
|
|||
</>;
|
||||
}
|
||||
|
||||
function ShowEventDestination(dst: EventDestination) {
|
||||
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>>) {
|
||||
function autoDetectConns(ast: Statechart, plant: Plant<any, any>, setPlantConns: Dispatch<SetStateAction<Conns>>) {
|
||||
for (const {event: a} of plant.uiEvents) {
|
||||
for (const {event: b} of plant.inputEvents) {
|
||||
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 scInputs = <>{ast.inputEvents.map(e => <option key={'sc.'+e.event} value={'sc.'+e.event}>sc.{e.event}</option>)}</>;
|
||||
return <>
|
||||
|
|
@ -473,7 +500,8 @@ function ConnEditor(ast: Statechart, plant: Plant<any>, plantConns: Conns, setPl
|
|||
<select id={`select-dst-sc-${e}`}
|
||||
style={{width:'50%'}}
|
||||
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>
|
||||
{plantInputs}
|
||||
</select>
|
||||
|
|
@ -485,7 +513,8 @@ function ConnEditor(ast: Statechart, plant: Plant<any>, plantConns: Conns, setPl
|
|||
<select id={`select-dst-plant-${e.event}`}
|
||||
style={{width:'50%'}}
|
||||
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>
|
||||
{scInputs}
|
||||
</select>
|
||||
|
|
@ -497,7 +526,8 @@ function ConnEditor(ast: Statechart, plant: Plant<any>, plantConns: Conns, setPl
|
|||
<select id={`select-dst-plant-ui-${e.event}`}
|
||||
style={{width:'50%'}}
|
||||
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>
|
||||
{scInputs}
|
||||
{plantInputs}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
|||
import { detectConnections } from "@/statecharts/detect_connections";
|
||||
import { parseStatechart } from "@/statecharts/parser";
|
||||
import { RT_Statechart } from "@/statecharts/runtime_types";
|
||||
import { useEffect } from "react";
|
||||
import { memo, useEffect } from "react";
|
||||
import { makeStatechartPlant, PlantRenderProps } from "../Plant";
|
||||
|
||||
import dwatchConcreteSyntax from "./model.json";
|
||||
|
|
@ -12,6 +12,7 @@ import digitalFont from "./digital-font.ttf";
|
|||
import "./DigitalWatch.css";
|
||||
import imgNote from "./noteSmall.png";
|
||||
import imgWatch from "./watch.png";
|
||||
import { objectsEqual } from "@/util/util";
|
||||
|
||||
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.")
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
export function DigitalWatch({state, speed, raiseUIEvent}: PlantRenderProps<RT_Statechart>) {
|
||||
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});
|
||||
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>) {
|
||||
|
||||
let hhmmss;
|
||||
if (displayingTime) {
|
||||
|
|
@ -63,8 +97,6 @@ export function DigitalWatch({state, speed, raiseUIEvent}: PlantRenderProps<RT_S
|
|||
|
||||
preloadAudio(sndBeep);
|
||||
|
||||
const beep = state.mode.has("632");
|
||||
|
||||
useEffect(() => {
|
||||
if (beep) {
|
||||
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>
|
||||
|
||||
<rect className="watchButtonHelper" x={0} y={54} width={24} height={24}
|
||||
onMouseDown={() => raiseUIEvent({name: "topLeftPressed"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "topLeftReleased"})}
|
||||
onMouseDown={() => raiseUIEvent({name: "topLeftMouseDown"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "topLeftMouseUp"})}
|
||||
/>
|
||||
<rect className="watchButtonHelper" x={198} y={54} width={24} height={24}
|
||||
onMouseDown={() => raiseUIEvent({name: "topRightPressed"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "topRightReleased"})}
|
||||
onMouseDown={() => raiseUIEvent({name: "topRightMouseDown"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "topRightMouseUp"})}
|
||||
/>
|
||||
<rect className="watchButtonHelper" x={0} y={154} width={24} height={24}
|
||||
onMouseDown={() => raiseUIEvent({name: "bottomLeftPressed"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "bottomLeftReleased"})}
|
||||
onMouseDown={() => raiseUIEvent({name: "bottomLeftMouseDown"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "bottomLeftMouseUp"})}
|
||||
/>
|
||||
<rect className="watchButtonHelper" x={198} y={154} width={24} height={24}
|
||||
onMouseDown={() => raiseUIEvent({name: "bottomRightPressed"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "bottomRightReleased"})}
|
||||
onMouseDown={() => raiseUIEvent({name: "bottomRightMouseDown"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "bottomRightMouseUp"})}
|
||||
/>
|
||||
|
||||
{alarm &&
|
||||
{alarmOn &&
|
||||
<image x="54" y="98" xlinkHref={imgNote} />
|
||||
}
|
||||
</svg>
|
||||
</>;
|
||||
}
|
||||
}, objectsEqual);
|
||||
|
||||
export const digitalWatchPlant = makeStatechartPlant({
|
||||
ast: dwatchAbstractSyntax,
|
||||
cleanupState: dwatchConfigToState,
|
||||
render: DigitalWatch,
|
||||
uiEvents: [
|
||||
{ kind: "event", event: "topLeftPressed" },
|
||||
{ kind: "event", event: "topRightPressed" },
|
||||
{ kind: "event", event: "bottomRightPressed" },
|
||||
{ kind: "event", event: "bottomLeftPressed" },
|
||||
{ kind: "event", event: "topLeftReleased" },
|
||||
{ kind: "event", event: "topRightReleased" },
|
||||
{ kind: "event", event: "bottomRightReleased" },
|
||||
{ kind: "event", event: "bottomLeftReleased" },
|
||||
{ kind: "event", event: "topLeftMouseDown" },
|
||||
{ kind: "event", event: "topRightMouseDown" },
|
||||
{ kind: "event", event: "bottomRightMouseDown" },
|
||||
{ kind: "event", event: "bottomLeftMouseDown" },
|
||||
{ kind: "event", event: "topLeftMouseUp" },
|
||||
{ kind: "event", event: "topRightMouseUp" },
|
||||
{ kind: "event", event: "bottomRightMouseUp" },
|
||||
{ kind: "event", event: "bottomLeftMouseUp" },
|
||||
],
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,17 +1,18 @@
|
|||
import { Plant } from "../Plant";
|
||||
import { TimedReactive } from "@/statecharts/timed_reactive";
|
||||
|
||||
export const dummyExecution: TimedReactive<null> = {
|
||||
initial: () => [[], null],
|
||||
export const dummyExecution: TimedReactive<{}> = {
|
||||
initial: () => [[], {}],
|
||||
timeAdvance: () => Infinity,
|
||||
intTransition: () => { throw new Error("dummy never makes intTransition"); },
|
||||
extTransition: () => [[], null],
|
||||
extTransition: () => [[], {}],
|
||||
};
|
||||
|
||||
export const dummyPlant: Plant<null> = {
|
||||
export const dummyPlant: Plant<{}, {}> = {
|
||||
uiEvents: [],
|
||||
inputEvents: [],
|
||||
outputEvents: [],
|
||||
execution: dummyExecution,
|
||||
render: (props) => <></>,
|
||||
}
|
||||
cleanupState: ({}) => ({}),
|
||||
render: ({}) => <></>,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,12 +13,13 @@ import { memo, useEffect } from "react";
|
|||
|
||||
import "./Microwave.css";
|
||||
import { useAudioContext } from "../../useAudioContext";
|
||||
import { comparePlantRenderProps, makeStatechartPlant, PlantRenderProps, StatechartPlantSpec } from "../Plant";
|
||||
import { makeStatechartPlant, PlantRenderProps, StatechartPlantSpec } from "../Plant";
|
||||
import { detectConnections } from "@/statecharts/detect_connections";
|
||||
import { parseStatechart } from "@/statecharts/parser";
|
||||
|
||||
import microwaveConcreteSyntax from "./model.json";
|
||||
import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
||||
import { objectsEqual } from "@/util/util";
|
||||
|
||||
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.")
|
||||
}
|
||||
|
||||
|
||||
const imgs = {
|
||||
"false": { "false": imgSmallClosedOff, "true": imgSmallClosedOn },
|
||||
"true": { "false": imgSmallOpenedOff, "true": imgSmallOpenedOn },
|
||||
|
|
@ -47,7 +47,19 @@ const DOOR_Y0 = 68;
|
|||
const DOOR_WIDTH = 353;
|
||||
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);
|
||||
|
||||
// preload(imgSmallClosedOff, {as: "image"});
|
||||
|
|
@ -58,11 +70,6 @@ export const Microwave = memo(function Microwave({state, speed, raiseUIEvent}: P
|
|||
preloadAudio(sndRunning);
|
||||
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...
|
||||
useEffect(() => {
|
||||
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}/>
|
||||
|
||||
<rect className="microwaveButtonHelper" x={START_X0} y={START_Y0} width={BUTTON_WIDTH} height={BUTTON_HEIGHT}
|
||||
onMouseDown={() => raiseUIEvent({name: "startPressed"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "startReleased"})}
|
||||
onMouseDown={() => raiseUIEvent({name: "startMouseDown"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "startMouseUp"})}
|
||||
/>
|
||||
<rect className="microwaveButtonHelper" x={STOP_X0} y={STOP_Y0} width={BUTTON_WIDTH} height={BUTTON_HEIGHT}
|
||||
onMouseDown={() => raiseUIEvent({name: "stopPressed"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "stopReleased"})}
|
||||
onMouseDown={() => raiseUIEvent({name: "stopMouseDown"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "stopMouseUp"})}
|
||||
/>
|
||||
<rect className="microwaveButtonHelper" x={INCTIME_X0} y={INCTIME_Y0} width={BUTTON_WIDTH} height={BUTTON_HEIGHT}
|
||||
onMouseDown={() => raiseUIEvent({name: "incTimePressed"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "incTimeReleased"})}
|
||||
onMouseDown={() => raiseUIEvent({name: "incTimeMouseDown"})}
|
||||
onMouseUp={() => raiseUIEvent({name: "incTimeMouseUp"})}
|
||||
/>
|
||||
<rect className="microwaveDoorHelper"
|
||||
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>
|
||||
</svg>
|
||||
</>;
|
||||
}, comparePlantRenderProps);
|
||||
}, objectsEqual);
|
||||
|
||||
const microwavePlantSpec: StatechartPlantSpec = {
|
||||
const microwavePlantSpec: StatechartPlantSpec<MicrowaveState> = {
|
||||
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,
|
||||
uiEvents: [
|
||||
{kind: "event", event: "doorMouseDown"},
|
||||
{kind: "event", event: "doorMouseUp"},
|
||||
{kind: "event", event: "startPressed"},
|
||||
{kind: "event", event: "startReleased"},
|
||||
{kind: "event", event: "stopPressed"},
|
||||
{kind: "event", event: "stopReleased"},
|
||||
{kind: "event", event: "incTimePressed"},
|
||||
{kind: "event", event: "incTimeReleased"},
|
||||
{kind: "event", event: "startMouseDown"},
|
||||
{kind: "event", event: "startMouseUp"},
|
||||
{kind: "event", event: "stopMouseDown"},
|
||||
{kind: "event", event: "stopMouseUp"},
|
||||
{kind: "event", event: "incTimeMouseDown"},
|
||||
{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 { EventTrigger } from "@/statecharts/label_ast";
|
||||
import { BigStep, RaisedEvent, RT_Statechart } from "@/statecharts/runtime_types";
|
||||
import { statechartExecution, TimedReactive } from "@/statecharts/timed_reactive";
|
||||
import { mapsEqual, setsEqual } from "@/util/util";
|
||||
import { setsEqual } from "@/util/util";
|
||||
|
||||
export type PlantRenderProps<StateType> = {
|
||||
state: StateType,
|
||||
|
|
@ -11,18 +11,19 @@ export type PlantRenderProps<StateType> = {
|
|||
raiseUIEvent: (e: RaisedEvent) => void,
|
||||
};
|
||||
|
||||
export type Plant<StateType> = {
|
||||
export type Plant<StateType, CleanStateType> = {
|
||||
uiEvents: EventTrigger[];
|
||||
|
||||
inputEvents: EventTrigger[];
|
||||
outputEvents: EventTrigger[];
|
||||
|
||||
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.
|
||||
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 = {
|
||||
[scName]: {},
|
||||
[plantName]: {},
|
||||
|
|
@ -44,7 +45,7 @@ export function autoConnect(ast: Statechart, scName: string, plant: Plant<any>,
|
|||
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 = {};
|
||||
for (const i of plant.inputEvents) {
|
||||
// @ts-ignore
|
||||
|
|
@ -53,25 +54,27 @@ export function exposePlantInputs(plant: Plant<any>, plantName: string, tfm = (s
|
|||
return inputs
|
||||
}
|
||||
|
||||
export type StatechartPlantSpec = {
|
||||
export type StatechartPlantSpec<CleanStateType> = {
|
||||
uiEvents: EventTrigger[],
|
||||
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 {
|
||||
uiEvents,
|
||||
inputEvents: ast.inputEvents,
|
||||
outputEvents: [...ast.outputEvents].map(e => ({kind: "event" as const, event: e})),
|
||||
execution: statechartExecution(ast),
|
||||
cleanupState,
|
||||
render,
|
||||
}
|
||||
}
|
||||
|
||||
export function comparePlantRenderProps(oldProps: PlantRenderProps<RT_Statechart>, newProps: PlantRenderProps<RT_Statechart>) {
|
||||
return setsEqual(oldProps.state.mode, newProps.state.mode)
|
||||
&& oldProps.state.environment === newProps.state.environment // <-- could optimize this further
|
||||
&& oldProps.speed === newProps.speed
|
||||
&& oldProps.raiseUIEvent === newProps.raiseUIEvent
|
||||
}
|
||||
// export function comparePlantRenderProps(oldProps: PlantRenderProps<RT_Statechart>, newProps: PlantRenderProps<RT_Statechart>) {
|
||||
// return setsEqual(oldProps.state.mode, newProps.state.mode)
|
||||
// && oldProps.state.environment === newProps.state.environment // <-- could optimize this further
|
||||
// && oldProps.speed === newProps.speed
|
||||
// && oldProps.raiseUIEvent === newProps.raiseUIEvent
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ import trafficLightConcreteSyntax from "./model.json";
|
|||
import { parseStatechart } from "@/statecharts/parser";
|
||||
import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
||||
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 { useAudioContext } from "@/App/useAudioContext";
|
||||
import { memo, useEffect } from "react";
|
||||
import { objectsEqual } from "@/util/util";
|
||||
|
||||
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.")
|
||||
}
|
||||
|
||||
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(imgRedOverlay, {as: "image"});
|
||||
preload(imgYellowOverlay, {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);
|
||||
|
||||
// preloadAudio(sndAtmosphere);
|
||||
|
|
@ -89,10 +91,20 @@ export const TrafficLight = memo(function TrafficLight({state, speed, raiseUIEve
|
|||
<br/>
|
||||
<button onClick={() => raiseUIEvent({name: "policeInterrupt"})}>POLICE INTERRUPT</button>
|
||||
</>;
|
||||
}, comparePlantRenderProps);
|
||||
}, (oldProps, newProps) => {
|
||||
return objectsEqual(oldProps, newProps);
|
||||
});
|
||||
|
||||
const trafficLightPlantSpec: StatechartPlantSpec = {
|
||||
const trafficLightPlantSpec: StatechartPlantSpec<TrafficLightState> = {
|
||||
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,
|
||||
uiEvents: [
|
||||
{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,
|
||||
setTime: Dispatch<SetStateAction<TimeMode>>,
|
||||
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) => {
|
||||
setTrace(trace => trace && {
|
||||
...trace,
|
||||
|
|
@ -26,6 +47,7 @@ export function RTHistory({trace, setTrace, ast, setTime, showPlantTrace}: RTHis
|
|||
if (trace === null) {
|
||||
return <></>;
|
||||
}
|
||||
let j = 0;
|
||||
return trace.trace.map((item, i) => {
|
||||
const prevItem = trace.trace[i-1];
|
||||
// @ts-ignore
|
||||
|
|
@ -33,7 +55,16 @@ export function RTHistory({trace, setTrace, ast, setTime, showPlantTrace}: RTHis
|
|||
if (!showPlantTrace && isPlantStep) {
|
||||
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)})</>}</>;
|
||||
}
|
||||
|
||||
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") {
|
||||
// @ts-ignore
|
||||
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" : "")}
|
||||
onMouseDown={useCallback(() => onMouseDown(idx, item.simtime), [idx, item.simtime])}>
|
||||
<div>
|
||||
<div className={propertyClasses}/>
|
||||
 
|
||||
{formatTime(item.simtime)}
|
||||
 
|
||||
<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 width = Math.max(value.length, (paramName||"").length)*6;
|
||||
const shortcut = (i+1)%10;
|
||||
const KI = (i <= 10) ? KeyInfo : KeyInfoHidden;
|
||||
const KI = (i < 10) ? KeyInfo : KeyInfoHidden;
|
||||
return <div key={key} className="toolbarGroup">
|
||||
<KI keyInfo={<kbd>{shortcut}</kbd>} horizontal={true}>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ export const SpeedControl = memo(function SpeedControl({showKeys, timescale, set
|
|||
}, [onTimeScaleChange, timescale]);
|
||||
|
||||
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
// @ts-ignore
|
||||
if (["INPUT", "TEXTAREA", "SELECT"].includes(e.target?.tagName)) return;
|
||||
if (!e.ctrlKey) {
|
||||
if (e.key === "s") {
|
||||
e.preventDefault();
|
||||
|
|
|
|||
|
|
@ -118,6 +118,8 @@ export function useCopyPaste(makeCheckPoint: () => void, state: VisualEditorStat
|
|||
}, [setState]);
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
// @ts-ignore
|
||||
if (["INPUT", "TEXTAREA", "SELECT"].includes(e.target?.tagName)) return;
|
||||
if (e.key === "Delete") {
|
||||
// delete selection
|
||||
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>>) {
|
||||
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;
|
||||
height: 100%;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
body {
|
||||
|
|
@ -33,8 +34,6 @@ kbd:active { transform: translateY(1px); }
|
|||
input {
|
||||
/* border: solid blue 2px; */
|
||||
accent-color: rgba(0,0,255,0.2);
|
||||
|
||||
/* accent-color: blue; */
|
||||
}
|
||||
|
||||
::selection {
|
||||
|
|
@ -44,3 +43,4 @@ input {
|
|||
label {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ export type Statechart = {
|
|||
|
||||
uid2State: Map<string, ConcreteState|UnstableState>;
|
||||
|
||||
label2State: Map<string, ConcreteState>;
|
||||
|
||||
historyStates: HistoryState[];
|
||||
}
|
||||
|
||||
|
|
@ -88,6 +90,7 @@ export const emptyStatechart: Statechart = {
|
|||
internalEvents: [],
|
||||
outputEvents: new Set(),
|
||||
uid2State: new Map([["root", emptyRoot]]),
|
||||
label2State: new Map([]),
|
||||
historyStates: [],
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Action, EventTrigger, Expression, ParsedText } from "./label_ast";
|
|||
import { parse as parseLabel, SyntaxError } from "./label_parser";
|
||||
import { Connections } from "./detect_connections";
|
||||
import { HISTORY_RADIUS } from "../App/parameters";
|
||||
import { ConcreteSyntax, VisualEditorState } from "@/App/VisualEditor/VisualEditor";
|
||||
import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
||||
import { memoize } from "@/util/util";
|
||||
|
||||
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 label2State = new Map<string, ConcreteState>();
|
||||
const historyStates: HistoryState[] = [];
|
||||
|
||||
// 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") {
|
||||
// just append comments to their respective states
|
||||
if (!label2State.has(parsed.text)) {
|
||||
label2State.set(parsed.text, belongsToState);
|
||||
}
|
||||
belongsToState.comments.push([text.uid, parsed.text]);
|
||||
}
|
||||
}
|
||||
|
|
@ -384,6 +388,7 @@ export function parseStatechart(state: ConcreteSyntax, conns: Connections): [Sta
|
|||
internalEvents,
|
||||
outputEvents,
|
||||
uid2State,
|
||||
label2State,
|
||||
historyStates,
|
||||
}, errors];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,12 +194,13 @@ export function coupledExecution<T extends {[name: string]: any}>(models: {[name
|
|||
throw new Error("cannot make intTransition - timeAdvance is infinity");
|
||||
},
|
||||
extTransition: (simtime, c, e) => {
|
||||
if (!Object.hasOwn(conns, e.name)) {
|
||||
if (conns[e.name] === undefined) {
|
||||
console.warn('input event', e.name, 'goes to nowhere');
|
||||
return [[], c];
|
||||
}
|
||||
else {
|
||||
const [model, eventName] = conns[e.name];
|
||||
// console.log(conns);
|
||||
if (model !== null) {
|
||||
console.log('input event', e.name, 'goes to', `${model}.${eventName}`);
|
||||
const inputEvent: InputEvent = {
|
||||
|
|
|
|||
|
|
@ -83,4 +83,12 @@ export function mapsEqual<K,V>(a: Map<K,V>, b: Map<K,V>, cmp: (a: V, b: V) => bo
|
|||
}
|
||||
|
||||
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
|
||||
|
||||
- bugs
|
||||
interpreter: pseudo-state semantics is broken
|
||||
editing SC <-> Plant connections at runtime doesn't seem to work
|
||||
|
||||
- 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 error in bottom panel should highlight that rror in the SC
|
||||
- highlight selected shapes while making a selection
|
||||
|
||||
- highlight about-to-fire transitions
|
||||
|
||||
- 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
|
||||
- integrate undo-history with browser history (back/forward buttons)
|
||||
|
||||
- performance:
|
||||
maybe try this for rendering the execution trace:
|
||||
|
|
@ -69,7 +62,7 @@ TODO
|
|||
- multiverse execution history
|
||||
stable tree layout?
|
||||
https://pub.dev/packages/ploeg_tree_layout
|
||||
- local scopes
|
||||
- local variable scopes
|
||||
|
||||
for the assignment:
|
||||
*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