From f4cb1d3ff4778e65bf03bce6c1a343357a40ff25 Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Mon, 3 Apr 2023 15:23:42 +0300 Subject: [PATCH] Introduce Feature for Keyboard Shortcuts (#7442) * Introduce feature for assigning keyboard shortcuts Signed-off-by: Janne Savolainen * Enable keyboard shortcuts automatically instead of requiring explicit listener to be used in the application Signed-off-by: Janne Savolainen * Start using keyboard shortcuts feature Signed-off-by: Janne Savolainen * Update package-lock after rebase Signed-off-by: Janne Savolainen * Tweak scripts for a package Signed-off-by: Janne Savolainen * Introduce modifier for ctrl or command based on platform in use Signed-off-by: Janne Savolainen --------- Signed-off-by: Janne Savolainen --- package-lock.json | 180 +++++---- .../keyboard-shortcuts/.eslintrc.json | 6 + .../keyboard-shortcuts/.prettierrc | 1 + .../keyboard-shortcuts/README.md | 21 ++ .../keyboard-shortcuts/index.ts | 7 + .../keyboard-shortcuts/jest.config.js | 1 + .../keyboard-shortcuts/package.json | 46 +++ .../keyboard-shortcuts.test.tsx.snap | 15 + .../keyboard-shortcuts/src/feature.ts | 17 + .../src/invoke-shortcut.injectable.ts | 94 +++++ .../src/keyboard-shortcut-injection-token.ts | 22 ++ ...tener-react-application-hoc.injectable.tsx | 10 + .../src/keyboard-shortcut-listener.tsx | 41 ++ .../src/keyboard-shortcut-scope.tsx | 12 + .../src/keyboard-shortcuts.test.tsx | 353 ++++++++++++++++++ .../src/platform.injectable.ts | 11 + .../keyboard-shortcuts/tsconfig.json | 4 + .../keyboard-shortcuts/webpack.config.js | 1 + packages/open-lens/package.json | 1 + packages/open-lens/src/renderer/index.ts | 2 + 20 files changed, 772 insertions(+), 73 deletions(-) create mode 100644 packages/business-features/keyboard-shortcuts/.eslintrc.json create mode 100644 packages/business-features/keyboard-shortcuts/.prettierrc create mode 100644 packages/business-features/keyboard-shortcuts/README.md create mode 100644 packages/business-features/keyboard-shortcuts/index.ts create mode 100644 packages/business-features/keyboard-shortcuts/jest.config.js create mode 100644 packages/business-features/keyboard-shortcuts/package.json create mode 100644 packages/business-features/keyboard-shortcuts/src/__snapshots__/keyboard-shortcuts.test.tsx.snap create mode 100644 packages/business-features/keyboard-shortcuts/src/feature.ts create mode 100644 packages/business-features/keyboard-shortcuts/src/invoke-shortcut.injectable.ts create mode 100644 packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-injection-token.ts create mode 100644 packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener-react-application-hoc.injectable.tsx create mode 100644 packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener.tsx create mode 100644 packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-scope.tsx create mode 100644 packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx create mode 100644 packages/business-features/keyboard-shortcuts/src/platform.injectable.ts create mode 100644 packages/business-features/keyboard-shortcuts/tsconfig.json create mode 100644 packages/business-features/keyboard-shortcuts/webpack.config.js diff --git a/package-lock.json b/package-lock.json index d66943ede4..31bf465203 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4606,6 +4606,10 @@ "resolved": "packages/infrastructure/jest", "link": true }, + "node_modules/@k8slens/keyboard-shortcuts": { + "resolved": "packages/business-features/keyboard-shortcuts", + "link": true + }, "node_modules/@k8slens/legacy-extension-example": { "resolved": "packages/legacy-extension-example", "link": true @@ -5042,13 +5046,13 @@ } }, "node_modules/@lerna/legacy-package-management/node_modules/glob": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz", - "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz", + "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", - "minimatch": "^7.4.1", + "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" }, @@ -5069,15 +5073,15 @@ } }, "node_modules/@lerna/legacy-package-management/node_modules/glob/node_modules/minimatch": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", - "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz", + "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -6022,13 +6026,13 @@ } }, "node_modules/@npmcli/arborist/node_modules/cacache/node_modules/glob": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz", - "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz", + "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", - "minimatch": "^7.4.1", + "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" }, @@ -6040,15 +6044,15 @@ } }, "node_modules/@npmcli/arborist/node_modules/cacache/node_modules/minimatch": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", - "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz", + "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -6091,9 +6095,9 @@ } }, "node_modules/@npmcli/arborist/node_modules/ignore-walk/node_modules/minimatch": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", - "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.4.tgz", + "integrity": "sha512-T+8B3kNrLP7jDb5eaC4rUIp6DKoeTSb6f9SwF2phcY2gxJUA0GEf1i29/FHxBMEfx0ppWlr434/D0P+6jb8bOQ==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -6612,13 +6616,13 @@ } }, "node_modules/@npmcli/arborist/node_modules/read-package-json/node_modules/glob": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz", - "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz", + "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", - "minimatch": "^7.4.1", + "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" }, @@ -6630,15 +6634,15 @@ } }, "node_modules/@npmcli/arborist/node_modules/read-package-json/node_modules/minimatch": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", - "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz", + "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -6888,13 +6892,13 @@ } }, "node_modules/@npmcli/map-workspaces/node_modules/glob": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz", - "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz", + "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", - "minimatch": "^7.4.1", + "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" }, @@ -6905,10 +6909,25 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@npmcli/map-workspaces/node_modules/glob/node_modules/minimatch": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz", + "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", - "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.4.tgz", + "integrity": "sha512-T+8B3kNrLP7jDb5eaC4rUIp6DKoeTSb6f9SwF2phcY2gxJUA0GEf1i29/FHxBMEfx0ppWlr434/D0P+6jb8bOQ==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -7040,13 +7059,13 @@ } }, "node_modules/@npmcli/metavuln-calculator/node_modules/glob": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz", - "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz", + "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", - "minimatch": "^7.4.1", + "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" }, @@ -7081,6 +7100,21 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@npmcli/metavuln-calculator/node_modules/ignore-walk/node_modules/minimatch": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.4.tgz", + "integrity": "sha512-T+8B3kNrLP7jDb5eaC4rUIp6DKoeTSb6f9SwF2phcY2gxJUA0GEf1i29/FHxBMEfx0ppWlr434/D0P+6jb8bOQ==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@npmcli/metavuln-calculator/node_modules/json-parse-even-better-errors": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", @@ -7260,15 +7294,15 @@ } }, "node_modules/@npmcli/metavuln-calculator/node_modules/minimatch": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", - "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz", + "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -9386,9 +9420,9 @@ } }, "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", - "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.4.tgz", + "integrity": "sha512-T+8B3kNrLP7jDb5eaC4rUIp6DKoeTSb6f9SwF2phcY2gxJUA0GEf1i29/FHxBMEfx0ppWlr434/D0P+6jb8bOQ==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -24023,13 +24057,13 @@ } }, "node_modules/lerna/node_modules/glob": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz", - "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz", + "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", - "minimatch": "^7.4.1", + "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" }, @@ -24050,15 +24084,15 @@ } }, "node_modules/lerna/node_modules/glob/node_modules/minimatch": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", - "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz", + "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -31480,9 +31514,9 @@ } }, "node_modules/promise-call-limit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.1.tgz", - "integrity": "sha512-3+hgaa19jzCGLuSCbieeRsu5C2joKfYn8pY6JAuXFRVfF4IO+L7UPpFWNTeWT9pM7uhskvbPPd/oEOktCn317Q==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.2.tgz", + "integrity": "sha512-1vTUnfI2hzui8AEIixbdAJlFY4LFDXqQswy/2eOlThAscXCY4It8FdVuI0fMJGAB2aWGbdQf/gv0skKYXmdrHA==", "dev": true, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -33701,13 +33735,13 @@ } }, "node_modules/sigstore/node_modules/glob": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz", - "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz", + "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", - "minimatch": "^7.4.1", + "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" }, @@ -33754,15 +33788,15 @@ } }, "node_modules/sigstore/node_modules/minimatch": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", - "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz", + "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -35858,13 +35892,13 @@ } }, "node_modules/tuf-js/node_modules/glob": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz", - "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz", + "integrity": "sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", - "minimatch": "^7.4.1", + "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" }, @@ -35911,15 +35945,15 @@ } }, "node_modules/tuf-js/node_modules/minimatch": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", - "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz", + "integrity": "sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -37809,7 +37843,6 @@ "packages/business-features/keyboard-shortcuts": { "name": "@k8slens/keyboard-shortcuts", "version": "1.0.0-alpha.0", - "extraneous": true, "license": "MIT", "devDependencies": { "@async-fn/jest": "^1.6.4", @@ -42366,6 +42399,7 @@ "@k8slens/core": "^6.5.0-alpha.3", "@k8slens/ensure-binaries": "^6.5.0-alpha.1", "@k8slens/feature-core": "^6.5.0-alpha.1", + "@k8slens/keyboard-shortcuts": "^1.0.0-alpha.0", "@k8slens/legacy-extension-example": "^1.0.0-alpha.1", "@k8slens/legacy-extensions": "^1.0.0-alpha.1", "@k8slens/messaging": "^1.0.0-alpha.1", diff --git a/packages/business-features/keyboard-shortcuts/.eslintrc.json b/packages/business-features/keyboard-shortcuts/.eslintrc.json new file mode 100644 index 0000000000..b15115cb69 --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": "@k8slens/eslint-config/eslint", + "parserOptions": { + "project": "./tsconfig.json" + } +} diff --git a/packages/business-features/keyboard-shortcuts/.prettierrc b/packages/business-features/keyboard-shortcuts/.prettierrc new file mode 100644 index 0000000000..edd47b479e --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/.prettierrc @@ -0,0 +1 @@ +"@k8slens/eslint-config/prettier" diff --git a/packages/business-features/keyboard-shortcuts/README.md b/packages/business-features/keyboard-shortcuts/README.md new file mode 100644 index 0000000000..25cc2f8cdb --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/README.md @@ -0,0 +1,21 @@ +# @k8slens/keyboard-shortcuts + +This Feature enables keyboard shortcuts in Lens + +# Usage + +```bash +$ npm install @k8slens/keyboard-shortcuts +``` + +```typescript +import { keyboardShortcutsFeature } from "@k8slens/keyboard-shortcuts"; +import { registerFeature } from "@k8slens/feature-core"; +import { createContainer } from "@ogre-tools/injectable"; + +const di = createContainer("some-container"); + +registerFeature(di, keyboardShortcutsFeature); +``` + +## Extendability diff --git a/packages/business-features/keyboard-shortcuts/index.ts b/packages/business-features/keyboard-shortcuts/index.ts new file mode 100644 index 0000000000..7f8ac51c47 --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/index.ts @@ -0,0 +1,7 @@ +export { KeyboardShortcutScope } from "./src/keyboard-shortcut-scope"; +export type { KeyboardShortcutScopeProps } from "./src/keyboard-shortcut-scope"; + +export { keyboardShortcutInjectionToken } from "./src/keyboard-shortcut-injection-token"; +export type { Binding, KeyboardShortcut } from "./src/keyboard-shortcut-injection-token"; + +export { keyboardShortcutsFeature } from "./src/feature"; diff --git a/packages/business-features/keyboard-shortcuts/jest.config.js b/packages/business-features/keyboard-shortcuts/jest.config.js new file mode 100644 index 0000000000..38d54ab7b6 --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/jest.config.js @@ -0,0 +1 @@ +module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact; diff --git a/packages/business-features/keyboard-shortcuts/package.json b/packages/business-features/keyboard-shortcuts/package.json new file mode 100644 index 0000000000..1b60e66133 --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/package.json @@ -0,0 +1,46 @@ +{ + "name": "@k8slens/keyboard-shortcuts", + "private": false, + "version": "1.0.0-alpha.0", + "description": "Keyboard shortcuts for Lens", + "type": "commonjs", + "files": [ + "dist" + ], + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/lensapp/lens.git" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "author": { + "name": "OpenLens Authors", + "email": "info@k8slens.dev" + }, + "license": "MIT", + "homepage": "https://github.com/lensapp/lens", + "scripts": { + "build": "webpack", + "clean": "rimraf dist/", + "test:unit": "jest --coverage --runInBand", + "lint": "lens-lint", + "lint:fix": "lens-lint --fix" + }, + "peerDependencies": { + "@k8slens/feature-core": "^6.5.0-alpha.0", + "@k8slens/react-application": "^1.0.0-alpha.0", + "@ogre-tools/injectable": "^15.1.2", + "@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2", + "@ogre-tools/fp": "^15.1.2", + "lodash": "^4.17.21" + }, + "devDependencies": { + "@async-fn/jest": "^1.6.4", + "@k8slens/eslint-config": "6.5.0-alpha.1", + "@k8slens/react-testing-library-discovery": "^1.0.0-alpha.0" + } +} diff --git a/packages/business-features/keyboard-shortcuts/src/__snapshots__/keyboard-shortcuts.test.tsx.snap b/packages/business-features/keyboard-shortcuts/src/__snapshots__/keyboard-shortcuts.test.tsx.snap new file mode 100644 index 0000000000..b2cba6a543 --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/src/__snapshots__/keyboard-shortcuts.test.tsx.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`keyboard-shortcuts when application is started renders 1`] = ` + +
+
+
+
+
+ +`; diff --git a/packages/business-features/keyboard-shortcuts/src/feature.ts b/packages/business-features/keyboard-shortcuts/src/feature.ts new file mode 100644 index 0000000000..32d5f2bc62 --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/src/feature.ts @@ -0,0 +1,17 @@ +import { getFeature } from "@k8slens/feature-core"; +import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration"; +import { reactApplicationFeature } from "@k8slens/react-application"; + +export const keyboardShortcutsFeature = getFeature({ + id: "keyboard-shortcuts", + + register: (di) => { + autoRegister({ + di, + targetModule: module, + getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)], + }); + }, + + dependencies: [reactApplicationFeature], +}); diff --git a/packages/business-features/keyboard-shortcuts/src/invoke-shortcut.injectable.ts b/packages/business-features/keyboard-shortcuts/src/invoke-shortcut.injectable.ts new file mode 100644 index 0000000000..5b0c324203 --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/src/invoke-shortcut.injectable.ts @@ -0,0 +1,94 @@ +import { pipeline } from "@ogre-tools/fp"; +import { filter, isString } from "lodash/fp"; +import { getInjectable } from "@ogre-tools/injectable"; +import { + Binding, + KeyboardShortcut, + keyboardShortcutInjectionToken, +} from "./keyboard-shortcut-injection-token"; +import platformInjectable from "./platform.injectable"; + +export type InvokeShortcut = (event: KeyboardEvent) => void; + +const toShortcutsWithMatchingScope = (shortcut: KeyboardShortcut) => { + const activeScopeElement = document.activeElement?.closest("[data-keyboard-shortcut-scope]"); + + if (!activeScopeElement) { + const shortcutIsRootLevel = !shortcut.scope; + + return shortcutIsRootLevel; + } + + const castedActiveScopeElementHtml = activeScopeElement as HTMLDivElement; + + // eslint-disable-next-line xss/no-mixed-html + const activeScope = castedActiveScopeElementHtml.dataset.keyboardShortcutScope; + + return shortcut.scope === activeScope; +}; + +const toBindingWithDefaults = (binding: Binding) => + isString(binding) + ? { + code: binding, + shift: false, + ctrl: false, + altOrOption: false, + meta: false, + ctrlOrCommand: false, + } + : { + ctrl: false, + shift: false, + altOrOption: false, + meta: false, + ctrlOrCommand: false, + ...binding, + }; + +const toShortcutsWithMatchingBinding = + (event: KeyboardEvent, platform: string) => (shortcut: KeyboardShortcut) => { + const binding = toBindingWithDefaults(shortcut.binding); + + const shiftModifierMatches = binding.shift === event.shiftKey; + const altModifierMatches = binding.altOrOption === event.altKey; + + const isMac = platform === "darwin"; + + const ctrlModifierMatches = + binding.ctrl === event.ctrlKey || (!isMac && binding.ctrlOrCommand === event.ctrlKey); + + const metaModifierMatches = + binding.meta === event.metaKey || (isMac && binding.ctrlOrCommand === event.metaKey); + + return ( + event.code === binding.code && + shiftModifierMatches && + ctrlModifierMatches && + altModifierMatches && + metaModifierMatches + ); + }; + +const invokeShortcutInjectable = getInjectable({ + id: "invoke-shortcut", + + instantiate: (di): InvokeShortcut => { + const getShortcuts = () => di.injectMany(keyboardShortcutInjectionToken); + const platform = di.inject(platformInjectable); + + return (event) => { + const shortcutsToInvoke = pipeline( + getShortcuts(), + filter(toShortcutsWithMatchingBinding(event, platform)), + filter(toShortcutsWithMatchingScope), + ); + + if (shortcutsToInvoke.length) { + shortcutsToInvoke.forEach((shortcut) => shortcut.invoke()); + } + }; + }, +}); + +export default invokeShortcutInjectable; diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-injection-token.ts b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-injection-token.ts new file mode 100644 index 0000000000..637990b107 --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-injection-token.ts @@ -0,0 +1,22 @@ +import { getInjectionToken } from "@ogre-tools/injectable"; + +export type Binding = + | string + | { + code: string; + shift?: boolean; + ctrl?: boolean; + altOrOption?: boolean; + meta?: boolean; + ctrlOrCommand?: boolean; + }; + +export type KeyboardShortcut = { + binding: Binding; + invoke: () => void; + scope?: string; +}; + +export const keyboardShortcutInjectionToken = getInjectionToken({ + id: "keyboard-shortcut-injection-token", +}); diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener-react-application-hoc.injectable.tsx b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener-react-application-hoc.injectable.tsx new file mode 100644 index 0000000000..ed22ef7fe8 --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener-react-application-hoc.injectable.tsx @@ -0,0 +1,10 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import { KeyboardShortcutListener } from "./keyboard-shortcut-listener"; +import { reactApplicationHigherOrderComponentInjectionToken } from "@k8slens/react-application"; + +export const keyboardShortcutListenerReactApplicationHocInjectable = getInjectable({ + id: "keyboard-shortcut-listener-react-application-hoc", + instantiate: () => KeyboardShortcutListener, + + injectionToken: reactApplicationHigherOrderComponentInjectionToken, +}); diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener.tsx b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener.tsx new file mode 100644 index 0000000000..5f2479d059 --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener.tsx @@ -0,0 +1,41 @@ +import { withInjectables } from "@ogre-tools/injectable-react"; +import React, { useEffect } from "react"; + +import invokeShortcutInjectable, { InvokeShortcut } from "./invoke-shortcut.injectable"; + +export interface KeyboardShortcutListenerProps { + children: React.ReactNode; +} + +interface Dependencies { + invokeShortcut: InvokeShortcut; +} + +const NonInjectedKeyboardShortcutListener = ({ + children, + invokeShortcut, +}: KeyboardShortcutListenerProps & Dependencies) => { + useEffect(() => { + document.addEventListener("keydown", invokeShortcut); + + return () => { + document.removeEventListener("keydown", invokeShortcut); + }; + }); + + return <>{children}; +}; + +export const KeyboardShortcutListener = withInjectables< + Dependencies, + KeyboardShortcutListenerProps +>( + NonInjectedKeyboardShortcutListener, + + { + getProps: (di, props) => ({ + invokeShortcut: di.inject(invokeShortcutInjectable), + ...props, + }), + }, +); diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-scope.tsx b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-scope.tsx new file mode 100644 index 0000000000..0e725cc4d4 --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-scope.tsx @@ -0,0 +1,12 @@ +import React from "react"; + +export interface KeyboardShortcutScopeProps { + id: string; + children: React.ReactNode; +} + +export const KeyboardShortcutScope = ({ id, children }: KeyboardShortcutScopeProps) => ( +
+ {children} +
+); diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx new file mode 100644 index 0000000000..d2bbfb85a4 --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx @@ -0,0 +1,353 @@ +import userEvent from "@testing-library/user-event"; +import type { RenderResult } from "@testing-library/react"; +import { render } from "@testing-library/react"; +import { createContainer, DiContainer, getInjectable } from "@ogre-tools/injectable"; +import { registerInjectableReact } from "@ogre-tools/injectable-react"; +import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx"; +import { keyboardShortcutInjectionToken } from "./keyboard-shortcut-injection-token"; +import { registerFeature } from "@k8slens/feature-core"; +import { keyboardShortcutsFeature } from "./feature"; +import React from "react"; +import { computed, runInAction } from "mobx"; +import { KeyboardShortcutScope } from "./keyboard-shortcut-scope"; +import { Discover, discoverFor } from "@k8slens/react-testing-library-discovery"; +import { startApplicationInjectionToken } from "@k8slens/application"; +import { renderInjectionToken } from "@k8slens/react-application"; +import { reactApplicationChildrenInjectionToken } from "@k8slens/react-application"; +import platformInjectable from "./platform.injectable"; + +describe("keyboard-shortcuts", () => { + let di: DiContainer; + let invokeMock: jest.Mock; + let rendered: RenderResult; + + beforeEach(() => { + di = createContainer("irrelevant"); + + registerInjectableReact(di); + registerMobX(di); + + runInAction(() => { + registerFeature(di, keyboardShortcutsFeature); + }); + + invokeMock = jest.fn(); + + const someKeyboardShortcutInjectable = getInjectable({ + id: "some-keyboard-shortcut", + + instantiate: () => ({ + binding: "Escape", + invoke: () => invokeMock("esc-in-root"), + }), + + injectionToken: keyboardShortcutInjectionToken, + }); + + const someScopedKeyboardShortcutInjectable = getInjectable({ + id: "some-scoped-keyboard-shortcut", + + instantiate: () => ({ + binding: "Escape", + invoke: () => invokeMock("esc-in-scope"), + scope: "some-scope", + }), + + injectionToken: keyboardShortcutInjectionToken, + }); + + const someOtherKeyboardShortcutInjectable = getInjectable({ + id: "some-other-keyboard-shortcut", + + instantiate: () => ({ + binding: "something-else-than-esc", + invoke: () => invokeMock("something-else-than-esc"), + }), + + injectionToken: keyboardShortcutInjectionToken, + }); + + const childComponentForScopeInjectable = getInjectable({ + id: "some-child-component-for-scope", + + instantiate: () => ({ + id: "some-child-component-for-scope", + + enabled: computed(() => true), + + Component: () => ( + +
+ + ), + }), + + injectionToken: reactApplicationChildrenInjectionToken, + }); + + runInAction(() => { + di.register( + someKeyboardShortcutInjectable, + someScopedKeyboardShortcutInjectable, + someOtherKeyboardShortcutInjectable, + childComponentForScopeInjectable, + ); + }); + + di.override(renderInjectionToken, () => (application) => { + rendered = render(application); + }); + }); + + describe("when application is started", () => { + let discover: Discover; + + beforeEach(async () => { + const startApplication = di.inject(startApplicationInjectionToken); + + await startApplication(); + + discover = discoverFor(() => rendered); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + it("given focus is in the body, when pressing the shortcut, calls shortcut in global scope", () => { + userEvent.keyboard("{Escape}"); + + expect(invokeMock.mock.calls).toEqual([["esc-in-root"]]); + }); + + it("given focus inside a nested scope, when pressing the shortcut, calls only the callback for the scope", () => { + const result = discover.getSingleElement("keyboard-shortcut-scope", "some-scope"); + + const discoveredHtml = result.discovered as HTMLDivElement; + + discoveredHtml.focus(); + + userEvent.keyboard("{Escape}"); + + expect(invokeMock.mock.calls).toEqual([["esc-in-scope"]]); + }); + + it("given conflicting shortcut, when pressing the shortcut, calls both callbacks", () => { + const conflictingShortcutInjectable = getInjectable({ + id: "some-conflicting-keyboard-shortcut", + + instantiate: () => ({ + binding: "Escape", + invoke: () => invokeMock("conflicting-esc-in-root"), + }), + + injectionToken: keyboardShortcutInjectionToken, + }); + + runInAction(() => { + di.register(conflictingShortcutInjectable); + }); + + userEvent.keyboard("{Escape}"); + + expect(invokeMock.mock.calls).toEqual([["esc-in-root"], ["conflicting-esc-in-root"]]); + }); + + [ + { + scenario: "given shortcut without modifiers, when shortcut is pressed, calls the callback", + binding: { code: "Escape" }, + keyboard: "{Escape}", + shouldCallCallback: true, + }, + { + scenario: + "given shortcut without modifiers, when shortcut is pressed but with modifier, does not call the callback", + binding: { code: "F1" }, + keyboard: "{Meta>}[F1]", + shouldCallCallback: false, + }, + { + scenario: "given shortcut with meta modifier, when shortcut is pressed, calls the callback", + + binding: { meta: true, code: "F1" }, + keyboard: "{Meta>}[F1]", + shouldCallCallback: true, + }, + { + scenario: + "given shortcut with shift modifier, when shortcut is pressed, calls the callback", + + binding: { shift: true, code: "F1" }, + keyboard: "{Shift>}[F1]", + shouldCallCallback: true, + }, + { + scenario: "given shortcut with alt modifier, when shortcut is pressed, calls the callback", + binding: { altOrOption: true, code: "F1" }, + keyboard: "{Alt>}[F1]", + shouldCallCallback: true, + }, + { + scenario: "given shortcut with ctrl modifier, when shortcut is pressed, calls the callback", + binding: { ctrl: true, code: "F1" }, + keyboard: "{Control>}[F1]", + shouldCallCallback: true, + }, + { + scenario: "given shortcut with all modifiers, when shortcut is pressed, calls the callback", + + binding: { ctrl: true, altOrOption: true, shift: true, meta: true, code: "F1" }, + keyboard: "{Meta>}{Shift>}{Alt>}{Control>}[F1]", + shouldCallCallback: true, + }, + ].forEach(({ binding, keyboard, scenario, shouldCallCallback }) => { + // eslint-disable-next-line jest/valid-title + it(scenario, () => { + const invokeMock = jest.fn(); + + const shortcutInjectable = getInjectable({ + id: "shortcut", + + instantiate: () => ({ + binding, + invoke: invokeMock, + }), + + injectionToken: keyboardShortcutInjectionToken, + }); + + runInAction(() => { + di.register(shortcutInjectable); + }); + + userEvent.keyboard(keyboard); + + if (shouldCallCallback) { + // eslint-disable-next-line jest/no-conditional-expect + expect(invokeMock).toHaveBeenCalled(); + } else { + // eslint-disable-next-line jest/no-conditional-expect + expect(invokeMock).not.toHaveBeenCalled(); + } + }); + }); + }); + + describe("given in mac and keyboard shortcut with modifier for ctrl or command", () => { + beforeEach(async () => { + di.override(platformInjectable, () => "darwin"); + + invokeMock = jest.fn(); + + const shortcutInjectable = getInjectable({ + id: "shortcut", + + instantiate: () => ({ + binding: { code: "KeyK", ctrlOrCommand: true }, + invoke: invokeMock, + }), + + injectionToken: keyboardShortcutInjectionToken, + }); + + runInAction(() => { + di.register(shortcutInjectable); + }); + + const startApplication = di.inject(startApplicationInjectionToken); + + await startApplication(); + }); + + it("when pressing the keyboard shortcut with command, calls the callback", () => { + userEvent.keyboard("{Meta>}[KeyK]"); + + expect(invokeMock).toHaveBeenCalled(); + }); + + it("when pressing the keyboard shortcut with ctrl, does not call the callback", () => { + userEvent.keyboard("{Control>}[KeyK]"); + + expect(invokeMock).not.toHaveBeenCalled(); + }); + }); + + describe("given in windows and keyboard shortcut with modifier for ctrl or command", () => { + beforeEach(async () => { + di.override(platformInjectable, () => "win32"); + + invokeMock = jest.fn(); + + const shortcutInjectable = getInjectable({ + id: "shortcut", + + instantiate: () => ({ + binding: { code: "KeyK", ctrlOrCommand: true }, + invoke: invokeMock, + }), + + injectionToken: keyboardShortcutInjectionToken, + }); + + runInAction(() => { + di.register(shortcutInjectable); + }); + + const startApplication = di.inject(startApplicationInjectionToken); + + await startApplication(); + }); + + it("when pressing the keyboard shortcut with windows, does not call the callback", () => { + userEvent.keyboard("{Meta>}[KeyK]"); + + expect(invokeMock).not.toHaveBeenCalled(); + }); + + it("when pressing the keyboard shortcut with ctrl, calls the callback", () => { + userEvent.keyboard("{Control>}[KeyK]"); + + expect(invokeMock).toHaveBeenCalled(); + }); + }); + + describe("given in any other platform and keyboard shortcut with modifier for ctrl or command", () => { + beforeEach(async () => { + di.override(platformInjectable, () => "some-other-platform"); + + invokeMock = jest.fn(); + + const shortcutInjectable = getInjectable({ + id: "shortcut", + + instantiate: () => ({ + binding: { code: "KeyK", ctrlOrCommand: true }, + invoke: invokeMock, + }), + + injectionToken: keyboardShortcutInjectionToken, + }); + + runInAction(() => { + di.register(shortcutInjectable); + }); + + const startApplication = di.inject(startApplicationInjectionToken); + + await startApplication(); + }); + + it("when pressing the keyboard shortcut with meta, does not call the callback", () => { + userEvent.keyboard("{Meta>}[KeyK]"); + + expect(invokeMock).not.toHaveBeenCalled(); + }); + + it("when pressing the keyboard shortcut with ctrl, calls the callback", () => { + userEvent.keyboard("{Control>}[KeyK]"); + + expect(invokeMock).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/business-features/keyboard-shortcuts/src/platform.injectable.ts b/packages/business-features/keyboard-shortcuts/src/platform.injectable.ts new file mode 100644 index 0000000000..407af8a43d --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/src/platform.injectable.ts @@ -0,0 +1,11 @@ +import { getInjectable } from "@ogre-tools/injectable"; + +export const allPlatforms = ["win32", "darwin", "linux"] as const; + +const platformInjectable = getInjectable({ + id: "platform", + instantiate: () => process.platform as (typeof allPlatforms)[number], + causesSideEffects: true, +}); + +export default platformInjectable; diff --git a/packages/business-features/keyboard-shortcuts/tsconfig.json b/packages/business-features/keyboard-shortcuts/tsconfig.json new file mode 100644 index 0000000000..9e140d79da --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@k8slens/typescript/config/base.json", + "include": ["**/*.ts", "**/*.tsx"], +} diff --git a/packages/business-features/keyboard-shortcuts/webpack.config.js b/packages/business-features/keyboard-shortcuts/webpack.config.js new file mode 100644 index 0000000000..1cda407f5a --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/webpack.config.js @@ -0,0 +1 @@ +module.exports = require("@k8slens/webpack").configForReact; diff --git a/packages/open-lens/package.json b/packages/open-lens/package.json index 7fa19eeb23..c229a32d58 100644 --- a/packages/open-lens/package.json +++ b/packages/open-lens/package.json @@ -190,6 +190,7 @@ "@k8slens/core": "^6.5.0-alpha.3", "@k8slens/ensure-binaries": "^6.5.0-alpha.1", "@k8slens/feature-core": "^6.5.0-alpha.1", + "@k8slens/keyboard-shortcuts": "^1.0.0-alpha.0", "@k8slens/legacy-extension-example": "^1.0.0-alpha.1", "@k8slens/legacy-extensions": "^1.0.0-alpha.1", "@k8slens/messaging": "^1.0.0-alpha.1", diff --git a/packages/open-lens/src/renderer/index.ts b/packages/open-lens/src/renderer/index.ts index 2d5dda2f04..d14e817347 100644 --- a/packages/open-lens/src/renderer/index.ts +++ b/packages/open-lens/src/renderer/index.ts @@ -15,6 +15,7 @@ import { createContainer } from "@ogre-tools/injectable"; import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx"; import { registerInjectableReact } from "@ogre-tools/injectable-react"; import { messagingFeatureForRenderer } from "@k8slens/messaging-for-renderer"; +import { keyboardShortcutsFeature } from "@k8slens/keyboard-shortcuts"; import { reactApplicationFeature } from "@k8slens/react-application"; const environment = "renderer"; @@ -30,6 +31,7 @@ runInAction(() => { di, applicationFeature, messagingFeatureForRenderer, + keyboardShortcutsFeature, reactApplicationFeature );