re-organize project structure a bit + add icons
This commit is contained in:
parent
3cb3ef91d2
commit
5e7b944978
24 changed files with 514 additions and 249 deletions
164
bun.lock
164
bun.lock
|
|
@ -4,6 +4,10 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "bun-react-template",
|
"name": "bun-react-template",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/styled": "^11.14.1",
|
||||||
|
"@fontsource/roboto": "^5.2.8",
|
||||||
|
"@mui/icons-material": "^7.3.4",
|
||||||
|
"@mui/material": "^7.3.4",
|
||||||
"@nick/lz4": "npm:@jsr/nick__lz4",
|
"@nick/lz4": "npm:@jsr/nick__lz4",
|
||||||
"react": "^19",
|
"react": "^19",
|
||||||
"react-dom": "^19",
|
"react-dom": "^19",
|
||||||
|
|
@ -16,26 +20,186 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"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/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=="],
|
||||||
|
|
||||||
|
"@mui/material": ["@mui/material@7.3.4", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@mui/core-downloads-tracker": "^7.3.4", "@mui/system": "^7.3.3", "@mui/types": "^7.4.7", "@mui/utils": "^7.3.3", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1", "react-is": "^19.1.1", "react-transition-group": "^4.4.5" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", "@mui/material-pigment-css": "^7.3.3", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled", "@mui/material-pigment-css", "@types/react"] }, "sha512-gEQL9pbJZZHT7lYJBKQCS723v1MGys2IFc94COXbUIyCTWa+qC77a7hUax4Yjd5ggEm35dk4AyYABpKKWC4MLw=="],
|
||||||
|
|
||||||
|
"@mui/private-theming": ["@mui/private-theming@7.3.3", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@mui/utils": "^7.3.3", "prop-types": "^15.8.1" }, "peerDependencies": { "@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-OJM+9nj5JIyPUvsZ5ZjaeC9PfktmK+W5YaVLToLR8L0lB/DGmv1gcKE43ssNLSvpoW71Hct0necfade6+kW3zQ=="],
|
||||||
|
|
||||||
|
"@mui/styled-engine": ["@mui/styled-engine@7.3.3", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@emotion/cache": "^11.14.0", "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "peerDependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled"] }, "sha512-CmFxvRJIBCEaWdilhXMw/5wFJ1+FT9f3xt+m2pPXhHPeVIbBg9MnMvNSJjdALvnQJMPw8jLhrUtXmN7QAZV2fw=="],
|
||||||
|
|
||||||
|
"@mui/system": ["@mui/system@7.3.3", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@mui/private-theming": "^7.3.3", "@mui/styled-engine": "^7.3.3", "@mui/types": "^7.4.7", "@mui/utils": "^7.3.3", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled", "@types/react"] }, "sha512-Lqq3emZr5IzRLKaHPuMaLBDVaGvxoh6z7HMWd1RPKawBM5uMRaQ4ImsmmgXWtwJdfZux5eugfDhXJUo2mliS8Q=="],
|
||||||
|
|
||||||
|
"@mui/types": ["@mui/types@7.4.7", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-8vVje9rdEr1rY8oIkYgP+Su5Kwl6ik7O3jQ0wl78JGSmiZhRHV+vkjooGdKD8pbtZbutXFVTWQYshu2b3sG9zw=="],
|
||||||
|
|
||||||
|
"@mui/utils": ["@mui/utils@7.3.3", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@mui/types": "^7.4.7", "@types/prop-types": "^15.7.15", "clsx": "^2.1.1", "prop-types": "^15.8.1", "react-is": "^19.1.1" }, "peerDependencies": { "@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-kwNAUh7bLZ7mRz9JZ+6qfRnnxbE4Zuc+RzXnhSpRSxjTlSTj7b4JxRLXpG+MVtPVtqks5k/XC8No1Vs3x4Z2gg=="],
|
||||||
|
|
||||||
"@nick/lz4": ["@jsr/nick__lz4@0.3.4", "https://npm.jsr.io/~/11/@jsr/nick__lz4/0.3.4.tgz", {}, "sha512-ZNc+8lCMC8D/cIa9GrSxRcEQC/MyThBOXXlg6rhrvAWSUcKPODwvscsVA+v1UugiBzfJ2dvQIZ/j8484PMadkg=="],
|
"@nick/lz4": ["@jsr/nick__lz4@0.3.4", "https://npm.jsr.io/~/11/@jsr/nick__lz4/0.3.4.tgz", {}, "sha512-ZNc+8lCMC8D/cIa9GrSxRcEQC/MyThBOXXlg6rhrvAWSUcKPODwvscsVA+v1UugiBzfJ2dvQIZ/j8484PMadkg=="],
|
||||||
|
|
||||||
|
"@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.2.23", "", { "dependencies": { "bun-types": "1.2.23" } }, "sha512-le8ueOY5b6VKYf19xT3McVbXqLqmxzPXHsQT/q9JHgikJ2X22wyTW3g3ohz2ZMnp7dod6aduIiq8A14Xyimm0A=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.6.2", "", { "dependencies": { "undici-types": "~7.13.0" } }, "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang=="],
|
"@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/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.0", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA=="],
|
||||||
|
|
||||||
"@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.0", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg=="],
|
||||||
|
|
||||||
|
"@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=="],
|
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
|
||||||
|
|
||||||
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
|
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
|
||||||
|
|
||||||
|
"react-is": ["react-is@19.2.0", "", {}, "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
"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=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@
|
||||||
"start": "NODE_ENV=production bun src/index.tsx"
|
"start": "NODE_ENV=production bun src/index.tsx"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/styled": "^11.14.1",
|
||||||
|
"@fontsource/roboto": "^5.2.8",
|
||||||
|
"@mui/icons-material": "^7.3.4",
|
||||||
|
"@mui/material": "^7.3.4",
|
||||||
"@nick/lz4": "npm:@jsr/nick__lz4",
|
"@nick/lz4": "npm:@jsr/nick__lz4",
|
||||||
"react": "^19",
|
"react": "^19",
|
||||||
"react-dom": "^19"
|
"react-dom": "^19"
|
||||||
|
|
|
||||||
58
src/App/AST.tsx
Normal file
58
src/App/AST.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { ConcreteState, stateDescription, Transition } from "../statecharts/abstract_syntax";
|
||||||
|
import { Action, Expression } from "../statecharts/label_ast";
|
||||||
|
|
||||||
|
export function ShowTransition(props: {transition: Transition}) {
|
||||||
|
return <>➔ {stateDescription(props.transition.tgt)}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ShowExpr(props: {expr: Expression}) {
|
||||||
|
if (props.expr.kind === "literal") {
|
||||||
|
return <>{props.expr.value}</>;
|
||||||
|
}
|
||||||
|
else if (props.expr.kind === "ref") {
|
||||||
|
return <>{props.expr.variable}</>;
|
||||||
|
}
|
||||||
|
else if (props.expr.kind === "unaryExpr") {
|
||||||
|
return <>{props.expr.operator}<ShowExpr expr={props.expr.expr}/></>;
|
||||||
|
}
|
||||||
|
else if (props.expr.kind === "binaryExpr") {
|
||||||
|
return <><ShowExpr expr={props.expr.lhs}/>{props.expr.operator}<ShowExpr expr={props.expr.rhs}/></>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ShowAction(props: {action: Action}) {
|
||||||
|
if (props.action.kind === "raise") {
|
||||||
|
return <>^{props.action.event}</>;
|
||||||
|
}
|
||||||
|
else if (props.action.kind === "assignment") {
|
||||||
|
return <>{props.action.lhs} = <ShowExpr expr={props.action.rhs}/>;</>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AST(props: {root: ConcreteState, transitions: Map<string, Transition[]>}) {
|
||||||
|
const description = stateDescription(props.root);
|
||||||
|
const outgoing = props.transitions.get(props.root.uid) || [];
|
||||||
|
|
||||||
|
return <details open={true}>
|
||||||
|
<summary>{props.root.kind}: {description}</summary>
|
||||||
|
|
||||||
|
{props.root.entryActions.length>0 &&
|
||||||
|
props.root.entryActions.map(action =>
|
||||||
|
<div> entry / <ShowAction action={action}/></div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{props.root.exitActions.length>0 &&
|
||||||
|
props.root.exitActions.map(action =>
|
||||||
|
<div> exit / <ShowAction action={action}/></div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{props.root.children.length>0 &&
|
||||||
|
props.root.children.map(child =>
|
||||||
|
<AST root={child} transitions={props.transitions} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{outgoing.length>0 &&
|
||||||
|
outgoing.map(transition => <> <ShowTransition transition={transition}/><br/></>)
|
||||||
|
}
|
||||||
|
</details>
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
.layoutVertical {
|
/* .layoutVertical {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -21,10 +21,22 @@
|
||||||
.content {
|
.content {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
} */
|
||||||
|
|
||||||
details {
|
details {
|
||||||
padding-left :10;
|
padding-left: 20;
|
||||||
|
/* margin-left: 30; */
|
||||||
|
}
|
||||||
|
summary {
|
||||||
|
margin-left: -20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.runtimeState {
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
|
padding-top: 2px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.runtimeState:hover {
|
.runtimeState:hover {
|
||||||
|
|
@ -47,3 +59,13 @@ details {
|
||||||
background-color:"#eee";
|
background-color:"#eee";
|
||||||
text-align: "right";
|
text-align: "right";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.toolbar > * {
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
.toolbar > input {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
267
src/App/App.tsx
267
src/App/App.tsx
|
|
@ -1,100 +1,29 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { ConcreteState, emptyStatechart, Statechart, stateDescription, Transition } from "../VisualEditor/ast";
|
import { emptyStatechart, Statechart } from "../statecharts/abstract_syntax";
|
||||||
import { handleInputEvent, initialize } from "../VisualEditor/interpreter";
|
import { handleInputEvent, initialize } from "../statecharts/interpreter";
|
||||||
import { TimerElapseEvent, Timers } from "@/VisualEditor/runtime_types";
|
import { BigStep, BigStepOutput } from "../statecharts/runtime_types";
|
||||||
import { Action, Expression } from "../VisualEditor/label_ast";
|
|
||||||
import { BigStep, BigStepOutput, Environment, Mode } from "../VisualEditor/runtime_types";
|
|
||||||
import { VisualEditor } from "../VisualEditor/VisualEditor";
|
import { VisualEditor } from "../VisualEditor/VisualEditor";
|
||||||
import { getSimTime, getWallClkDelay, setPaused, setRealtime, TimeMode } from "../VisualEditor/time";
|
import { getSimTime, getWallClkDelay, TimeMode } from "../statecharts/time";
|
||||||
|
|
||||||
import "../index.css";
|
import "../index.css";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
||||||
export function ShowTransition(props: {transition: Transition}) {
|
import { Box, Stack } from "@mui/material";
|
||||||
return <>➔ {stateDescription(props.transition.tgt)}</>;
|
import { TopPanel } from "./TopPanel";
|
||||||
}
|
import { RTHistory } from "./RTHistory";
|
||||||
|
import { AST } from "./AST";
|
||||||
export function ShowExpr(props: {expr: Expression}) {
|
|
||||||
if (props.expr.kind === "literal") {
|
|
||||||
return <>{props.expr.value}</>;
|
|
||||||
}
|
|
||||||
else if (props.expr.kind === "ref") {
|
|
||||||
return <>{props.expr.variable}</>;
|
|
||||||
}
|
|
||||||
else if (props.expr.kind === "unaryExpr") {
|
|
||||||
return <>{props.expr.operator}<ShowExpr expr={props.expr.expr}/></>;
|
|
||||||
}
|
|
||||||
else if (props.expr.kind === "binaryExpr") {
|
|
||||||
return <><ShowExpr expr={props.expr.lhs}/>{props.expr.operator}<ShowExpr expr={props.expr.rhs}/></>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ShowAction(props: {action: Action}) {
|
|
||||||
if (props.action.kind === "raise") {
|
|
||||||
return <>^{props.action.event}</>;
|
|
||||||
}
|
|
||||||
else if (props.action.kind === "assignment") {
|
|
||||||
return <>{props.action.lhs} = <ShowExpr expr={props.action.rhs}/>;</>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AST(props: {root: ConcreteState, transitions: Map<string, Transition[]>}) {
|
|
||||||
const description = stateDescription(props.root);
|
|
||||||
const outgoing = props.transitions.get(props.root.uid) || [];
|
|
||||||
|
|
||||||
return <details open={true}>
|
|
||||||
<summary>{props.root.kind}: {description}</summary>
|
|
||||||
|
|
||||||
{props.root.entryActions.length>0 &&
|
|
||||||
props.root.entryActions.map(action =>
|
|
||||||
<div> entry / <ShowAction action={action}/></div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{props.root.exitActions.length>0 &&
|
|
||||||
props.root.exitActions.map(action =>
|
|
||||||
<div> exit / <ShowAction action={action}/></div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{props.root.children.length>0 &&
|
|
||||||
props.root.children.map(child =>
|
|
||||||
<AST root={child} transitions={props.transitions} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{outgoing.length>0 &&
|
|
||||||
outgoing.map(transition => <> <ShowTransition transition={transition}/><br/></>)
|
|
||||||
}
|
|
||||||
</details>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function formatTime(timeMs: number) {
|
|
||||||
const leadingZeros = "00" + Math.floor(timeMs) % 1000;
|
|
||||||
const formatted = `${Math.floor(timeMs / 1000)}.${(leadingZeros).substring(leadingZeros.length-3)}`;
|
|
||||||
return formatted;
|
|
||||||
}
|
|
||||||
|
|
||||||
function compactTime(timeMs: number) {
|
|
||||||
if (timeMs % 1000 === 0) {
|
|
||||||
return `${timeMs / 1000}s`;
|
|
||||||
}
|
|
||||||
return `${timeMs} ms`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const [ast, setAST] = useState<Statechart>(emptyStatechart);
|
const [ast, setAST] = useState<Statechart>(emptyStatechart);
|
||||||
const [errors, setErrors] = useState<[string,string][]>([]);
|
const [errors, setErrors] = useState<[string,string][]>([]);
|
||||||
|
|
||||||
const [rt, setRT] = useState<BigStep[]>([]);
|
const [rt, setRT] = useState<BigStep[]>([]);
|
||||||
const [rtIdx, setRTIdx] = useState<number|null>(null);
|
const [rtIdx, setRTIdx] = useState<number|undefined>();
|
||||||
|
|
||||||
const [time, setTime] = useState<TimeMode>({kind: "paused", simtime: 0});
|
const [time, setTime] = useState<TimeMode>({kind: "paused", simtime: 0});
|
||||||
const [timescale, setTimescale] = useState(1);
|
|
||||||
const [displayTime, setDisplayTime] = useState("0.000");
|
|
||||||
|
|
||||||
|
function onInit() {
|
||||||
function restart() {
|
|
||||||
const config = initialize(ast);
|
const config = initialize(ast);
|
||||||
console.log('runtime: ', rt);
|
console.log('runtime: ', rt);
|
||||||
setRT([{inputEvent: null, simtime: 0, ...config}]);
|
setRT([{inputEvent: null, simtime: 0, ...config}]);
|
||||||
|
|
@ -102,14 +31,14 @@ export function App() {
|
||||||
setTime({kind: "paused", simtime: 0});
|
setTime({kind: "paused", simtime: 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear() {
|
function onClear() {
|
||||||
setRT([]);
|
setRT([]);
|
||||||
setRTIdx(null);
|
setRTIdx(undefined);
|
||||||
setTime({kind: "paused", simtime: 0});
|
setTime({kind: "paused", simtime: 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
function raise(inputEvent: string) {
|
function onRaise(inputEvent: string) {
|
||||||
if (rt.length>0 && rtIdx!==null && ast.inputEvents.has(inputEvent)) {
|
if (rt.length>0 && rtIdx!==undefined && ast.inputEvents.has(inputEvent)) {
|
||||||
const simtime = getSimTime(time, performance.now());
|
const simtime = getSimTime(time, performance.now());
|
||||||
const nextConfig = handleInputEvent(simtime, inputEvent, ast, rt[rtIdx]!);
|
const nextConfig = handleInputEvent(simtime, inputEvent, ast, rt[rtIdx]!);
|
||||||
appendNewConfig(inputEvent, simtime, nextConfig);
|
appendNewConfig(inputEvent, simtime, nextConfig);
|
||||||
|
|
@ -121,19 +50,9 @@ export function App() {
|
||||||
setRTIdx(rtIdx!+1);
|
setRTIdx(rtIdx!+1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDisplayedTime() {
|
|
||||||
const now = performance.now();
|
|
||||||
const timeMs = getSimTime(time, now);
|
|
||||||
setDisplayTime(formatTime(timeMs));
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(() => {
|
|
||||||
updateDisplayedTime();
|
|
||||||
}, 20);
|
|
||||||
|
|
||||||
let timeout: NodeJS.Timeout | undefined;
|
let timeout: NodeJS.Timeout | undefined;
|
||||||
if (rtIdx !== null) {
|
if (rtIdx !== undefined) {
|
||||||
const currentRt = rt[rtIdx]!;
|
const currentRt = rt[rtIdx]!;
|
||||||
const timers = currentRt.environment.get("_timers") || [];
|
const timers = currentRt.environment.get("_timers") || [];
|
||||||
if (timers.length > 0) {
|
if (timers.length > 0) {
|
||||||
|
|
@ -156,135 +75,45 @@ export function App() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval);
|
|
||||||
if (timeout) clearTimeout(timeout);
|
if (timeout) clearTimeout(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [time, rtIdx]);
|
}, [time, rtIdx]);
|
||||||
|
|
||||||
function onChangePaused(paused: boolean, wallclktime: number) {
|
return <Stack sx={{height:'100vh'}}>
|
||||||
setTime(time => {
|
{/* Top bar */}
|
||||||
if (paused) {
|
<Box
|
||||||
return setPaused(time, performance.now());
|
sx={{
|
||||||
}
|
display: "flex",
|
||||||
else {
|
borderBottom: 1,
|
||||||
return setRealtime(time, timescale, wallclktime);
|
borderColor: "divider",
|
||||||
}
|
alignItems: 'center',
|
||||||
});
|
}}>
|
||||||
updateDisplayedTime();
|
<TopPanel
|
||||||
}
|
rt={rtIdx === undefined ? undefined : rt[rtIdx]}
|
||||||
|
{...{ast, time, setTime, onInit, onClear, onRaise}}
|
||||||
function onTimeScaleChange(newValue: string, wallclktime: number) {
|
/>
|
||||||
const asFloat = parseFloat(newValue);
|
</Box>
|
||||||
if (Number.isNaN(asFloat)) {
|
<Stack direction="row" sx={{height:'calc(100vh - 32px)'}}>
|
||||||
return;
|
{/* main */}
|
||||||
}
|
<Box sx={{flexGrow:1, overflow:'auto'}}>
|
||||||
setTimescale(asFloat);
|
|
||||||
setTime(time => {
|
|
||||||
if (time.kind === "paused") {
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return setRealtime(time, asFloat, wallclktime);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function gotoRt(idx: number, timestamp: number) {
|
|
||||||
setRTIdx(idx);
|
|
||||||
setTime({kind: "paused", simtime: timestamp});
|
|
||||||
}
|
|
||||||
|
|
||||||
// timestamp of next timed transition, in simulated time
|
|
||||||
const timers: Timers = (rt[rtIdx!]?.environment.get("_timers") || []);
|
|
||||||
const nextTimedTransition: [number, TimerElapseEvent] | undefined = timers[0];
|
|
||||||
|
|
||||||
return <div className="layoutVertical">
|
|
||||||
<div className="panel">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className="panel">
|
|
||||||
<button onClick={restart}>(re)start</button>
|
|
||||||
<button onClick={clear} disabled={rtIdx===null}>clear</button>
|
|
||||||
 
|
|
||||||
{ast.inputEvents &&
|
|
||||||
<>raise
|
|
||||||
{[...ast.inputEvents].map(event => <button title="raise input event" disabled={rtIdx===null} onClick={() => raise(event)}>{event}</button>)}
|
|
||||||
 </>
|
|
||||||
}
|
|
||||||
<input type="radio" name="paused" id="radio-paused" checked={time.kind==="paused"} disabled={rtIdx===null} onChange={e => onChangePaused(e.target.checked, performance.now())}/>
|
|
||||||
<label htmlFor="radio-paused">paused</label>
|
|
||||||
<input type="radio" name="realtime" id="radio-realtime" checked={time.kind==="realtime"} disabled={rtIdx===null} onChange={e => onChangePaused(!e.target.checked, performance.now())}/>
|
|
||||||
<label htmlFor="radio-realtime">real time</label>
|
|
||||||
 
|
|
||||||
<label htmlFor="number-timescale">timescale</label>
|
|
||||||
<input title="controls how fast the simulation should run in real time mode - larger than 1 means: faster than wall-clock time" type="number" min={0} id="number-timescale" disabled={rtIdx===null} value={timescale} style={{width:40}} onChange={e => onTimeScaleChange(e.target.value, performance.now())}/>
|
|
||||||
 
|
|
||||||
<label htmlFor="time">time (s)</label>
|
|
||||||
<input title="the current simulated time" id="time" disabled={rtIdx===null} value={displayTime} readOnly={true} className="readonlyTextBox" />
|
|
||||||
{nextTimedTransition &&
|
|
||||||
<>
|
|
||||||
 
|
|
||||||
<label htmlFor="next-timeout">next timeout (s)</label>
|
|
||||||
<input id="next-timeout" disabled={rtIdx===null} value={formatTime(nextTimedTransition[0])} readOnly={true} className="readonlyTextBox"/>
|
|
||||||
<button title="advance time to the next timer elapse" onClick={() => {
|
|
||||||
const now = performance.now();
|
|
||||||
setTime(time => {
|
|
||||||
if (time.kind === "paused") {
|
|
||||||
return {kind: "paused", simtime: nextTimedTransition[0]};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return {kind: "realtime", scale: time.scale, since: {simtime: nextTimedTransition[0], wallclktime: now}};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}>advance</button>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="layout">
|
|
||||||
<main className="content">
|
|
||||||
<VisualEditor {...{ast, setAST, rt: rt.at(rtIdx!), setRT, errors, setErrors}}/>
|
<VisualEditor {...{ast, setAST, rt: rt.at(rtIdx!), setRT, errors, setErrors}}/>
|
||||||
</main>
|
</Box>
|
||||||
<aside className="sidebar">
|
{/* right sidebar */}
|
||||||
<AST {...ast}/>
|
<Box
|
||||||
{rt.map((rt, idx) => <><hr/><div className={"runtimeState"+(idx===rtIdx?" active":"")} onClick={() => gotoRt(idx, rt.simtime)}>
|
sx={{
|
||||||
<div>({formatTime(rt.simtime)}, {rt.inputEvent || "<init>"})</div>
|
borderLeft: 1,
|
||||||
<ShowMode mode={rt.mode} statechart={ast}/>
|
borderColor: "divider",
|
||||||
<ShowEnvironment environment={rt.environment}/>
|
flex: '0 0 content',
|
||||||
{rt.outputEvents.length>0 && <div>
|
paddingRight: 1,
|
||||||
{rt.outputEvents.map((e:string) => '^'+e).join(', ')}
|
paddingLeft: 1,
|
||||||
</div>}
|
}}>
|
||||||
</div></>)}
|
<AST {...ast}/>
|
||||||
</aside>
|
<hr/>
|
||||||
</div>
|
<RTHistory {...{ast, rt, rtIdx, setTime, setRTIdx}}/>
|
||||||
</div>;
|
</Box>
|
||||||
}
|
</Stack>
|
||||||
|
</Stack>;
|
||||||
function ShowEnvironment(props: {environment: Environment}) {
|
|
||||||
return <div>{[...props.environment.entries()]
|
|
||||||
.filter(([variable]) => !variable.startsWith('_'))
|
|
||||||
.map(([variable,value]) =>
|
|
||||||
`${variable}: ${value}`
|
|
||||||
).join(', ')}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ShowMode(props: {mode: Mode, statechart: Statechart}) {
|
|
||||||
const activeLeafs = getActiveLeafs(props.mode, props.statechart);
|
|
||||||
return <div>mode: {[...activeLeafs].map(uid =>
|
|
||||||
stateDescription(props.statechart.uid2State.get(uid)!)).join(", ")}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getActiveLeafs(mode: Mode, sc: Statechart) {
|
|
||||||
const toDelete = [];
|
|
||||||
for (const stateA of mode) {
|
|
||||||
for (const stateB of mode) {
|
|
||||||
if (sc.uid2State.get(stateA)!.parent === sc.uid2State.get(stateB)) {
|
|
||||||
toDelete.push(stateB);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mode.difference(new Set(toDelete));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
||||||
57
src/App/RTHistory.tsx
Normal file
57
src/App/RTHistory.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
import { Statechart, stateDescription } from "../statecharts/abstract_syntax";
|
||||||
|
import { BigStep, Environment, Mode } from "../statecharts/runtime_types";
|
||||||
|
import { formatTime } from "./util";
|
||||||
|
import { TimeMode } from "../statecharts/time";
|
||||||
|
|
||||||
|
type RTHistoryProps = {
|
||||||
|
rt: BigStep[],
|
||||||
|
rtIdx: number | undefined,
|
||||||
|
ast: Statechart,
|
||||||
|
setRTIdx: Dispatch<SetStateAction<number|undefined>>,
|
||||||
|
setTime: Dispatch<SetStateAction<TimeMode>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RTHistory({rt, rtIdx, ast, setRTIdx, setTime}: RTHistoryProps) {
|
||||||
|
function gotoRt(idx: number, timestamp: number) {
|
||||||
|
setRTIdx(idx);
|
||||||
|
setTime({kind: "paused", simtime: timestamp});
|
||||||
|
}
|
||||||
|
|
||||||
|
return rt.map((rt, idx) => <>
|
||||||
|
<div className={"runtimeState"+(idx===rtIdx?" active":"")} onClick={() => gotoRt(idx, rt.simtime)}>
|
||||||
|
<div>({formatTime(rt.simtime)}, {rt.inputEvent || "<init>"})</div>
|
||||||
|
<ShowMode mode={rt.mode} statechart={ast}/>
|
||||||
|
<ShowEnvironment environment={rt.environment}/>
|
||||||
|
{rt.outputEvents.length>0 && <div>
|
||||||
|
{rt.outputEvents.map((e:string) => '^'+e).join(', ')}
|
||||||
|
</div>}
|
||||||
|
</div></>);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ShowEnvironment(props: {environment: Environment}) {
|
||||||
|
return <div>{[...props.environment.entries()]
|
||||||
|
.filter(([variable]) => !variable.startsWith('_'))
|
||||||
|
.map(([variable,value]) =>
|
||||||
|
`${variable}: ${value}`
|
||||||
|
).join(', ')}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShowMode(props: {mode: Mode, statechart: Statechart}) {
|
||||||
|
const activeLeafs = getActiveLeafs(props.mode, props.statechart);
|
||||||
|
return <div>mode: {[...activeLeafs].map(uid =>
|
||||||
|
stateDescription(props.statechart.uid2State.get(uid)!)).join(", ")}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveLeafs(mode: Mode, sc: Statechart) {
|
||||||
|
const toDelete = [];
|
||||||
|
for (const stateA of mode) {
|
||||||
|
for (const stateB of mode) {
|
||||||
|
if (sc.uid2State.get(stateA)!.parent === sc.uid2State.get(stateB)) {
|
||||||
|
toDelete.push(stateB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mode.difference(new Set(toDelete));
|
||||||
|
}
|
||||||
127
src/App/TopPanel.tsx
Normal file
127
src/App/TopPanel.tsx
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
||||||
|
import { BigStep, TimerElapseEvent, Timers } from "../statecharts/runtime_types";
|
||||||
|
import { getSimTime, setPaused, setRealtime, TimeMode } from "../statecharts/time";
|
||||||
|
import { Statechart } from "../statecharts/abstract_syntax";
|
||||||
|
|
||||||
|
import CachedIcon from '@mui/icons-material/Cached';
|
||||||
|
import ClearIcon from '@mui/icons-material/Clear';
|
||||||
|
import PauseIcon from '@mui/icons-material/Pause';
|
||||||
|
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||||
|
import BoltIcon from '@mui/icons-material/Bolt';
|
||||||
|
import SkipNextIcon from '@mui/icons-material/SkipNext';
|
||||||
|
import { formatTime } from "./util";
|
||||||
|
|
||||||
|
export type TopPanelProps = {
|
||||||
|
rt?: BigStep,
|
||||||
|
time: TimeMode,
|
||||||
|
setTime: Dispatch<SetStateAction<TimeMode>>,
|
||||||
|
onInit: () => void,
|
||||||
|
onClear: () => void,
|
||||||
|
onRaise: (e: string) => void,
|
||||||
|
ast: Statechart,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast}: TopPanelProps) {
|
||||||
|
const [displayTime, setDisplayTime] = useState("0.000");
|
||||||
|
const [timescale, setTimescale] = useState(1);
|
||||||
|
|
||||||
|
function updateDisplayedTime() {
|
||||||
|
const now = performance.now();
|
||||||
|
const timeMs = getSimTime(time, now);
|
||||||
|
setDisplayTime(formatTime(timeMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
updateDisplayedTime();
|
||||||
|
}, 20);
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
}, [time]);
|
||||||
|
|
||||||
|
function onChangePaused(paused: boolean, wallclktime: number) {
|
||||||
|
setTime(time => {
|
||||||
|
if (paused) {
|
||||||
|
return setPaused(time, performance.now());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return setRealtime(time, timescale, wallclktime);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateDisplayedTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTimeScaleChange(newValue: string, wallclktime: number) {
|
||||||
|
const asFloat = parseFloat(newValue);
|
||||||
|
if (Number.isNaN(asFloat)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const maxed = Math.min(asFloat, 64);
|
||||||
|
const mined = Math.max(maxed, 1/64);
|
||||||
|
setTimescale(mined);
|
||||||
|
setTime(time => {
|
||||||
|
if (time.kind === "paused") {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return setRealtime(time, mined, wallclktime);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// timestamp of next timed transition, in simulated time
|
||||||
|
const timers: Timers = (rt?.environment.get("_timers") || []);
|
||||||
|
const nextTimedTransition: [number, TimerElapseEvent] | undefined = timers[0];
|
||||||
|
|
||||||
|
return <div className="toolbar">
|
||||||
|
<button title="(re)initialize simulation" onClick={onInit} ><CachedIcon fontSize="small"/></button>
|
||||||
|
<button title="clear the simulation" onClick={onClear} disabled={!rt}><ClearIcon fontSize="small"/></button>
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<button title="pause the simulation" disabled={!rt || time.kind==="paused"} onClick={() => onChangePaused(true, performance.now())}><PauseIcon fontSize="small"/></button>
|
||||||
|
<button title="run the simulation in real time" disabled={!rt || time.kind==="realtime"} onClick={() => onChangePaused(false, performance.now())}><PlayArrowIcon fontSize="small"/></button>
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<label htmlFor="number-timescale">timescale</label>
|
||||||
|
<button title="slower" onClick={() => onTimeScaleChange((timescale/2).toString(), performance.now())}>÷2</button>
|
||||||
|
<input title="controls how fast the simulation should run in real time mode - larger than 1 means: faster than wall-clock time" id="number-timescale" value={timescale.toFixed(3)} style={{width:40}} readOnly onChange={e => onTimeScaleChange(e.target.value, performance.now())}/>
|
||||||
|
<button title="faster" onClick={() => onTimeScaleChange((timescale*2).toString(), performance.now())}>×2</button>
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
{ast.inputEvents &&
|
||||||
|
<>
|
||||||
|
{[...ast.inputEvents].map(event => <button title={`raise input event '${event}'`} disabled={!rt} onClick={() => onRaise(event)}><BoltIcon fontSize="small"/> {event}</button>)}
|
||||||
|
 </>
|
||||||
|
}
|
||||||
|
|
||||||
|
{/* <ToggleButtonGroup value={time.kind} exclusive onChange={(_,newValue) => onChangePaused(newValue==="paused", performance.now())} size="small">
|
||||||
|
<ToggleButton disableRipple value="paused" disabled={!rt}><PauseIcon/></ToggleButton>
|
||||||
|
<ToggleButton disableRipple value="realtime" disabled={!rt}><PlayArrowIcon/></ToggleButton>
|
||||||
|
</ToggleButtonGroup> */}
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<label htmlFor="time">time (s)</label>
|
||||||
|
<input title="the current simulated time" id="time" disabled={!rt} value={displayTime} readOnly={true} className="readonlyTextBox" />
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
<label htmlFor="next-timeout">next (s)</label>
|
||||||
|
<input title="next point in simulated time where a timed transition may fire" id="next-timeout" disabled={!rt} value={nextTimedTransition ? formatTime(nextTimedTransition[0]) : '+inf'} readOnly={true} className="readonlyTextBox"/>
|
||||||
|
<button title="advance time just enough for the next timer to elapse" disabled={nextTimedTransition===undefined} onClick={() => {
|
||||||
|
const now = performance.now();
|
||||||
|
setTime(time => {
|
||||||
|
if (time.kind === "paused") {
|
||||||
|
return {kind: "paused", simtime: nextTimedTransition[0]};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return {kind: "realtime", scale: time.scale, since: {simtime: nextTimedTransition[0], wallclktime: now}};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}><SkipNextIcon fontSize="small"/></button>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
13
src/App/util.ts
Normal file
13
src/App/util.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
export function formatTime(timeMs: number) {
|
||||||
|
const leadingZeros = "00" + Math.floor(timeMs) % 1000;
|
||||||
|
const formatted = `${Math.floor(timeMs / 1000)}.${(leadingZeros).substring(leadingZeros.length-3)}`;
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compactTime(timeMs: number) {
|
||||||
|
if (timeMs % 1000 === 0) {
|
||||||
|
return `${timeMs / 1000}s`;
|
||||||
|
}
|
||||||
|
return `${timeMs} ms`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
.svgCanvas {
|
.svgCanvas {
|
||||||
cursor: crosshair;
|
cursor: crosshair;
|
||||||
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
text, text.highlight {
|
text, text.highlight {
|
||||||
|
|
@ -102,6 +103,7 @@ text.highlight {
|
||||||
|
|
||||||
.rountangle.or {
|
.rountangle.or {
|
||||||
stroke-dasharray: 7 6;
|
stroke-dasharray: 7 6;
|
||||||
|
fill: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ import { ArcDirection, Line2D, Rect2D, Vec2D, addV2D, arcDirection, area, euclid
|
||||||
import "./VisualEditor.css";
|
import "./VisualEditor.css";
|
||||||
|
|
||||||
import { getBBoxInSvgCoords } from "./svg_helper";
|
import { getBBoxInSvgCoords } from "./svg_helper";
|
||||||
import { VisualEditorState, Rountangle, emptyState, Arrow, ArrowPart, RountanglePart, findNearestRountangleSide, findNearestArrow, Text, findRountangle } from "./editor_types";
|
import { VisualEditorState, Rountangle, emptyState, Arrow, ArrowPart, RountanglePart, findNearestRountangleSide, findNearestArrow, Text, findRountangle } from "../statecharts/concrete_syntax";
|
||||||
import { parseStatechart } from "./parser";
|
import { parseStatechart } from "../statecharts/parser";
|
||||||
import { CORNER_HELPER_OFFSET, CORNER_HELPER_RADIUS, MIN_ROUNTANGLE_SIZE, ROUNTANGLE_RADIUS } from "./parameters";
|
import { CORNER_HELPER_OFFSET, CORNER_HELPER_RADIUS, MIN_ROUNTANGLE_SIZE, ROUNTANGLE_RADIUS } from "./parameters";
|
||||||
|
|
||||||
import * as lz4 from "@nick/lz4";
|
import * as lz4 from "@nick/lz4";
|
||||||
import { RT_Statechart } from "./runtime_types";
|
import { BigStep, RT_Statechart } from "../statecharts/runtime_types";
|
||||||
import { Statechart } from "./ast";
|
import { Statechart } from "../statecharts/abstract_syntax";
|
||||||
|
|
||||||
|
|
||||||
type DraggingState = {
|
type DraggingState = {
|
||||||
|
|
@ -50,15 +50,13 @@ export const sides: [RountanglePart, (r:Rect2D)=>Line2D][] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
type VisualEditorProps = {
|
type VisualEditorProps = {
|
||||||
ast: Statechart,
|
|
||||||
setAST: Dispatch<SetStateAction<Statechart>>,
|
setAST: Dispatch<SetStateAction<Statechart>>,
|
||||||
rt: RT_Statechart|null,
|
rt: BigStep|undefined,
|
||||||
setRT: Dispatch<SetStateAction<RT_Statechart|null>>,
|
|
||||||
errors: [string,string][],
|
errors: [string,string][],
|
||||||
setErrors: Dispatch<SetStateAction<[string,string][]>>,
|
setErrors: Dispatch<SetStateAction<[string,string][]>>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function VisualEditor({ast, setAST, rt, setRT, errors, setErrors}: VisualEditorProps) {
|
export function VisualEditor({setAST, rt, errors, setErrors}: VisualEditorProps) {
|
||||||
const [historyState, setHistoryState] = useState<HistoryState>({current: emptyState, history: [], future: []});
|
const [historyState, setHistoryState] = useState<HistoryState>({current: emptyState, history: [], future: []});
|
||||||
|
|
||||||
const state = historyState.current;
|
const state = historyState.current;
|
||||||
|
|
@ -145,7 +143,7 @@ export function VisualEditor({ast, setAST, rt, setRT, errors, setErrors}: Visual
|
||||||
window.location.hash = "#"+compressedStateString;
|
window.location.hash = "#"+compressedStateString;
|
||||||
|
|
||||||
const [statechart, errors] = parseStatechart(state);
|
const [statechart, errors] = parseStatechart(state);
|
||||||
console.log('statechart: ', statechart, 'errors:', errors);
|
// console.log('statechart: ', statechart, 'errors:', errors);
|
||||||
setErrors(errors);
|
setErrors(errors);
|
||||||
setAST(statechart);
|
setAST(statechart);
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { RountanglePart } from "./editor_types";
|
import { RountanglePart } from "../statecharts/concrete_syntax";
|
||||||
|
|
||||||
export type Vec2D = {
|
export type Vec2D = {
|
||||||
x: number;
|
x: number;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="icon" type="image/svg+xml" href="./logo.svg" />
|
<!-- <link rel="icon" type="image/svg+xml" href="./logo.svg" /> -->
|
||||||
<title>StateBuddy</title>
|
<title>StateBuddy</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
<svg id="Bun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 70"><title>Bun Logo</title><path id="Shadow" d="M71.09,20.74c-.16-.17-.33-.34-.5-.5s-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5A26.46,26.46,0,0,1,75.5,35.7c0,16.57-16.82,30.05-37.5,30.05-11.58,0-21.94-4.23-28.83-10.86l.5.5.5.5.5.5.5.5.5.5.5.5.5.5C19.55,65.3,30.14,69.75,42,69.75c20.68,0,37.5-13.48,37.5-30C79.5,32.69,76.46,26,71.09,20.74Z"/><g id="Body"><path id="Background" d="M73,35.7c0,15.21-15.67,27.54-35,27.54S3,50.91,3,35.7C3,26.27,9,17.94,18.22,13S33.18,3,38,3s8.94,4.13,19.78,10C67,17.94,73,26.27,73,35.7Z" style="fill:#fbf0df"/><path id="Bottom_Shadow" data-name="Bottom Shadow" d="M73,35.7a21.67,21.67,0,0,0-.8-5.78c-2.73,33.3-43.35,34.9-59.32,24.94A40,40,0,0,0,38,63.24C57.3,63.24,73,50.89,73,35.7Z" style="fill:#f6dece"/><path id="Light_Shine" data-name="Light Shine" d="M24.53,11.17C29,8.49,34.94,3.46,40.78,3.45A9.29,9.29,0,0,0,38,3c-2.42,0-5,1.25-8.25,3.13-1.13.66-2.3,1.39-3.54,2.15-2.33,1.44-5,3.07-8,4.7C8.69,18.13,3,26.62,3,35.7c0,.4,0,.8,0,1.19C9.06,15.48,20.07,13.85,24.53,11.17Z" style="fill:#fffefc"/><path id="Top" d="M35.12,5.53A16.41,16.41,0,0,1,29.49,18c-.28.25-.06.73.3.59,3.37-1.31,7.92-5.23,6-13.14C35.71,5,35.12,5.12,35.12,5.53Zm2.27,0A16.24,16.24,0,0,1,39,19c-.12.35.31.65.55.36C41.74,16.56,43.65,11,37.93,5,37.64,4.74,37.19,5.14,37.39,5.49Zm2.76-.17A16.42,16.42,0,0,1,47,17.12a.33.33,0,0,0,.65.11c.92-3.49.4-9.44-7.17-12.53C40.08,4.54,39.82,5.08,40.15,5.32ZM21.69,15.76a16.94,16.94,0,0,0,10.47-9c.18-.36.75-.22.66.18-1.73,8-7.52,9.67-11.12,9.45C21.32,16.4,21.33,15.87,21.69,15.76Z" style="fill:#ccbea7;fill-rule:evenodd"/><path id="Outline" d="M38,65.75C17.32,65.75.5,52.27.5,35.7c0-10,6.18-19.33,16.53-24.92,3-1.6,5.57-3.21,7.86-4.62,1.26-.78,2.45-1.51,3.6-2.19C32,1.89,35,.5,38,.5s5.62,1.2,8.9,3.14c1,.57,2,1.19,3.07,1.87,2.49,1.54,5.3,3.28,9,5.27C69.32,16.37,75.5,25.69,75.5,35.7,75.5,52.27,58.68,65.75,38,65.75ZM38,3c-2.42,0-5,1.25-8.25,3.13-1.13.66-2.3,1.39-3.54,2.15-2.33,1.44-5,3.07-8,4.7C8.69,18.13,3,26.62,3,35.7,3,50.89,18.7,63.25,38,63.25S73,50.89,73,35.7C73,26.62,67.31,18.13,57.78,13,54,11,51.05,9.12,48.66,7.64c-1.09-.67-2.09-1.29-3-1.84C42.63,4,40.42,3,38,3Z"/></g><g id="Mouth"><g id="Background-2" data-name="Background"><path d="M45.05,43a8.93,8.93,0,0,1-2.92,4.71,6.81,6.81,0,0,1-4,1.88A6.84,6.84,0,0,1,34,47.71,8.93,8.93,0,0,1,31.12,43a.72.72,0,0,1,.8-.81H44.26A.72.72,0,0,1,45.05,43Z" style="fill:#b71422"/></g><g id="Tongue"><path id="Background-3" data-name="Background" d="M34,47.79a6.91,6.91,0,0,0,4.12,1.9,6.91,6.91,0,0,0,4.11-1.9,10.63,10.63,0,0,0,1-1.07,6.83,6.83,0,0,0-4.9-2.31,6.15,6.15,0,0,0-5,2.78C33.56,47.4,33.76,47.6,34,47.79Z" style="fill:#ff6164"/><path id="Outline-2" data-name="Outline" d="M34.16,47a5.36,5.36,0,0,1,4.19-2.08,6,6,0,0,1,4,1.69c.23-.25.45-.51.66-.77a7,7,0,0,0-4.71-1.93,6.36,6.36,0,0,0-4.89,2.36A9.53,9.53,0,0,0,34.16,47Z"/></g><path id="Outline-3" data-name="Outline" d="M38.09,50.19a7.42,7.42,0,0,1-4.45-2,9.52,9.52,0,0,1-3.11-5.05,1.2,1.2,0,0,1,.26-1,1.41,1.41,0,0,1,1.13-.51H44.26a1.44,1.44,0,0,1,1.13.51,1.19,1.19,0,0,1,.25,1h0a9.52,9.52,0,0,1-3.11,5.05A7.42,7.42,0,0,1,38.09,50.19Zm-6.17-7.4c-.16,0-.2.07-.21.09a8.29,8.29,0,0,0,2.73,4.37A6.23,6.23,0,0,0,38.09,49a6.28,6.28,0,0,0,3.65-1.73,8.3,8.3,0,0,0,2.72-4.37.21.21,0,0,0-.2-.09Z"/></g><g id="Face"><ellipse id="Right_Blush" data-name="Right Blush" cx="53.22" cy="40.18" rx="5.85" ry="3.44" style="fill:#febbd0"/><ellipse id="Left_Bluch" data-name="Left Bluch" cx="22.95" cy="40.18" rx="5.85" ry="3.44" style="fill:#febbd0"/><path id="Eyes" d="M25.7,38.8a5.51,5.51,0,1,0-5.5-5.51A5.51,5.51,0,0,0,25.7,38.8Zm24.77,0A5.51,5.51,0,1,0,45,33.29,5.5,5.5,0,0,0,50.47,38.8Z" style="fill-rule:evenodd"/><path id="Iris" d="M24,33.64a2.07,2.07,0,1,0-2.06-2.07A2.07,2.07,0,0,0,24,33.64Zm24.77,0a2.07,2.07,0,1,0-2.06-2.07A2.07,2.07,0,0,0,48.75,33.64Z" style="fill:#fff;fill-rule:evenodd"/></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.8 KiB |
|
|
@ -1,8 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-11.5 -10.23174 23 20.46348">
|
|
||||||
<circle cx="0" cy="0" r="2.05" fill="#61dafb"/>
|
|
||||||
<g stroke="#61dafb" stroke-width="1" fill="none">
|
|
||||||
<ellipse rx="11" ry="4.2"/>
|
|
||||||
<ellipse rx="11" ry="4.2" transform="rotate(60)"/>
|
|
||||||
<ellipse rx="11" ry="4.2" transform="rotate(120)"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 338 B |
|
|
@ -1,6 +1,6 @@
|
||||||
import { Rect2D, Vec2D, Line2D, euclideanDistance, intersectLines, isWithin, lineBBox, isEntirelyWithin } from "./geometry";
|
import { Rect2D, Vec2D, Line2D, euclideanDistance, intersectLines, isWithin, lineBBox, isEntirelyWithin } from "../VisualEditor/geometry";
|
||||||
import { ARROW_SNAP_THRESHOLD, TEXT_SNAP_THRESHOLD } from "./parameters";
|
import { ARROW_SNAP_THRESHOLD, TEXT_SNAP_THRESHOLD } from "../VisualEditor/parameters";
|
||||||
import { sides } from "./VisualEditor";
|
import { sides } from "../VisualEditor/VisualEditor";
|
||||||
|
|
||||||
export type Rountangle = {
|
export type Rountangle = {
|
||||||
uid: string;
|
uid: string;
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { evalExpr } from "./actionlang_interpreter";
|
import { evalExpr } from "./actionlang_interpreter";
|
||||||
import { computeArena, ConcreteState, getDescendants, isOverlapping, OrState, Statechart, stateDescription, Transition } from "./ast";
|
import { computeArena, ConcreteState, getDescendants, isOverlapping, OrState, Statechart, stateDescription, Transition } from "./abstract_syntax";
|
||||||
import { Action } from "./label_ast";
|
import { Action } from "./label_ast";
|
||||||
import { Environment, RaisedEvents, Mode, RT_Statechart, initialRaised, BigStepOutput, TimerElapseEvent, Timers } from "./runtime_types";
|
import { Environment, RaisedEvents, Mode, RT_Statechart, initialRaised, BigStepOutput, TimerElapseEvent, Timers } from "./runtime_types";
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ConcreteState, OrState, Statechart, Transition } from "./ast";
|
import { ConcreteState, OrState, Statechart, Transition } from "./abstract_syntax";
|
||||||
import { findNearestArrow, findNearestRountangleSide, findRountangle, Rountangle, VisualEditorState } from "./editor_types";
|
import { findNearestArrow, findNearestRountangleSide, findRountangle, Rountangle, VisualEditorState } from "./concrete_syntax";
|
||||||
import { isEntirelyWithin } from "./geometry";
|
import { isEntirelyWithin } from "../VisualEditor/geometry";
|
||||||
import { Action, Expression, ParsedText } from "./label_ast";
|
import { Action, Expression, ParsedText } from "./label_ast";
|
||||||
|
|
||||||
import { parse as parseLabel, SyntaxError } from "./label_parser";
|
import { parse as parseLabel, SyntaxError } from "./label_parser";
|
||||||
Loading…
Add table
Add a link
Reference in a new issue