1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-18 01:50:50 +00:00

feat: extension list mostly works

This commit is contained in:
Ash Keel 2023-01-31 12:20:27 +01:00
parent b3dea0fe53
commit 196ab184e6
No known key found for this signature in database
GPG key ID: BAD8D93E7314ED3E
9 changed files with 492 additions and 180 deletions

View file

@ -23,9 +23,9 @@
"@radix-ui/react-toggle-group": "^1.0.2",
"@radix-ui/react-toolbar": "^1.0.2",
"@redux-devtools/extension": "^3.2.5",
"@reduxjs/toolkit": "^1.9.1",
"@reduxjs/toolkit": "^1.9.2",
"@stitches/react": "^1.2.8",
"@strimertul/kilovolt-client": "^7.0.1",
"@strimertul/kilovolt-client": "^7.1.0",
"@types/node": "^18.11.18",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
@ -41,14 +41,14 @@
"react-router-dom": "^6.8.0",
"redux-thunk": "^2.4.2",
"sass": "^1.57.1",
"typescript": "^4.9.4",
"typescript": "^4.9.5",
"vite": "^4.0.4",
"vite-tsconfig-paths": "^4.0.5"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.49.0",
"eslint": "^8.32.0",
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0",
"eslint": "^8.33.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.6.0",
"eslint-import-resolver-typescript": "^3.5.3",
@ -1470,9 +1470,9 @@
}
},
"node_modules/@reduxjs/toolkit": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.1.tgz",
"integrity": "sha512-HikrdY+IDgRfRYlCTGUQaiCxxDDgM1mQrRbZ6S1HFZX5ZYuJ4o8EstNmhTwHdPl2rTmLxzwSu0b3AyeyTlR+RA==",
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.2.tgz",
"integrity": "sha512-5ZAZ7hwAKWSii5T6NTPmgIBUqyVdlDs+6JjThz6J6dmHLDm6zCzv2OjHIFAi3Vvs1qjmXU0bm6eBojukYXjVMQ==",
"dependencies": {
"immer": "^9.0.16",
"redux": "^4.2.0",
@ -1509,9 +1509,9 @@
}
},
"node_modules/@strimertul/kilovolt-client": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@strimertul/kilovolt-client/-/kilovolt-client-7.0.1.tgz",
"integrity": "sha512-G9xLgufk1aRmrzUnxZeQHbSLewEy199WAox4zGolWf9GdlKREKcfcLYWq/idxvTacRusRuex2Z7+Gl/lXh7Swg=="
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@strimertul/kilovolt-client/-/kilovolt-client-7.1.0.tgz",
"integrity": "sha512-iZxCVRrJZNsnu6N2T/G83lwt3UBOHD0fCzaJRQsTBg6Ybdc8oi9v5/AqgZfA+mlif/iL0X5gkukKzSa1nAmg/w=="
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
@ -1579,15 +1579,16 @@
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.49.0.tgz",
"integrity": "sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.50.0.tgz",
"integrity": "sha512-vwksQWSFZiUhgq3Kv7o1Jcj0DUNylwnIlGvKvLLYsq8pAWha6/WCnXUeaSoNNha/K7QSf2+jvmkxggC1u3pIwQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/type-utils": "5.49.0",
"@typescript-eslint/utils": "5.49.0",
"@typescript-eslint/scope-manager": "5.50.0",
"@typescript-eslint/type-utils": "5.50.0",
"@typescript-eslint/utils": "5.50.0",
"debug": "^4.3.4",
"grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
"regexpp": "^3.2.0",
@ -1612,14 +1613,14 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.49.0.tgz",
"integrity": "sha512-veDlZN9mUhGqU31Qiv2qEp+XrJj5fgZpJ8PW30sHU+j/8/e5ruAhLaVDAeznS7A7i4ucb/s8IozpDtt9NqCkZg==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.50.0.tgz",
"integrity": "sha512-KCcSyNaogUDftK2G9RXfQyOCt51uB5yqC6pkUYqhYh8Kgt+DwR5M0EwEAxGPy/+DH6hnmKeGsNhiZRQxjH71uQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/typescript-estree": "5.49.0",
"@typescript-eslint/scope-manager": "5.50.0",
"@typescript-eslint/types": "5.50.0",
"@typescript-eslint/typescript-estree": "5.50.0",
"debug": "^4.3.4"
},
"engines": {
@ -1639,13 +1640,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz",
"integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz",
"integrity": "sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.49.0"
"@typescript-eslint/types": "5.50.0",
"@typescript-eslint/visitor-keys": "5.50.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -1656,13 +1657,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.49.0.tgz",
"integrity": "sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.50.0.tgz",
"integrity": "sha512-dcnXfZ6OGrNCO7E5UY/i0ktHb7Yx1fV6fnQGGrlnfDhilcs6n19eIRcvLBqx6OQkrPaFlDPk3OJ0WlzQfrV0bQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "5.49.0",
"@typescript-eslint/utils": "5.49.0",
"@typescript-eslint/typescript-estree": "5.50.0",
"@typescript-eslint/utils": "5.50.0",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
@ -1683,9 +1684,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz",
"integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz",
"integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -1696,13 +1697,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz",
"integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz",
"integrity": "sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.49.0",
"@typescript-eslint/types": "5.50.0",
"@typescript-eslint/visitor-keys": "5.50.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@ -1723,16 +1724,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz",
"integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.50.0.tgz",
"integrity": "sha512-v/AnUFImmh8G4PH0NDkf6wA8hujNNcrwtecqW4vtQ1UOSNBaZl49zP1SHoZ/06e+UiwzHpgb5zP5+hwlYYWYAw==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/typescript-estree": "5.49.0",
"@typescript-eslint/scope-manager": "5.50.0",
"@typescript-eslint/types": "5.50.0",
"@typescript-eslint/typescript-estree": "5.50.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
@ -1749,12 +1750,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz",
"integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz",
"integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/types": "5.50.0",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
@ -2412,9 +2413,9 @@
}
},
"node_modules/eslint": {
"version": "8.32.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.32.0.tgz",
"integrity": "sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==",
"version": "8.33.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz",
"integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==",
"dev": true,
"dependencies": {
"@eslint/eslintrc": "^1.4.1",
@ -4890,9 +4891,9 @@
}
},
"node_modules/typescript": {
"version": "4.9.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -6079,9 +6080,9 @@
}
},
"@reduxjs/toolkit": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.1.tgz",
"integrity": "sha512-HikrdY+IDgRfRYlCTGUQaiCxxDDgM1mQrRbZ6S1HFZX5ZYuJ4o8EstNmhTwHdPl2rTmLxzwSu0b3AyeyTlR+RA==",
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.2.tgz",
"integrity": "sha512-5ZAZ7hwAKWSii5T6NTPmgIBUqyVdlDs+6JjThz6J6dmHLDm6zCzv2OjHIFAi3Vvs1qjmXU0bm6eBojukYXjVMQ==",
"requires": {
"immer": "^9.0.16",
"redux": "^4.2.0",
@ -6101,9 +6102,9 @@
"requires": {}
},
"@strimertul/kilovolt-client": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@strimertul/kilovolt-client/-/kilovolt-client-7.0.1.tgz",
"integrity": "sha512-G9xLgufk1aRmrzUnxZeQHbSLewEy199WAox4zGolWf9GdlKREKcfcLYWq/idxvTacRusRuex2Z7+Gl/lXh7Swg=="
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@strimertul/kilovolt-client/-/kilovolt-client-7.1.0.tgz",
"integrity": "sha512-iZxCVRrJZNsnu6N2T/G83lwt3UBOHD0fCzaJRQsTBg6Ybdc8oi9v5/AqgZfA+mlif/iL0X5gkukKzSa1nAmg/w=="
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
@ -6171,15 +6172,16 @@
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
},
"@typescript-eslint/eslint-plugin": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.49.0.tgz",
"integrity": "sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.50.0.tgz",
"integrity": "sha512-vwksQWSFZiUhgq3Kv7o1Jcj0DUNylwnIlGvKvLLYsq8pAWha6/WCnXUeaSoNNha/K7QSf2+jvmkxggC1u3pIwQ==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/type-utils": "5.49.0",
"@typescript-eslint/utils": "5.49.0",
"@typescript-eslint/scope-manager": "5.50.0",
"@typescript-eslint/type-utils": "5.50.0",
"@typescript-eslint/utils": "5.50.0",
"debug": "^4.3.4",
"grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
"regexpp": "^3.2.0",
@ -6188,53 +6190,53 @@
}
},
"@typescript-eslint/parser": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.49.0.tgz",
"integrity": "sha512-veDlZN9mUhGqU31Qiv2qEp+XrJj5fgZpJ8PW30sHU+j/8/e5ruAhLaVDAeznS7A7i4ucb/s8IozpDtt9NqCkZg==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.50.0.tgz",
"integrity": "sha512-KCcSyNaogUDftK2G9RXfQyOCt51uB5yqC6pkUYqhYh8Kgt+DwR5M0EwEAxGPy/+DH6hnmKeGsNhiZRQxjH71uQ==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/typescript-estree": "5.49.0",
"@typescript-eslint/scope-manager": "5.50.0",
"@typescript-eslint/types": "5.50.0",
"@typescript-eslint/typescript-estree": "5.50.0",
"debug": "^4.3.4"
}
},
"@typescript-eslint/scope-manager": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz",
"integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz",
"integrity": "sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.49.0"
"@typescript-eslint/types": "5.50.0",
"@typescript-eslint/visitor-keys": "5.50.0"
}
},
"@typescript-eslint/type-utils": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.49.0.tgz",
"integrity": "sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.50.0.tgz",
"integrity": "sha512-dcnXfZ6OGrNCO7E5UY/i0ktHb7Yx1fV6fnQGGrlnfDhilcs6n19eIRcvLBqx6OQkrPaFlDPk3OJ0WlzQfrV0bQ==",
"dev": true,
"requires": {
"@typescript-eslint/typescript-estree": "5.49.0",
"@typescript-eslint/utils": "5.49.0",
"@typescript-eslint/typescript-estree": "5.50.0",
"@typescript-eslint/utils": "5.50.0",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
}
},
"@typescript-eslint/types": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz",
"integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz",
"integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz",
"integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz",
"integrity": "sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/visitor-keys": "5.49.0",
"@typescript-eslint/types": "5.50.0",
"@typescript-eslint/visitor-keys": "5.50.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@ -6243,28 +6245,28 @@
}
},
"@typescript-eslint/utils": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz",
"integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.50.0.tgz",
"integrity": "sha512-v/AnUFImmh8G4PH0NDkf6wA8hujNNcrwtecqW4vtQ1UOSNBaZl49zP1SHoZ/06e+UiwzHpgb5zP5+hwlYYWYAw==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.49.0",
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/typescript-estree": "5.49.0",
"@typescript-eslint/scope-manager": "5.50.0",
"@typescript-eslint/types": "5.50.0",
"@typescript-eslint/typescript-estree": "5.50.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz",
"integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz",
"integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.49.0",
"@typescript-eslint/types": "5.50.0",
"eslint-visitor-keys": "^3.3.0"
}
},
@ -6729,9 +6731,9 @@
"dev": true
},
"eslint": {
"version": "8.32.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.32.0.tgz",
"integrity": "sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==",
"version": "8.33.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz",
"integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==",
"dev": true,
"requires": {
"@eslint/eslintrc": "^1.4.1",
@ -8449,9 +8451,9 @@
"dev": true
},
"typescript": {
"version": "4.9.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg=="
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g=="
},
"unbox-primitive": {
"version": "1.0.2",

View file

@ -18,9 +18,9 @@
"@radix-ui/react-toggle-group": "^1.0.2",
"@radix-ui/react-toolbar": "^1.0.2",
"@redux-devtools/extension": "^3.2.5",
"@reduxjs/toolkit": "^1.9.1",
"@reduxjs/toolkit": "^1.9.2",
"@stitches/react": "^1.2.8",
"@strimertul/kilovolt-client": "^7.0.1",
"@strimertul/kilovolt-client": "^7.1.0",
"@types/node": "^18.11.18",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
@ -36,7 +36,7 @@
"react-router-dom": "^6.8.0",
"redux-thunk": "^2.4.2",
"sass": "^1.57.1",
"typescript": "^4.9.4",
"typescript": "^4.9.5",
"vite": "^4.0.4",
"vite-tsconfig-paths": "^4.0.5"
},
@ -49,9 +49,9 @@
"last 1 Chrome version"
],
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.49.0",
"eslint": "^8.32.0",
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0",
"eslint": "^8.33.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.6.0",
"eslint-import-resolver-typescript": "^3.5.3",

View file

@ -1 +1 @@
66a652086f52f01615c5cbe3a6b5474e
ac94996b5cd655ce090a6545016d312c

View file

@ -1,9 +1,10 @@
import { ExtensionEntry } from '~/store/extensions/reducer';
import {
ExtensionStatus,
ExtensionOptions,
ExtensionDependencies,
ExtensionHostMessage,
ExtensionHostCommand,
ExtensionRunOptions,
} from './types';
export const blankTemplate = (slug: string) => `// ==Extension==
@ -22,10 +23,9 @@ export class Extension extends EventTarget {
private workerError?: ErrorEvent;
constructor(
public readonly name: string,
public readonly source: string,
public readonly options: ExtensionOptions,
public readonly info: ExtensionEntry,
dependencies: ExtensionDependencies,
runOptions: ExtensionRunOptions = { autostart: false },
) {
super();
@ -43,14 +43,13 @@ export class Extension extends EventTarget {
// Initialize ext host
this.send({
kind: 'arguments',
source,
options,
source: info.source,
options: runOptions,
dependencies,
});
}
private send(cmd: ExtensionHostCommand) {
console.log(cmd);
this.worker.postMessage(cmd);
}
@ -74,6 +73,13 @@ export class Extension extends EventTarget {
return this.workerError;
}
public get running() {
return (
this.status === ExtensionStatus.Running ||
this.status === ExtensionStatus.Finished
);
}
start() {
switch (this.status) {
case ExtensionStatus.Ready:
@ -94,6 +100,9 @@ export class Extension extends EventTarget {
}
stop() {
if (this.workerStatus === ExtensionStatus.Terminated) {
return;
}
this.worker.terminate();
this.workerStatus = ExtensionStatus.Terminated;
this.dispatchEvent(

View file

@ -6,9 +6,12 @@ export interface ExtensionDependencies {
}
export interface ExtensionOptions {
autostart: boolean;
enabled: boolean;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ExtensionRunOptions {}
export enum ExtensionStatus {
GettingReady = 'not-ready',
Ready = 'ready',
@ -22,7 +25,7 @@ export type ExtensionHostCommand = EHParamMessage | EHStartMessage;
export type ExtensionHostMessage = EHStatusChangeMessage;
interface EHParamMessage {
kind: 'arguments';
options: ExtensionOptions;
options: ExtensionRunOptions;
dependencies: ExtensionDependencies;
source: string;
}

View file

@ -49,9 +49,7 @@ onmessage = async (ev: MessageEvent<ExtensionHostCommand>) => {
extFn = ExtensionFunction.constructor('kv', cmd.source);
setStatus(ExtensionStatus.Ready);
if (cmd.options.autostart) {
start();
}
start();
break;
}
case 'start':

View file

@ -317,7 +317,8 @@
"create": "Create new",
"search": "Search by name",
"tab-manage": "Manage",
"tab-editor": "Editor"
"tab-editor": "Editor",
"remove-alert": "Remove extension \"{{name}}\"?"
}
},
"form-actions": {
@ -336,7 +337,9 @@
"create": "Create",
"submit": "Submit",
"password-reveal": "Reveal",
"password-hide": "Hide"
"password-hide": "Hide",
"start": "Start",
"stop": "Stop"
},
"debug": {
"dev-build": "Development build"

View file

@ -4,14 +4,18 @@ import { Extension } from '~/lib/extensions/extension';
import {
ExtensionDependencies,
ExtensionOptions,
ExtensionRunOptions,
ExtensionStatus,
} from '~/lib/extensions/types';
import { RootState } from '..';
import { HTTPConfig } from '../api/types';
interface ExtensionsState {
ready: boolean;
installed: Record<string, Extension>;
installed: Record<string, ExtensionEntry>;
running: Record<string, Extension>;
unsaved: Record<string, string>;
status: Record<string, ExtensionStatus>;
editorCurrentFile: string;
dependencies: ExtensionDependencies;
}
@ -25,8 +29,10 @@ export interface ExtensionEntry {
const initialState: ExtensionsState = {
ready: false,
installed: {},
running: {},
unsaved: {},
editorCurrentFile: null,
status: {},
dependencies: {
kilovolt: { address: '' },
},
@ -54,37 +60,111 @@ const extensionsReducer = createSlice({
extensionSourceChanged(state, { payload }: PayloadAction<string>) {
state.unsaved[state.editorCurrentFile] = payload;
},
extensionStatusChanged(
state,
{ payload }: PayloadAction<{ name: string; status: ExtensionStatus }>,
) {
state.status[payload.name] = payload.status;
},
extensionAdded(state, { payload }: PayloadAction<ExtensionEntry>) {
// Remove from unsaved
if (payload.name in state.unsaved) {
delete state.unsaved[payload.name];
}
// If running, terminate running instance
if (payload.name in state.installed) {
state.installed[payload.name]?.dispose();
}
// If we don't have a file selected in the editor, set a default as soon as possible
if (!state.editorCurrentFile) {
state.editorCurrentFile = payload.name;
}
state.installed[payload.name] = payload;
},
extensionInstanceAdded(state, { payload }: PayloadAction<Extension>) {
// If running, terminate running instance
if (payload.info.name in state.running) {
state.running[payload.info.name]?.dispose();
}
// Create new instance with stored code
state.installed[payload.name] = new Extension(
payload.name,
payload.source,
payload.options,
{
kilovolt: { ...state.dependencies.kilovolt },
},
);
state.status[payload.info.name] = ExtensionStatus.GettingReady;
state.running[payload.info.name] = payload;
},
extensionRemoved(state, { payload }: PayloadAction<string>) {
// If running, terminate running instance
if (payload in state.running) {
state.running[payload]?.dispose();
}
// Remove from other lists
delete state.installed[payload];
delete state.running[payload];
delete state.unsaved[payload];
delete state.status[payload];
// If it's the currently selected file in the editor, select another or none
if (state.editorCurrentFile === payload) {
const others = Object.keys(state.installed);
state.editorCurrentFile = others.length > 0 ? others[0] : null;
}
},
},
});
const extensionPrefix = 'ui/extensions/installed/';
export const createExtensionInstance = createAsyncThunk(
'extensions/new-instance',
(
payload: {
entry: ExtensionEntry;
dependencies: ExtensionDependencies;
runOptions?: ExtensionRunOptions;
},
{ dispatch },
) => {
const ext = new Extension(
payload.entry,
payload.dependencies,
payload.runOptions,
);
ext.addEventListener(
'statusChanged',
(ev: CustomEvent<ExtensionStatus>) => {
dispatch(
extensionsReducer.actions.extensionStatusChanged({
name: payload.entry.name,
status: ev.detail,
}),
);
},
);
dispatch(extensionsReducer.actions.extensionAdded(payload.entry));
dispatch(extensionsReducer.actions.extensionInstanceAdded(ext));
},
);
export const refreshExtensionInstance = createAsyncThunk(
'extensions/refresh-instance',
async (payload: ExtensionEntry, { dispatch, getState }) => {
const { extensions } = getState() as RootState;
if (payload.options.enabled) {
await dispatch(
createExtensionInstance({
entry: payload,
dependencies: extensions.dependencies,
}),
);
} else {
// If running, terminate running instance
if (payload.name in extensions.running) {
extensions.running[payload.name]?.dispose();
}
dispatch(extensionsReducer.actions.extensionAdded(payload));
}
},
);
export const initializeExtensions = createAsyncThunk(
'extensions/initialize',
async (_: void, { getState, dispatch }) => {
@ -95,19 +175,18 @@ export const initializeExtensions = createAsyncThunk(
const httpConfig = await api.client.getJSON<HTTPConfig>('http/config');
// Set dependencies
dispatch(
extensionsReducer.actions.initialized({
kilovolt: {
address: `ws://${httpConfig.bind}/ws`,
password: httpConfig.kv_password,
},
}),
);
const deps = {
kilovolt: {
address: `ws://${httpConfig.bind}/ws`,
password: httpConfig.kv_password,
},
};
dispatch(extensionsReducer.actions.initialized(deps));
// Become reactive to extension changes
await api.client.subscribePrefix(extensionPrefix, (newValue, newKey) => {
dispatch(
extensionsReducer.actions.extensionAdded({
void dispatch(
refreshExtensionInstance({
...(JSON.parse(newValue) as ExtensionEntry),
name: newKey.substring(extensionPrefix.length),
}),
@ -115,18 +194,57 @@ export const initializeExtensions = createAsyncThunk(
});
// Get installed extensions
const extensions = await api.client.getKeysByPrefix(extensionPrefix);
Object.entries(extensions).forEach(([extName, extContent]) =>
dispatch(
extensionsReducer.actions.extensionAdded({
const installed = await api.client.getKeysByPrefix(extensionPrefix);
await Promise.all(
Object.entries(installed).map(async ([extName, extContent]) => {
const entry = {
...(JSON.parse(extContent) as ExtensionEntry),
name: extName.substring(extensionPrefix.length),
}),
),
};
if (entry.options.enabled) {
await dispatch(
createExtensionInstance({
entry,
dependencies: deps,
}),
);
} else {
dispatch(extensionsReducer.actions.extensionAdded(entry));
}
}),
);
},
);
export const startExtension = createAsyncThunk(
'extensions/start',
async (name: string, { getState, dispatch }) => {
const { extensions } = getState() as RootState;
// If terminated, re-create extension
if (extensions.running[name].status === ExtensionStatus.Terminated) {
await dispatch(
createExtensionInstance({
entry: extensions.installed[name],
dependencies: extensions.dependencies,
runOptions: { autostart: true },
}),
);
return;
}
extensions.running[name].start();
},
);
export const stopExtension = createAsyncThunk(
'extensions/stop',
(name: string, { getState }) => {
const { extensions } = getState() as RootState;
extensions.running[name].stop();
},
);
export const saveExtension = createAsyncThunk(
'extensions/save',
async (entry: ExtensionEntry, { getState }) => {
@ -136,4 +254,14 @@ export const saveExtension = createAsyncThunk(
},
);
export const removeExtension = createAsyncThunk(
'extensions/remove',
async (name: string, { getState, dispatch }) => {
// Get kv client
const { api } = getState() as RootState;
dispatch(extensionsReducer.actions.extensionRemoved(name));
await api.client.deleteKey(extensionPrefix + name);
},
);
export default extensionsReducer;

View file

@ -2,10 +2,18 @@ import Editor from '@monaco-editor/react';
import { InputIcon, PlusIcon } from '@radix-ui/react-icons';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { blankTemplate, Extension } from '~/lib/extensions/extension';
import { blankTemplate } from '~/lib/extensions/extension';
import { ExtensionStatus } from '~/lib/extensions/types';
import slug from '~/lib/slug';
import { useAppDispatch, useAppSelector } from '~/store';
import extensionsReducer, { saveExtension } from '~/store/extensions/reducer';
import extensionsReducer, {
ExtensionEntry,
removeExtension,
saveExtension,
startExtension,
stopExtension,
} from '~/store/extensions/reducer';
import AlertContent from '../components/AlertContent';
import Loading from '../components/Loading';
import {
Button,
@ -13,6 +21,7 @@ import {
Field,
FlexRow,
InputBox,
MultiButton,
PageContainer,
PageHeader,
PageTitle,
@ -22,13 +31,129 @@ import {
TabContent,
TabList,
} from '../theme';
import { Alert, AlertTrigger } from '../theme/alert';
interface ExtensionListProps {
extensions: Record<string, Extension>;
onNewClicked: () => void;
const ExtensionRow = styled('article', {
marginBottom: '0.4rem',
backgroundColor: '$gray2',
margin: '0.5rem 0',
padding: '0.3rem 0.5rem',
borderLeft: '5px solid $teal8',
borderRadius: '0.25rem',
borderBottom: '1px solid $gray4',
transition: 'all 50ms',
'&:hover': {
backgroundColor: '$gray3',
},
variants: {
status: {
enabled: {},
disabled: {
borderLeftColor: '$red6',
backgroundColor: '$gray3',
color: '$gray10',
},
},
},
});
const ExtensionName = styled('div', {
flex: '1',
});
const ExtensionActions = styled('div', {
display: 'flex',
alignItems: 'center',
gap: '0.25rem',
});
const isRunning = (status: ExtensionStatus) =>
status === ExtensionStatus.Running || status === ExtensionStatus.Finished;
type ExtensionListItemProps = {
enabled: boolean;
entry: ExtensionEntry;
status: ExtensionStatus;
onEdit: () => void;
onRemove: () => void;
onToggleEnable: () => void;
onToggleStatus: () => void;
};
function ExtensionListItem(props: ExtensionListItemProps) {
const { t } = useTranslation();
return (
<ExtensionRow
status={props.enabled && isRunning(props.status) ? 'enabled' : 'disabled'}
>
<FlexRow>
<ExtensionName>
{props.entry.name} {props.enabled ? `(${props.status})` : null}
</ExtensionName>
<ExtensionActions>
<MultiButton>
<Button
styling="multi"
size="small"
onClick={() => props.onToggleEnable()}
>
{t(
props.entry.options.enabled
? 'form-actions.disable'
: 'form-actions.enable',
)}
</Button>
{props.enabled ? (
<>
<Button
styling="multi"
size="small"
onClick={() => props.onToggleStatus()}
>
{t(
isRunning(props.status)
? 'form-actions.stop'
: 'form-actions.start',
)}
</Button>
</>
) : null}
<Button styling="multi" size="small" onClick={() => props.onEdit()}>
{t('form-actions.edit')}
</Button>
<Alert>
<AlertTrigger asChild>
<Button styling="multi" size="small">
{t('form-actions.delete')}
</Button>
</AlertTrigger>
<AlertContent
variation="danger"
title={t('pages.extensions.remove-alert', {
name: props.entry.name,
})}
description={t('form-actions.warning-delete')}
actionText={t('form-actions.delete')}
actionButtonProps={{ variation: 'danger' }}
showCancel={true}
onAction={() => props.onRemove()}
/>
</Alert>
</MultiButton>
</ExtensionActions>
</FlexRow>
</ExtensionRow>
);
}
function ExtensionList({ extensions, onNewClicked }: ExtensionListProps) {
interface ExtensionListProps {
onNew: () => void;
onEdit: (name: string) => void;
}
function ExtensionList({ onNew, onEdit }: ExtensionListProps) {
const extensions = useAppSelector((state) => state.extensions);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const [filter, setFilter] = useState('');
const filterLC = filter.toLowerCase();
@ -40,7 +165,7 @@ function ExtensionList({ extensions, onNewClicked }: ExtensionListProps) {
</PageHeader>
<Field size="fullWidth" spacing="none">
<FlexRow css={{ flex: 1, alignItems: 'stretch' }} spacing="1">
<Button variation="primary" onClick={() => onNewClicked()}>
<Button variation="primary" onClick={() => onNew()}>
<PlusIcon /> {t('pages.extensions.create')}
</Button>
<InputBox
@ -51,10 +176,39 @@ function ExtensionList({ extensions, onNewClicked }: ExtensionListProps) {
/>
</FlexRow>
</Field>
{Object.values(extensions)
{Object.values(extensions.installed)
?.filter((r) => r.name.toLowerCase().includes(filterLC))
.map((e) => (
<div key={e.name}>{e.name}</div>
<ExtensionListItem
key={e.name}
entry={e}
enabled={e.options.enabled}
status={extensions.status[e.name]}
onEdit={() => onEdit(e.name)}
onRemove={() => {
// Toggle enabled status
void dispatch(removeExtension(e.name));
}}
onToggleEnable={() => {
// Toggle enabled status
void dispatch(
saveExtension({
...e,
options: {
...e.options,
enabled: !e.options.enabled,
},
}),
);
}}
onToggleStatus={() => {
if (isRunning(extensions.status[e.name])) {
void dispatch(stopExtension(e.name));
} else {
void dispatch(startExtension(e.name));
}
}}
/>
))}
</PageContainer>
);
@ -80,10 +234,13 @@ const EditorDropdown = styled(ComboBox, {
function ExtensionEditor() {
const extensions = useAppSelector((state) => state.extensions);
const dispatch = useAppDispatch();
const currentFile =
extensions.editorCurrentFile in extensions.unsaved
? extensions.unsaved[extensions.editorCurrentFile]
: extensions.installed[extensions.editorCurrentFile].source;
const isUnsaved =
extensions.editorCurrentFile in extensions.unsaved &&
extensions.unsaved[extensions.editorCurrentFile] !==
extensions.installed[extensions.editorCurrentFile]?.source;
const currentFile = isUnsaved
? extensions.unsaved[extensions.editorCurrentFile]
: extensions.installed[extensions.editorCurrentFile].source;
return (
<div
style={{
@ -98,6 +255,11 @@ function ExtensionEditor() {
>
<EditorDropdown
value={extensions.editorCurrentFile}
onChange={(ev) => {
void dispatch(
extensionsReducer.actions.editorSelectedFile(ev.target.value),
);
}}
css={{ flex: '1' }}
>
{Object.values(extensions.installed)
@ -109,7 +271,8 @@ function ExtensionEditor() {
))}
{Object.keys(extensions.unsaved).map((ext) => (
<option key={ext} value={ext}>
{ext}*
{ext}
{isUnsaved ? '*' : ''}
</option>
))}
</EditorDropdown>
@ -118,7 +281,7 @@ function ExtensionEditor() {
</EditorButton>
<EditorButton
size="small"
disabled={!(extensions.editorCurrentFile in extensions.unsaved)}
disabled={!isUnsaved}
onClick={() => {
void dispatch(
saveExtension({
@ -127,7 +290,7 @@ function ExtensionEditor() {
options:
extensions.editorCurrentFile in extensions.installed
? extensions.installed[extensions.editorCurrentFile].options
: { autostart: false },
: { enabled: false },
}),
);
}}
@ -169,7 +332,7 @@ export default function ExtensionsPage(): React.ReactElement {
extensionsReducer.actions.extensionDrafted({
name: defaultName,
source: blankTemplate(defaultName),
options: { autostart: false },
options: { enabled: false },
}),
);
@ -178,6 +341,12 @@ export default function ExtensionsPage(): React.ReactElement {
setCurrentTab('editor');
};
const editClicked = (name: string) => {
// Set it as current file in editor
dispatch(extensionsReducer.actions.editorSelectedFile(name));
setCurrentTab('editor');
};
if (!extensions.ready) {
return (
<PageContainer>
@ -202,8 +371,8 @@ export default function ExtensionsPage(): React.ReactElement {
</TabList>
<TabContent css={{ paddingTop: '1rem' }} value="list">
<ExtensionList
extensions={extensions.installed}
onNewClicked={() => newClicked()}
onNew={() => newClicked()}
onEdit={(name) => editClicked(name)}
/>
</TabContent>
<TabContent css={{ paddingTop: '0' }} value="editor">