From 196ab184e662daf7e34c588a94e52572ae57372d Mon Sep 17 00:00:00 2001 From: Ash Keel Date: Tue, 31 Jan 2023 12:20:27 +0100 Subject: [PATCH] feat: extension list mostly works --- frontend/package-lock.json | 222 +++++++++--------- frontend/package.json | 12 +- frontend/package.json.md5 | 2 +- frontend/src/lib/extensions/extension.ts | 23 +- frontend/src/lib/extensions/types.ts | 7 +- .../lib/extensions/workers/extensionHost.ts | 4 +- frontend/src/locale/en/translation.json | 7 +- frontend/src/store/extensions/reducer.ts | 188 ++++++++++++--- frontend/src/ui/pages/Extensions.tsx | 207 ++++++++++++++-- 9 files changed, 492 insertions(+), 180 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4610af4..43ac541 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index b9efd46..163ce0c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 0fc7db6..c76d8be 100644 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -66a652086f52f01615c5cbe3a6b5474e \ No newline at end of file +ac94996b5cd655ce090a6545016d312c \ No newline at end of file diff --git a/frontend/src/lib/extensions/extension.ts b/frontend/src/lib/extensions/extension.ts index 5b9b6a5..3d4382d 100644 --- a/frontend/src/lib/extensions/extension.ts +++ b/frontend/src/lib/extensions/extension.ts @@ -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( diff --git a/frontend/src/lib/extensions/types.ts b/frontend/src/lib/extensions/types.ts index 42af7d5..f938180 100644 --- a/frontend/src/lib/extensions/types.ts +++ b/frontend/src/lib/extensions/types.ts @@ -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; } diff --git a/frontend/src/lib/extensions/workers/extensionHost.ts b/frontend/src/lib/extensions/workers/extensionHost.ts index af2a718..40fe912 100644 --- a/frontend/src/lib/extensions/workers/extensionHost.ts +++ b/frontend/src/lib/extensions/workers/extensionHost.ts @@ -49,9 +49,7 @@ onmessage = async (ev: MessageEvent) => { extFn = ExtensionFunction.constructor('kv', cmd.source); setStatus(ExtensionStatus.Ready); - if (cmd.options.autostart) { - start(); - } + start(); break; } case 'start': diff --git a/frontend/src/locale/en/translation.json b/frontend/src/locale/en/translation.json index 4cba75c..b904817 100644 --- a/frontend/src/locale/en/translation.json +++ b/frontend/src/locale/en/translation.json @@ -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" diff --git a/frontend/src/store/extensions/reducer.ts b/frontend/src/store/extensions/reducer.ts index 6c46321..ce5228f 100644 --- a/frontend/src/store/extensions/reducer.ts +++ b/frontend/src/store/extensions/reducer.ts @@ -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; + installed: Record; + running: Record; unsaved: Record; + status: Record; 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) { state.unsaved[state.editorCurrentFile] = payload; }, + extensionStatusChanged( + state, + { payload }: PayloadAction<{ name: string; status: ExtensionStatus }>, + ) { + state.status[payload.name] = payload.status; + }, extensionAdded(state, { payload }: PayloadAction) { // 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) { + // 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) { + // 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) => { + 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('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; diff --git a/frontend/src/ui/pages/Extensions.tsx b/frontend/src/ui/pages/Extensions.tsx index 0e8adbf..b353787 100644 --- a/frontend/src/ui/pages/Extensions.tsx +++ b/frontend/src/ui/pages/Extensions.tsx @@ -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; - 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 ( + + + + {props.entry.name} {props.enabled ? `(${props.status})` : null} + + + + + {props.enabled ? ( + <> + + + ) : null} + + + + + + + props.onRemove()} + /> + + + + + + ); } -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) { - - {Object.values(extensions) + {Object.values(extensions.installed) ?.filter((r) => r.name.toLowerCase().includes(filterLC)) .map((e) => ( -
{e.name}
+ 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)); + } + }} + /> ))} ); @@ -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 (
{ + 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) => ( ))} @@ -118,7 +281,7 @@ function ExtensionEditor() { { 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 ( @@ -202,8 +371,8 @@ export default function ExtensionsPage(): React.ReactElement { newClicked()} + onNew={() => newClicked()} + onEdit={(name) => editClicked(name)} />