diff --git a/.eslintrc.js b/.eslintrc.js index 1415136093..dba0a666d9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -108,6 +108,8 @@ module.exports = { parser: "@typescript-eslint/parser", extends: [ "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", ], plugins: [ "header", @@ -193,6 +195,8 @@ module.exports = { extends: [ "plugin:@typescript-eslint/recommended", "plugin:react/recommended", + "plugin:import/recommended", + "plugin:import/typescript", ], parserOptions: { ecmaVersion: 2018, @@ -202,6 +206,7 @@ module.exports = { rules: { "no-irregular-whitespace": "error", "header/header": [2, "./license-header"], + "react/prop-types": "off", "no-invalid-this": "off", "@typescript-eslint/no-invalid-this": ["error"], "@typescript-eslint/explicit-function-return-type": "off", @@ -246,7 +251,6 @@ module.exports = { "objectsInObjects": false, "arraysInObjects": true, }], - "react/prop-types": "off", "semi": "off", "@typescript-eslint/semi": ["error"], "linebreak-style": ["error", "unix"], diff --git a/docs/extensions/capabilities/common-capabilities.md b/docs/extensions/capabilities/common-capabilities.md index 8ba03253c5..e7b585af66 100644 --- a/docs/extensions/capabilities/common-capabilities.md +++ b/docs/extensions/capabilities/common-capabilities.md @@ -37,9 +37,9 @@ export default class ExampleMainExtension extends Main.LensExtension { } ``` -### App Menus +### Menus -This extension can register custom app menus that will be displayed on OS native menus. +This extension can register custom app and tray menus that will be displayed on OS native menus. Example: @@ -56,6 +56,29 @@ export default class ExampleMainExtension extends Main.LensExtension { } } ] + + trayMenus = [ + { + label: "My links", + submenu: [ + { + label: "Lens", + click() { + Main.Navigation.navigate("https://k8slens.dev"); + } + }, + { + type: "separator" + }, + { + label: "Lens Github", + click() { + Main.Navigation.navigate("https://github.com/lensapp/lens"); + } + } + ] + } + ] } ``` diff --git a/docs/extensions/guides/main-extension.md b/docs/extensions/guides/main-extension.md index fec937db17..a0e20880bf 100644 --- a/docs/extensions/guides/main-extension.md +++ b/docs/extensions/guides/main-extension.md @@ -3,7 +3,7 @@ The Main Extension API is the interface to Lens's main process. Lens runs in both main and renderer processes. The Main Extension API allows you to access, configure, and customize Lens data, add custom application menu items and [protocol handlers](protocol-handlers.md), and run custom code in Lens's main process. -It also provides convenient methods for navigating to built-in Lens pages and extension pages, as well as adding and removing sources of catalog entities. +It also provides convenient methods for navigating to built-in Lens pages and extension pages, as well as adding and removing sources of catalog entities. ## `Main.LensExtension` Class @@ -45,7 +45,6 @@ For more details on accessing Lens state data, please see the [Stores](../stores ### `appMenus` The Main Extension API allows you to customize the UI application menu. -Note that this is the only UI feature that the Main Extension API allows you to customize. The following example demonstrates adding an item to the **Help** menu. ``` typescript @@ -65,7 +64,7 @@ export default class SamplePageMainExtension extends Main.LensExtension { ``` `appMenus` is an array of objects that satisfy the `MenuRegistration` interface. -`MenuRegistration` extends React's `MenuItemConstructorOptions` interface. +`MenuRegistration` extends Electron's `MenuItemConstructorOptions` interface. The properties of the appMenus array objects are defined as follows: * `parentId` is the name of the menu where your new menu item will be listed. @@ -96,6 +95,35 @@ export default class SamplePageMainExtension extends Main.LensExtension { When the menu item is clicked the `navigate()` method looks for and displays a global page with id `"myGlobalPage"`. This page would be defined in your extension's `Renderer.LensExtension` implementation (See [`Renderer.LensExtension`](renderer-extension.md)). +### `trayMenus` + +`trayMenus` is an array of `TrayMenuRegistration` objects. Most importantly you can define a `label` and a `click` handler. Other properties are `submenu`, `enabled`, `toolTip`, `id` and `type`. + +``` typescript +interface TrayMenuRegistration { + label?: string; + click?: (menuItem: TrayMenuRegistration) => void; + id?: string; + type?: "normal" | "separator" | "submenu" + toolTip?: string; + enabled?: boolean; + submenu?: TrayMenuRegistration[] +} +``` + +The following example demonstrates how tray menus can be added from extension: + +``` typescript +import { Main } from "@k8slens/extensions"; + +export default class SampleTrayMenuMainExtension extends Main.LensExtension { + trayMenus = [{ + label: "menu from the extension", + click: () => { console.log("tray menu clicked!") } + }] +} +``` + ### `addCatalogSource()` and `removeCatalogSource()` Methods The `Main.LensExtension` class also provides the `addCatalogSource()` and `removeCatalogSource()` methods, for managing custom catalog items (or entities). diff --git a/extensions/.eslintrc.js b/extensions/.eslintrc.js new file mode 100644 index 0000000000..d0daadb1aa --- /dev/null +++ b/extensions/.eslintrc.js @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +module.exports = { + "overrides": [ + { + files: [ + "**/*.ts", + "**/*.tsx", + ], + rules: { + "import/no-unresolved": ["error", { + ignore: ["@k8slens/extensions"], + }], + }, + }, + ], +}; diff --git a/package.json b/package.json index 93af1a9bdf..43497e5edd 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,7 @@ }, "moduleNameMapper": { "\\.(css|scss)$": "/__mocks__/styleMock.ts", - "\\.(svg)$": "/__mocks__/imageMock.ts", - "src/(.*)": "/__mocks__/windowMock.ts" + "\\.(svg)$": "/__mocks__/imageMock.ts" }, "modulePathIgnorePatterns": [ "/dist", @@ -196,10 +195,11 @@ "@hapi/call": "^8.0.1", "@hapi/subtext": "^7.0.3", "@kubernetes/client-node": "^0.16.1", - "@ogre-tools/injectable": "1.5.0", - "@ogre-tools/injectable-react": "1.5.2", + "@ogre-tools/injectable": "2.0.0", + "@ogre-tools/injectable-react": "2.0.0", "@sentry/electron": "^2.5.4", "@sentry/integrations": "^6.15.0", + "@types/circular-dependency-plugin": "5.0.4", "abort-controller": "^3.0.0", "auto-bind": "^4.0.0", "autobind-decorator": "^2.4.0", @@ -214,7 +214,7 @@ "filehound": "^1.17.5", "fs-extra": "^9.0.1", "glob-to-regexp": "^0.4.1", - "got": "^11.8.2", + "got": "^11.8.3", "grapheme-splitter": "^1.0.4", "handlebars": "^4.7.7", "http-proxy": "^1.18.1", @@ -265,7 +265,7 @@ "ws": "^7.5.5" }, "devDependencies": { - "@async-fn/jest": "^1.5.0", + "@async-fn/jest": "1.5.3", "@material-ui/core": "^4.12.3", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.60", @@ -333,7 +333,7 @@ "concurrently": "^5.3.0", "css-loader": "^5.2.7", "deepdash": "^5.3.9", - "dompurify": "^2.3.3", + "dompurify": "^2.3.4", "electron": "^13.6.1", "electron-builder": "^22.14.5", "electron-notarize": "^0.3.0", @@ -341,6 +341,7 @@ "esbuild-loader": "^2.16.0", "eslint": "^7.32.0", "eslint-plugin-header": "^3.1.1", + "eslint-plugin-import": "^2.25.3", "eslint-plugin-react": "^7.27.1", "eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-unused-imports": "^1.1.5", @@ -361,8 +362,7 @@ "nodemon": "^2.0.14", "playwright": "^1.17.1", "postcss": "^8.4.5", - "postcss-loader": "4.3.0", - "postinstall-postinstall": "^2.1.0", + "postcss-loader": "^4.3.0", "progress-bar-webpack-plugin": "^2.1.0", "randomcolor": "^0.6.2", "raw-loader": "^4.0.2", @@ -373,11 +373,11 @@ "react-select-event": "^5.1.0", "react-table": "^7.7.0", "react-window": "^1.8.6", - "sass": "^1.44.0", - "sass-loader": "^8.0.2", + "sass": "^1.45.1", + "sass-loader": "^10.2.0", "sharp": "^0.29.3", "style-loader": "^2.0.0", - "tailwindcss": "^2.2.19", + "tailwindcss": "^3.0.7", "ts-jest": "26.5.6", "ts-loader": "^7.0.5", "ts-node": "^10.4.0", diff --git a/src/common/__tests__/hotbar-store.test.ts b/src/common/__tests__/hotbar-store.test.ts index 092754394d..ffc4e361da 100644 --- a/src/common/__tests__/hotbar-store.test.ts +++ b/src/common/__tests__/hotbar-store.test.ts @@ -20,9 +20,11 @@ */ import { anyObject } from "jest-mock-extended"; +import { merge } from "lodash"; import mockFs from "mock-fs"; import logger from "../../main/logger"; import { AppPaths } from "../app-paths"; +import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog"; import { ClusterStore } from "../cluster-store"; import { HotbarStore } from "../hotbar-store"; @@ -54,68 +56,58 @@ jest.mock("../../main/catalog/catalog-entity-registry", () => ({ }, })); -const testCluster = { - uid: "test", - name: "test", +function getMockCatalogEntity(data: Partial & CatalogEntityKindData): CatalogEntity { + return merge(data, { + getName: jest.fn(() => data.metadata?.name), + getId: jest.fn(() => data.metadata?.uid), + getSource: jest.fn(() => data.metadata?.source ?? "unknown"), + isEnabled: jest.fn(() => data.status?.enabled ?? true), + onContextMenuOpen: jest.fn(), + onSettingsOpen: jest.fn(), + metadata: {}, + spec: {}, + status: {}, + }) as CatalogEntity; +} + +const testCluster = getMockCatalogEntity({ apiVersion: "v1", kind: "Cluster", status: { phase: "Running", }, - spec: {}, - getName: jest.fn(), - getId: jest.fn(), - onDetailsOpen: jest.fn(), - onContextMenuOpen: jest.fn(), - onSettingsOpen: jest.fn(), metadata: { uid: "test", name: "test", labels: {}, }, -}; +}); -const minikubeCluster = { - uid: "minikube", - name: "minikube", +const minikubeCluster = getMockCatalogEntity({ apiVersion: "v1", kind: "Cluster", status: { phase: "Running", }, - spec: {}, - getName: jest.fn(), - getId: jest.fn(), - onDetailsOpen: jest.fn(), - onContextMenuOpen: jest.fn(), - onSettingsOpen: jest.fn(), metadata: { uid: "minikube", name: "minikube", labels: {}, }, -}; +}); -const awsCluster = { - uid: "aws", - name: "aws", +const awsCluster = getMockCatalogEntity({ apiVersion: "v1", kind: "Cluster", status: { phase: "Running", }, - spec: {}, - getName: jest.fn(), - getId: jest.fn(), - onDetailsOpen: jest.fn(), - onContextMenuOpen: jest.fn(), - onSettingsOpen: jest.fn(), metadata: { uid: "aws", name: "aws", labels: {}, }, -}; +}); jest.mock("electron", () => ({ app: { diff --git a/src/common/__tests__/user-store.test.ts b/src/common/__tests__/user-store.test.ts index ba7272f4c9..f53e3286c2 100644 --- a/src/common/__tests__/user-store.test.ts +++ b/src/common/__tests__/user-store.test.ts @@ -42,9 +42,9 @@ import { Console } from "console"; import { SemVer } from "semver"; import electron from "electron"; import { stdout, stderr } from "process"; -import { ThemeStore } from "../../renderer/theme.store"; import type { ClusterStoreModel } from "../cluster-store"; import { AppPaths } from "../app-paths"; +import { defaultTheme } from "../vars"; console = new Console(stdout, stderr); AppPaths.init(); @@ -75,7 +75,7 @@ describe("user store tests", () => { us.httpsProxy = "abcd://defg"; expect(us.httpsProxy).toBe("abcd://defg"); - expect(us.colorTheme).toBe(ThemeStore.defaultTheme); + expect(us.colorTheme).toBe(defaultTheme); us.colorTheme = "light"; expect(us.colorTheme).toBe("light"); @@ -86,7 +86,7 @@ describe("user store tests", () => { us.colorTheme = "some other theme"; us.resetTheme(); - expect(us.colorTheme).toBe(ThemeStore.defaultTheme); + expect(us.colorTheme).toBe(defaultTheme); }); it("correctly calculates if the last seen version is an old release", () => { diff --git a/src/common/app-paths.ts b/src/common/app-paths.ts index d23cc8c1a4..6802cd8f88 100644 --- a/src/common/app-paths.ts +++ b/src/common/app-paths.ts @@ -23,7 +23,8 @@ import { app, ipcMain, ipcRenderer } from "electron"; import { observable, when } from "mobx"; import path from "path"; import logger from "./logger"; -import { fromEntries, toJS } from "./utils"; +import { fromEntries } from "./utils/objects"; +import { toJS } from "./utils/toJS"; import { isWindows } from "./vars"; export type PathName = Parameters[0]; diff --git a/src/common/catalog-entities/kubernetes-cluster.ts b/src/common/catalog-entities/kubernetes-cluster.ts index 1f4f933620..6901a9dad2 100644 --- a/src/common/catalog-entities/kubernetes-cluster.ts +++ b/src/common/catalog-entities/kubernetes-cluster.ts @@ -20,11 +20,10 @@ */ import { catalogCategoryRegistry } from "../catalog/catalog-category-registry"; -import { CatalogEntity, CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog"; +import { CatalogEntity, CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus, CatalogCategory, CatalogCategorySpec } from "../catalog"; import { clusterActivateHandler, clusterDisconnectHandler } from "../cluster-ipc"; import { ClusterStore } from "../cluster-store"; import { broadcastMessage, requestMain } from "../ipc"; -import { CatalogCategory, CatalogCategorySpec } from "../catalog"; import { app } from "electron"; import type { CatalogEntitySpec } from "../catalog/catalog-entity"; import { IpcRendererNavigationEvents } from "../../renderer/navigation/events"; diff --git a/src/common/catalog/catalog-entity.ts b/src/common/catalog/catalog-entity.ts index 9015dbd828..79083440c7 100644 --- a/src/common/catalog/catalog-entity.ts +++ b/src/common/catalog/catalog-entity.ts @@ -38,14 +38,44 @@ export type CatalogEntityConstructor = ( ); export interface CatalogCategoryVersion { + /** + * The specific version that the associated constructor is for. This MUST be + * a DNS label and SHOULD be of the form `vN`, `vNalphaY`, or `vNbetaY` where + * `N` and `Y` are both integers greater than 0. + * + * Examples: The following are valid values for this field. + * - `v1` + * - `v1beta1` + * - `v1alpha2` + * - `v3beta2` + */ name: string; + + /** + * The constructor for the entities. + */ entityClass: CatalogEntityConstructor; } export interface CatalogCategorySpec { + /** + * The grouping for for the category. This MUST be a DNS label. + */ group: string; + /** + * The specific versions of the constructors. + * + * NOTE: the field `.apiVersion` after construction MUST match `{.group}/{.versions.[] | .name}`. + * For example, if `group = "entity.k8slens.dev"` and there is an entry in `.versions` with + * `name = "v1alpha1"` then the resulting `.apiVersion` MUST be `entity.k8slens.dev/v1alpha1` + */ versions: CatalogCategoryVersion[]; names: { + /** + * The kind of entity that this category is for. This value MUST be a DNS + * label and MUST be equal to the `kind` fields that are produced by the + * `.versions.[] | .entityClass` fields. + */ kind: string; }; } @@ -114,6 +144,7 @@ export abstract class CatalogCategory extends (EventEmitter as new () => TypedEm export interface CatalogEntityMetadata { uid: string; name: string; + shortName?: string; description?: string; source?: string; labels: Record; @@ -211,7 +242,14 @@ export abstract class CatalogEntity< Status extends CatalogEntityStatus = CatalogEntityStatus, Spec extends CatalogEntitySpec = CatalogEntitySpec, > implements CatalogEntityKindData { + /** + * The group and version of this class. + */ public abstract readonly apiVersion: string; + + /** + * A DNS label name of the entity. + */ public abstract readonly kind: string; @observable metadata: Metadata; @@ -225,14 +263,35 @@ export abstract class CatalogEntity< this.spec = data.spec; } + /** + * Get the UID of this entity + */ public getId(): string { return this.metadata.uid; } + /** + * Get the name of this entity + */ public getName(): string { return this.metadata.name; } + /** + * Get the specified source of this entity, defaulting to `"unknown"` if not + * provided + */ + public getSource(): string { + return this.metadata.source ?? "unknown"; + } + + /** + * Get if this entity is enabled. + */ + public isEnabled(): boolean { + return this.status.enabled ?? true; + } + public abstract onRun?(context: CatalogEntityActionContext): void | Promise; public abstract onContextMenuOpen(context: CatalogEntityContextMenuContext): void | Promise; public abstract onSettingsOpen(context: CatalogEntitySettingsContext): void | Promise; diff --git a/postcss.config.js b/src/common/ipc/extension-loader.ipc.ts similarity index 91% rename from postcss.config.js rename to src/common/ipc/extension-loader.ipc.ts index 66746899cf..dae1c72316 100644 --- a/postcss.config.js +++ b/src/common/ipc/extension-loader.ipc.ts @@ -18,10 +18,4 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -module.exports = { - plugins: [ - require("tailwindcss/nesting"), - require("tailwindcss"), - ], -}; +export const BundledExtensionsLoaded = "extension-loader:bundled-extensions-loaded"; diff --git a/src/common/ipc/index.ts b/src/common/ipc/index.ts index ad1cf2a76c..774ce589e9 100644 --- a/src/common/ipc/index.ts +++ b/src/common/ipc/index.ts @@ -27,3 +27,4 @@ export * from "./update-available.ipc"; export * from "./cluster.ipc"; export * from "./type-enforced-ipc"; export * from "./hotbar"; +export * from "./extension-loader.ipc"; diff --git a/src/common/ipc/ipc.ts b/src/common/ipc/ipc.ts index b0eaa01e2a..0442d07630 100644 --- a/src/common/ipc/ipc.ts +++ b/src/common/ipc/ipc.ts @@ -30,7 +30,15 @@ import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames"; import type { Disposer } from "../utils"; import type remote from "@electron/remote"; -const electronRemote = ipcMain ? null : require("@electron/remote"); +const electronRemote = (() => { + if (ipcRenderer) { + try { + return require("@electron/remote"); + } catch {} + } + + return null; +})(); const subFramesChannel = "ipc:get-sub-frames"; diff --git a/src/common/k8s-api/__tests__/crd.test.ts b/src/common/k8s-api/__tests__/crd.test.ts index 0eba45e2cd..4403a9c7a4 100644 --- a/src/common/k8s-api/__tests__/crd.test.ts +++ b/src/common/k8s-api/__tests__/crd.test.ts @@ -19,10 +19,10 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { CustomResourceDefinition } from "../endpoints"; +import { CustomResourceDefinition, CustomResourceDefinitionSpec } from "../endpoints"; describe("Crds", () => { - describe("getVersion", () => { + describe("getVersion()", () => { it("should throw if none of the versions are served", () => { const crd = new CustomResourceDefinition({ apiVersion: "apiextensions.k8s.io/v1", @@ -136,7 +136,7 @@ describe("Crds", () => { expect(crd.getVersion()).toBe("123"); }); - it("should get the version name from the version field", () => { + it("should get the version name from the version field, ignoring versions on v1beta", () => { const crd = new CustomResourceDefinition({ apiVersion: "apiextensions.k8s.io/v1beta1", kind: "CustomResourceDefinition", @@ -147,7 +147,14 @@ describe("Crds", () => { }, spec: { version: "abc", - }, + versions: [ + { + name: "foobar", + served: true, + storage: true, + }, + ], + } as CustomResourceDefinitionSpec, }); expect(crd.getVersion()).toBe("abc"); diff --git a/src/common/k8s-api/endpoints/crd.api.ts b/src/common/k8s-api/endpoints/crd.api.ts index 4e2244d8a8..6b3f198f9d 100644 --- a/src/common/k8s-api/endpoints/crd.api.ts +++ b/src/common/k8s-api/endpoints/crd.api.ts @@ -48,34 +48,36 @@ export interface CRDVersion { additionalPrinterColumns?: AdditionalPrinterColumnsV1[]; } -export interface CustomResourceDefinition { - spec: { - group: string; - /** - * @deprecated for apiextensions.k8s.io/v1 but used previously - */ - version?: string; - names: { - plural: string; - singular: string; - kind: string; - listKind: string; - }; - scope: "Namespaced" | "Cluster" | string; - /** - * @deprecated for apiextensions.k8s.io/v1 but used previously - */ - validation?: object; - versions?: CRDVersion[]; - conversion: { - strategy?: string; - webhook?: any; - }; - /** - * @deprecated for apiextensions.k8s.io/v1 but used previously - */ - additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[]; +export interface CustomResourceDefinitionSpec { + group: string; + /** + * @deprecated for apiextensions.k8s.io/v1 but used in v1beta1 + */ + version?: string; + names: { + plural: string; + singular: string; + kind: string; + listKind: string; }; + scope: "Namespaced" | "Cluster"; + /** + * @deprecated for apiextensions.k8s.io/v1 but used in v1beta1 + */ + validation?: object; + versions?: CRDVersion[]; + conversion: { + strategy?: string; + webhook?: any; + }; + /** + * @deprecated for apiextensions.k8s.io/v1 but used in v1beta1 + */ + additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[]; +} + +export interface CustomResourceDefinition { + spec: CustomResourceDefinitionSpec; status: { conditions: { lastTransitionTime: string; @@ -150,27 +152,30 @@ export class CustomResourceDefinition extends KubeObject { } getPreferedVersion(): CRDVersion { - // Prefer the modern `versions` over the legacy `version` - if (this.spec.versions) { - for (const version of this.spec.versions) { - if (version.storage) { - return version; - } - } - } else if (this.spec.version) { - const { additionalPrinterColumns: apc } = this.spec; - const additionalPrinterColumns = apc?.map(({ JSONPath, ...apc }) => ({ ...apc, jsonPath: JSONPath })); + const { apiVersion } = this; - return { - name: this.spec.version, - served: true, - storage: true, - schema: this.spec.validation, - additionalPrinterColumns, - }; + switch (apiVersion) { + case "apiextensions.k8s.io/v1": + for (const version of this.spec.versions) { + if (version.storage) { + return version; + } + } + break; + case "apiextensions.k8s.io/v1beta1": + const { additionalPrinterColumns: apc } = this.spec; + const additionalPrinterColumns = apc?.map(({ JSONPath, ...apc }) => ({ ...apc, jsonPath: JSONPath })); + + return { + name: this.spec.version, + served: true, + storage: true, + schema: this.spec.validation, + additionalPrinterColumns, + }; } - throw new Error(`Failed to find a version for CustomResourceDefinition ${this.metadata.name}`); + throw new Error(`Unknown apiVersion=${apiVersion}: Failed to find a version for CustomResourceDefinition ${this.metadata.name}`); } getVersion() { @@ -197,7 +202,7 @@ export class CustomResourceDefinition extends KubeObject { const columns = this.getPreferedVersion().additionalPrinterColumns ?? []; return columns - .filter(column => column.name != "Age" && (ignorePriority || !column.priority)); + .filter(column => column.name.toLowerCase() != "age" && (ignorePriority || !column.priority)); } getValidation() { diff --git a/src/common/k8s-api/kube-api.ts b/src/common/k8s-api/kube-api.ts index 0f31758ab2..a5d9a91347 100644 --- a/src/common/k8s-api/kube-api.ts +++ b/src/common/k8s-api/kube-api.ts @@ -34,6 +34,9 @@ import type { IKubeWatchEvent } from "./kube-watch-api"; import { KubeJsonApi, KubeJsonApiData } from "./kube-json-api"; import { noop } from "../utils"; import type { RequestInit } from "node-fetch"; + +// BUG: https://github.com/mysticatea/abort-controller/pull/22 +// eslint-disable-next-line import/no-named-as-default import AbortController from "abort-controller"; import { Agent, AgentOptions } from "https"; import type { Patch } from "rfc6902"; diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index 1832cdfa10..6858a96907 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -30,6 +30,9 @@ import { ensureObjectSelfLink, IKubeApiQueryParams, KubeApi } from "./kube-api"; import { parseKubeApi } from "./kube-api-parse"; import type { KubeJsonApiData } from "./kube-json-api"; import type { RequestInit } from "node-fetch"; + +// BUG: https://github.com/mysticatea/abort-controller/pull/22 +// eslint-disable-next-line import/no-named-as-default import AbortController from "abort-controller"; import type { Patch } from "rfc6902"; diff --git a/src/common/user-store/preferences-helpers.ts b/src/common/user-store/preferences-helpers.ts index 450c75ae89..1ded307f0d 100644 --- a/src/common/user-store/preferences-helpers.ts +++ b/src/common/user-store/preferences-helpers.ts @@ -22,11 +22,11 @@ import moment from "moment-timezone"; import path from "path"; import os from "os"; -import { ThemeStore } from "../../renderer/theme.store"; import { getAppVersion, ObservableToggleSet } from "../utils"; import type { editor } from "monaco-editor"; import merge from "lodash/merge"; import { SemVer } from "semver"; +import { defaultTheme } from "../vars"; export interface KubeconfigSyncEntry extends KubeconfigSyncValue { filePath: string; @@ -72,10 +72,10 @@ const shell: PreferenceDescription = { const colorTheme: PreferenceDescription = { fromStore(val) { - return val || ThemeStore.defaultTheme; + return val || defaultTheme; }, toStore(val) { - if (!val || val === ThemeStore.defaultTheme) { + if (!val || val === defaultTheme) { return undefined; } diff --git a/src/common/utils/tuple.ts b/src/common/utils/tuple.ts index 5a252cf89c..b7b751bb30 100644 --- a/src/common/utils/tuple.ts +++ b/src/common/utils/tuple.ts @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { array } from "../utils"; +import * as array from "../utils/array"; /** * A strict N-tuple of type T diff --git a/src/common/vars.ts b/src/common/vars.ts index 138626a04b..b3d75f9bf3 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -41,6 +41,7 @@ export const isIntegrationTesting = process.argv.includes(integrationTestingArg) export const productName = packageInfo.productName; export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`; export const publicPath = "/build/" as string; +export const defaultTheme = "lens-dark" as string; // Webpack build paths export const contextDir = process.cwd(); diff --git a/src/extensions/extension-loader/extension-loader.injectable.ts b/src/extensions/extension-loader/extension-loader.injectable.ts index 70922daf94..ffb87e3b97 100644 --- a/src/extensions/extension-loader/extension-loader.injectable.ts +++ b/src/extensions/extension-loader/extension-loader.injectable.ts @@ -18,14 +18,12 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import { ExtensionLoader } from "./extension-loader"; -const extensionLoaderInjectable: Injectable = { - getDependencies: () => ({}), +const extensionLoaderInjectable = getInjectable({ instantiate: () => new ExtensionLoader(), lifecycle: lifecycleEnum.singleton, -}; +}); export default extensionLoaderInjectable; diff --git a/src/extensions/extension-loader/extension-loader.ts b/src/extensions/extension-loader/extension-loader.ts index b7d53402ed..00fb397079 100644 --- a/src/extensions/extension-loader/extension-loader.ts +++ b/src/extensions/extension-loader/extension-loader.ts @@ -255,17 +255,15 @@ export class ExtensionLoader { loadOnClusterManagerRenderer() { logger.debug(`${logModule}: load on main renderer (cluster manager)`); - this.autoInitExtensions(async (extension: LensRendererExtension) => { + + return this.autoInitExtensions(async (extension: LensRendererExtension) => { const removeItems = [ registries.GlobalPageRegistry.getInstance().add(extension.globalPages, extension), registries.AppPreferenceRegistry.getInstance().add(extension.appPreferences), registries.EntitySettingRegistry.getInstance().add(extension.entitySettings), registries.StatusBarRegistry.getInstance().add(extension.statusBarItems), registries.CommandRegistry.getInstance().add(extension.commands), - registries.WelcomeMenuRegistry.getInstance().add(extension.welcomeMenus), - registries.WelcomeBannerRegistry.getInstance().add(extension.welcomeBanners), registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems), - registries.TopBarRegistry.getInstance().add(extension.topBarItems), ]; this.events.on("remove", (removedExtension: LensRendererExtension) => { @@ -311,7 +309,9 @@ export class ExtensionLoader { } protected autoInitExtensions(register: (ext: LensExtension) => Promise) { - return reaction(() => this.toJSON(), installedExtensions => { + const loadingExtensions: { isBundled: boolean, loaded: Promise }[] = []; + + reaction(() => this.toJSON(), installedExtensions => { for (const [extId, extension] of installedExtensions) { const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(extension.manifest.name); @@ -326,7 +326,14 @@ export class ExtensionLoader { const instance = new LensExtensionClass(extension); - instance.enable(register); + const loaded = instance.enable(register).catch((err) => { + logger.error(`${logModule}: failed to enable`, { ext: extension, err }); + }); + + loadingExtensions.push({ + isBundled: extension.isBundled, + loaded, + }); this.instances.set(extId, instance); } catch (err) { logger.error(`${logModule}: activation extension error`, { ext: extension, err }); @@ -338,6 +345,8 @@ export class ExtensionLoader { }, { fireImmediately: true, }); + + return loadingExtensions; } protected requireExtension(extension: InstalledExtension): LensExtensionConstructor | null { diff --git a/src/extensions/extensions.injectable.ts b/src/extensions/extensions.injectable.ts index f05493bbbf..b8afde0f0b 100644 --- a/src/extensions/extensions.injectable.ts +++ b/src/extensions/extensions.injectable.ts @@ -18,24 +18,18 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { Injectable, lifecycleEnum } from "@ogre-tools/injectable"; -import { computed, IComputedValue } from "mobx"; -import type { LensExtension } from "./lens-extension"; -import type { ExtensionLoader } from "./extension-loader"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; import extensionLoaderInjectable from "./extension-loader/extension-loader.injectable"; -const extensionsInjectable: Injectable< - IComputedValue, - { extensionLoader: ExtensionLoader } -> = { - getDependencies: di => ({ - extensionLoader: di.inject(extensionLoaderInjectable), - }), +const extensionsInjectable = getInjectable({ + instantiate: (di) => { + const extensionLoader = di.inject(extensionLoaderInjectable); + + return computed(() => extensionLoader.enabledExtensionInstances); + }, lifecycle: lifecycleEnum.singleton, - - instantiate: ({ extensionLoader }) => - computed(() => extensionLoader.enabledExtensionInstances), -}; +}); export default extensionsInjectable; diff --git a/src/extensions/lens-main-extension.ts b/src/extensions/lens-main-extension.ts index c0c0a5674a..00f08ca5e5 100644 --- a/src/extensions/lens-main-extension.ts +++ b/src/extensions/lens-main-extension.ts @@ -25,9 +25,10 @@ import { catalogEntityRegistry } from "../main/catalog"; import type { CatalogEntity } from "../common/catalog"; import type { IObservableArray } from "mobx"; import type { MenuRegistration } from "../main/menu/menu-registration"; - +import type { TrayMenuRegistration } from "../main/tray/tray-menu-registration"; export class LensMainExtension extends LensExtension { appMenus: MenuRegistration[] = []; + trayMenus: TrayMenuRegistration[] = []; async navigate(pageId?: string, params?: Record, frameId?: number) { return WindowManager.getInstance().navigateExtension(this.id, pageId, params, frameId); diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index 67567969ee..9f7b947d82 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -26,7 +26,10 @@ import type { CatalogEntity } from "../common/catalog"; import type { Disposer } from "../common/utils"; import { catalogEntityRegistry, EntityFilter } from "../renderer/api/catalog-entity-registry"; import { catalogCategoryRegistry, CategoryFilter } from "../renderer/api/catalog-category-registry"; +import type { TopBarRegistration } from "../renderer/components/layout/top-bar/top-bar-registration"; import type { KubernetesCluster } from "../common/catalog-entities"; +import type { WelcomeMenuRegistration } from "../renderer/components/+welcome/welcome-menu-items/welcome-menu-registration"; +import type { WelcomeBannerRegistration } from "../renderer/components/+welcome/welcome-banner-items/welcome-banner-registration"; export class LensRendererExtension extends LensExtension { globalPages: registries.PageRegistration[] = []; @@ -40,10 +43,10 @@ export class LensRendererExtension extends LensExtension { kubeObjectMenuItems: registries.KubeObjectMenuRegistration[] = []; kubeWorkloadsOverviewItems: registries.WorkloadsOverviewDetailRegistration[] = []; commands: registries.CommandRegistration[] = []; - welcomeMenus: registries.WelcomeMenuRegistration[] = []; - welcomeBanners: registries.WelcomeBannerRegistration[] = []; + welcomeMenus: WelcomeMenuRegistration[] = []; + welcomeBanners: WelcomeBannerRegistration[] = []; catalogEntityDetailItems: registries.CatalogEntityDetailRegistration[] = []; - topBarItems: registries.TopBarRegistration[] = []; + topBarItems: TopBarRegistration[] = []; async navigate

(pageId?: string, params?: P) { const { navigate } = await import("../renderer/navigation"); diff --git a/src/extensions/main-extensions.injectable.ts b/src/extensions/main-extensions.injectable.ts new file mode 100644 index 0000000000..f0cb861c2b --- /dev/null +++ b/src/extensions/main-extensions.injectable.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import type { IComputedValue } from "mobx"; +import extensionsInjectable from "./extensions.injectable"; +import type { LensMainExtension } from "./lens-main-extension"; + +const mainExtensionsInjectable = getInjectable({ + lifecycle: lifecycleEnum.singleton, + + instantiate: (di) => + di.inject(extensionsInjectable) as IComputedValue, +}); + +export default mainExtensionsInjectable; diff --git a/src/extensions/registries/base-registry.ts b/src/extensions/registries/base-registry.ts index 9b596172c9..1fe7530779 100644 --- a/src/extensions/registries/base-registry.ts +++ b/src/extensions/registries/base-registry.ts @@ -22,7 +22,7 @@ // Base class for extensions-api registries import { action, observable, makeObservable } from "mobx"; import { Singleton } from "../../common/utils"; -import { LensExtension } from "../lens-extension"; +import type { LensExtension } from "../lens-extension"; export class BaseRegistry extends Singleton { private items = observable.map([], { deep: false }); diff --git a/src/extensions/registries/index.ts b/src/extensions/registries/index.ts index 4dd64a9c82..76f6c05d11 100644 --- a/src/extensions/registries/index.ts +++ b/src/extensions/registries/index.ts @@ -30,9 +30,6 @@ export * from "./kube-object-menu-registry"; export * from "./kube-object-status-registry"; export * from "./command-registry"; export * from "./entity-setting-registry"; -export * from "./welcome-menu-registry"; -export * from "./welcome-banner-registry"; export * from "./catalog-entity-detail-registry"; export * from "./workloads-overview-detail-registry"; -export * from "./topbar-registry"; export * from "./protocol-handler"; diff --git a/src/extensions/renderer-extensions.injectable.ts b/src/extensions/renderer-extensions.injectable.ts new file mode 100644 index 0000000000..dddcf11e80 --- /dev/null +++ b/src/extensions/renderer-extensions.injectable.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import type { IComputedValue } from "mobx"; +import extensionsInjectable from "./extensions.injectable"; +import type { LensRendererExtension } from "./lens-renderer-extension"; + +const rendererExtensionsInjectable = getInjectable({ + lifecycle: lifecycleEnum.singleton, + + instantiate: (di) => + di.inject(extensionsInjectable) as IComputedValue, +}); + +export default rendererExtensionsInjectable; diff --git a/src/main/app-updater.ts b/src/main/app-updater.ts index 3c358acde6..fb4300b266 100644 --- a/src/main/app-updater.ts +++ b/src/main/app-updater.ts @@ -25,10 +25,9 @@ import { isLinux, isMac, isPublishConfigured, isTestEnv } from "../common/vars"; import { delay } from "../common/utils"; import { areArgsUpdateAvailableToBackchannel, AutoUpdateChecking, AutoUpdateLogPrefix, AutoUpdateNoUpdateAvailable, broadcastMessage, onceCorrect, UpdateAvailableChannel, UpdateAvailableToBackchannel } from "../common/ipc"; import { once } from "lodash"; -import { ipcMain } from "electron"; +import { ipcMain, autoUpdater as electronAutoUpdater } from "electron"; import { nextUpdateChannel } from "./utils/update-channel"; import { UserStore } from "../common/user-store"; -import { autoUpdater as electronAutoUpdater } from "electron"; let installVersion: null | string = null; diff --git a/src/main/helm/helm-release-manager.ts b/src/main/helm/helm-release-manager.ts index 5a5f1e6ef2..d7ce9db5c1 100644 --- a/src/main/helm/helm-release-manager.ts +++ b/src/main/helm/helm-release-manager.ts @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import * as tempy from "tempy"; +import tempy from "tempy"; import fse from "fs-extra"; import * as yaml from "js-yaml"; import { promiseExecFile } from "../../common/utils/promise-exec"; @@ -43,6 +43,7 @@ async function execHelm(args: string[], options?: BaseEncodingOptions & ExecFile export async function listReleases(pathToKubeconfig: string, namespace?: string): Promise[]> { const args = [ "ls", + "--all", "--output", "json", ]; diff --git a/src/main/index.ts b/src/main/index.ts index 1f7b26e773..f40b92aa21 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -60,7 +60,7 @@ import { SentryInit } from "../common/sentry"; import { ensureDir } from "fs-extra"; import { Router } from "./router"; import { initMenu } from "./menu/menu"; -import { initTray } from "./tray"; +import { initTray } from "./tray/tray"; import { kubeApiRequest, shellApiRequest, ShellRequestAuthenticator } from "./proxy-functions"; import { AppPaths } from "../common/app-paths"; import { ShellSession } from "./shell-session/shell-session"; @@ -68,6 +68,7 @@ import { getDi } from "./getDi"; import electronMenuItemsInjectable from "./menu/electron-menu-items.injectable"; import extensionLoaderInjectable from "../extensions/extension-loader/extension-loader.injectable"; import lensProtocolRouterMainInjectable from "./protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable"; +import trayMenuItemsInjectable from "./tray/tray-menu-items.injectable"; const di = getDi(); @@ -104,6 +105,7 @@ mangleProxyEnv(); logger.debug("[APP-MAIN] initializing ipc main handlers"); const menuItems = di.inject(electronMenuItemsInjectable); +const trayMenuItems = di.inject(trayMenuItemsInjectable); initializers.initIpcMainHandlers(menuItems); @@ -244,7 +246,7 @@ app.on("ready", async () => { onQuitCleanup.push( initMenu(windowManager, menuItems), - initTray(windowManager), + initTray(windowManager, trayMenuItems), () => ShellSession.cleanup(), ); diff --git a/src/main/menu/electron-menu-items.injectable.ts b/src/main/menu/electron-menu-items.injectable.ts index 4f9d43d1fe..d0e5a650cf 100644 --- a/src/main/menu/electron-menu-items.injectable.ts +++ b/src/main/menu/electron-menu-items.injectable.ts @@ -18,24 +18,19 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { Injectable, lifecycleEnum } from "@ogre-tools/injectable"; -import { computed, IComputedValue } from "mobx"; -import type { LensMainExtension } from "../../extensions/lens-main-extension"; -import extensionsInjectable from "../../extensions/extensions.injectable"; -import type { MenuRegistration } from "./menu-registration"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import mainExtensionsInjectable from "../../extensions/main-extensions.injectable"; -const electronMenuItemsInjectable: Injectable< - IComputedValue, - { extensions: IComputedValue } -> = { +const electronMenuItemsInjectable = getInjectable({ lifecycle: lifecycleEnum.singleton, - getDependencies: di => ({ - extensions: di.inject(extensionsInjectable), - }), + instantiate: (di) => { + const extensions = di.inject(mainExtensionsInjectable); - instantiate: ({ extensions }) => - computed(() => extensions.get().flatMap(extension => extension.appMenus)), -}; + return computed(() => + extensions.get().flatMap((extension) => extension.appMenus)); + }, +}); export default electronMenuItemsInjectable; diff --git a/src/main/menu/electron-menu-items.test.ts b/src/main/menu/electron-menu-items.test.ts index e3062f2fb9..edd885d1c6 100644 --- a/src/main/menu/electron-menu-items.test.ts +++ b/src/main/menu/electron-menu-items.test.ts @@ -24,8 +24,8 @@ import electronMenuItemsInjectable from "./electron-menu-items.injectable"; import type { IComputedValue } from "mobx"; import { computed, ObservableMap, runInAction } from "mobx"; import type { MenuRegistration } from "./menu-registration"; -import extensionsInjectable from "../../extensions/extensions.injectable"; import { getDiForUnitTesting } from "../getDiForUnitTesting"; +import mainExtensionsInjectable from "../../extensions/main-extensions.injectable"; describe("electron-menu-items", () => { let di: ConfigurableDependencyInjectionContainer; @@ -38,8 +38,8 @@ describe("electron-menu-items", () => { extensionsStub = new ObservableMap(); di.override( - extensionsInjectable, - computed(() => [...extensionsStub.values()]), + mainExtensionsInjectable, + () => computed(() => [...extensionsStub.values()]), ); electronMenuItems = di.inject(electronMenuItemsInjectable); diff --git a/src/main/prometheus/operator.ts b/src/main/prometheus/operator.ts index 83638e4ec1..140b432baf 100644 --- a/src/main/prometheus/operator.ts +++ b/src/main/prometheus/operator.ts @@ -38,7 +38,7 @@ export class PrometheusOperator extends PrometheusProvider { case "cluster": switch (queryName) { case "memoryUsage": - return `sum(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes))`.replace(/_bytes/g, `_bytes{node=~"${opts.nodes}"}`); + return `sum(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes))`.replace(/_bytes/g, `_bytes * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}`); case "workloadMemoryUsage": return `sum(container_memory_working_set_bytes{container!="", instance=~"${opts.nodes}"}) by (component)`; case "memoryRequests": @@ -50,7 +50,7 @@ export class PrometheusOperator extends PrometheusProvider { case "memoryAllocatableCapacity": return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="memory"})`; case "cpuUsage": - return `sum(rate(node_cpu_seconds_total{node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`; + return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])* on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`; case "cpuRequests": return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"})`; case "cpuLimits": @@ -66,31 +66,31 @@ export class PrometheusOperator extends PrometheusProvider { case "podAllocatableCapacity": return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="pods"})`; case "fsSize": - return `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`; + return `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`; case "fsUsage": - return `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`; + return `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"} - node_filesystem_avail_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`; } break; case "nodes": switch (queryName) { case "memoryUsage": - return `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (node)`; + return `sum((node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) * on (pod, namespace) group_left(node) kube_pod_info) by (node)`; case "workloadMemoryUsage": - return `sum(container_memory_working_set_bytes{container!=""}) by (node)`; + return `sum(container_memory_working_set_bytes{container!="POD", container!=""}) by (node)`; case "memoryCapacity": return `sum(kube_node_status_capacity{resource="memory"}) by (node)`; case "memoryAllocatableCapacity": return `sum(kube_node_status_allocatable{resource="memory"}) by (node)`; case "cpuUsage": - return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(node)`; + return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}]) * on (pod, namespace) group_left(node) kube_pod_info) by (node)`; case "cpuCapacity": return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`; case "cpuAllocatableCapacity": return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`; case "fsSize": - return `sum(node_filesystem_size_bytes{mountpoint="/"}) by (node)`; + return `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info) by (node)`; case "fsUsage": - return `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (node)`; + return `sum((node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) * on (pod, namespace) group_left(node) kube_pod_info) by (node)`; } break; case "pods": diff --git a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts index 25b4ac3705..9f26ab6a4c 100644 --- a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts +++ b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts @@ -18,23 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable"; -import type { Dependencies } from "./lens-protocol-router-main"; import { LensProtocolRouterMain } from "./lens-protocol-router-main"; -const lensProtocolRouterMainInjectable: Injectable< - LensProtocolRouterMain, - Dependencies -> = { - getDependencies: di => ({ - extensionLoader: di.inject(extensionLoaderInjectable), - }), - - instantiate: dependencies => new LensProtocolRouterMain(dependencies), +const lensProtocolRouterMainInjectable = getInjectable({ + instantiate: (di) => + new LensProtocolRouterMain({ + extensionLoader: di.inject(extensionLoaderInjectable), + }), lifecycle: lifecycleEnum.singleton, -}; +}); export default lensProtocolRouterMainInjectable; diff --git a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts index 03d3df9d89..cae62989f4 100644 --- a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts +++ b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts @@ -51,7 +51,7 @@ function checkHost(url: URLParse): boolean { } } -export interface Dependencies { +interface Dependencies { extensionLoader: ExtensionLoader } diff --git a/src/main/resource-applier.ts b/src/main/resource-applier.ts index 7be1eec94d..61ca1dc1d0 100644 --- a/src/main/resource-applier.ts +++ b/src/main/resource-applier.ts @@ -25,7 +25,7 @@ import { exec } from "child_process"; import fs from "fs-extra"; import * as yaml from "js-yaml"; import path from "path"; -import * as tempy from "tempy"; +import tempy from "tempy"; import logger from "./logger"; import { appEventBus } from "../common/event-bus"; import { cloneJsonObject } from "../common/utils"; diff --git a/src/main/tray/tray-menu-items.injectable.ts b/src/main/tray/tray-menu-items.injectable.ts new file mode 100644 index 0000000000..8a31cc6af5 --- /dev/null +++ b/src/main/tray/tray-menu-items.injectable.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import mainExtensionsInjectable from "../../extensions/main-extensions.injectable"; + +const trayItemsInjectable = getInjectable({ + lifecycle: lifecycleEnum.singleton, + + instantiate: (di) => { + const extensions = di.inject(mainExtensionsInjectable); + + return computed(() => + extensions.get().flatMap(extension => extension.trayMenus)); + }, +}); + +export default trayItemsInjectable; diff --git a/src/main/tray/tray-menu-items.test.ts b/src/main/tray/tray-menu-items.test.ts new file mode 100644 index 0000000000..b46bce3671 --- /dev/null +++ b/src/main/tray/tray-menu-items.test.ts @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable"; +import { LensMainExtension } from "../../extensions/lens-main-extension"; +import trayItemsInjectable from "./tray-menu-items.injectable"; +import type { IComputedValue } from "mobx"; +import { computed, ObservableMap, runInAction } from "mobx"; +import { getDiForUnitTesting } from "../getDiForUnitTesting"; +import mainExtensionsInjectable from "../../extensions/main-extensions.injectable"; +import type { TrayMenuRegistration } from "./tray-menu-registration"; + +describe("tray-menu-items", () => { + let di: ConfigurableDependencyInjectionContainer; + let trayMenuItems: IComputedValue; + let extensionsStub: ObservableMap; + + beforeEach(() => { + di = getDiForUnitTesting(); + + extensionsStub = new ObservableMap(); + + di.override( + mainExtensionsInjectable, + () => computed(() => [...extensionsStub.values()]), + ); + + trayMenuItems = di.inject(trayItemsInjectable); + }); + + it("does not have any items yet", () => { + expect(trayMenuItems.get()).toHaveLength(0); + }); + + describe("when extension is enabled", () => { + beforeEach(() => { + const someExtension = new SomeTestExtension({ + id: "some-extension-id", + trayMenus: [{ label: "tray-menu-from-some-extension" }], + }); + + runInAction(() => { + extensionsStub.set("some-extension-id", someExtension); + }); + }); + + it("has tray menu items", () => { + expect(trayMenuItems.get()).toEqual([ + { + label: "tray-menu-from-some-extension", + }, + ]); + }); + + it("when disabling extension, does not have tray menu items", () => { + runInAction(() => { + extensionsStub.delete("some-extension-id"); + }); + + expect(trayMenuItems.get()).toHaveLength(0); + }); + + describe("when other extension is enabled", () => { + beforeEach(() => { + const someOtherExtension = new SomeTestExtension({ + id: "some-extension-id", + trayMenus: [{ label: "some-label-from-second-extension" }], + }); + + runInAction(() => { + extensionsStub.set("some-other-extension-id", someOtherExtension); + }); + }); + + it("has tray menu items for both extensions", () => { + expect(trayMenuItems.get()).toEqual([ + { + label: "tray-menu-from-some-extension", + }, + + { + label: "some-label-from-second-extension", + }, + ]); + }); + + it("when extension is disabled, still returns tray menu items for extensions that are enabled", () => { + runInAction(() => { + extensionsStub.delete("some-other-extension-id"); + }); + + expect(trayMenuItems.get()).toEqual([ + { + label: "tray-menu-from-some-extension", + }, + ]); + }); + }); + }); +}); + +class SomeTestExtension extends LensMainExtension { + constructor({ id, trayMenus }: { + id: string; + trayMenus: TrayMenuRegistration[]; + }) { + super({ + id, + absolutePath: "irrelevant", + isBundled: false, + isCompatible: false, + isEnabled: false, + manifest: { name: id, version: "some-version" }, + manifestPath: "irrelevant", + }); + + this.trayMenus = trayMenus; + } +} diff --git a/src/main/tray/tray-menu-registration.d.ts b/src/main/tray/tray-menu-registration.d.ts new file mode 100644 index 0000000000..0cf7fe611b --- /dev/null +++ b/src/main/tray/tray-menu-registration.d.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +export interface TrayMenuRegistration { + label?: string; + click?: (menuItem: TrayMenuRegistration) => void; + id?: string; + type?: "normal" | "separator" | "submenu" + toolTip?: string; + enabled?: boolean; + submenu?: TrayMenuRegistration[] +} diff --git a/src/main/tray.ts b/src/main/tray/tray.ts similarity index 70% rename from src/main/tray.ts rename to src/main/tray/tray.ts index 850d19fe21..ac3e140e3b 100644 --- a/src/main/tray.ts +++ b/src/main/tray/tray.ts @@ -20,16 +20,18 @@ */ import path from "path"; -import packageInfo from "../../package.json"; +import packageInfo from "../../../package.json"; import { Menu, Tray } from "electron"; -import { autorun } from "mobx"; -import { showAbout } from "./menu/menu"; -import { checkForUpdates, isAutoUpdateEnabled } from "./app-updater"; -import type { WindowManager } from "./window-manager"; -import logger from "./logger"; -import { isDevelopment, isWindows, productName } from "../common/vars"; -import { exitApp } from "./exit-app"; -import { preferencesURL } from "../common/routes"; +import { autorun, IComputedValue } from "mobx"; +import { showAbout } from "../menu/menu"; +import { checkForUpdates, isAutoUpdateEnabled } from "../app-updater"; +import type { WindowManager } from "../window-manager"; +import logger from "../logger"; +import { isDevelopment, isWindows, productName } from "../../common/vars"; +import { exitApp } from "../exit-app"; +import { preferencesURL } from "../../common/routes"; +import { toJS } from "../../common/utils"; +import type { TrayMenuRegistration } from "./tray-menu-registration"; const TRAY_LOG_PREFIX = "[TRAY]"; @@ -44,7 +46,10 @@ export function getTrayIcon(): string { ); } -export function initTray(windowManager: WindowManager) { +export function initTray( + windowManager: WindowManager, + trayMenuItems: IComputedValue, +) { const icon = getTrayIcon(); tray = new Tray(icon); @@ -62,7 +67,7 @@ export function initTray(windowManager: WindowManager) { const disposers = [ autorun(() => { try { - const menu = createTrayMenu(windowManager); + const menu = createTrayMenu(windowManager, toJS(trayMenuItems.get())); tray.setContextMenu(menu); } catch (error) { @@ -78,8 +83,21 @@ export function initTray(windowManager: WindowManager) { }; } -function createTrayMenu(windowManager: WindowManager): Menu { - const template: Electron.MenuItemConstructorOptions[] = [ +function getMenuItemConstructorOptions(trayItem: TrayMenuRegistration): Electron.MenuItemConstructorOptions { + return { + ...trayItem, + submenu: trayItem.submenu ? trayItem.submenu.map(getMenuItemConstructorOptions) : undefined, + click: trayItem.click ? () => { + trayItem.click(trayItem); + } : undefined, + }; +} + +function createTrayMenu( + windowManager: WindowManager, + extensionTrayItems: TrayMenuRegistration[], +): Menu { + let template: Electron.MenuItemConstructorOptions[] = [ { label: `Open ${productName}`, click() { @@ -108,6 +126,8 @@ function createTrayMenu(windowManager: WindowManager): Menu { }); } + template = template.concat(extensionTrayItems.map(getMenuItemConstructorOptions)); + return Menu.buildFromTemplate(template.concat([ { label: `About ${productName}`, diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index 48704ba074..3987e29824 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -24,7 +24,7 @@ import { makeObservable, observable } from "mobx"; import { app, BrowserWindow, dialog, ipcMain, shell, webContents } from "electron"; import windowStateKeeper from "electron-window-state"; import { appEventBus } from "../common/event-bus"; -import { ipcMainOn } from "../common/ipc"; +import { BundledExtensionsLoaded, ipcMainOn } from "../common/ipc"; import { delay, iter, Singleton } from "../common/utils"; import { ClusterFrameInfo, clusterFrameMap } from "../common/cluster-frames"; import { IpcRendererNavigationEvents } from "../renderer/navigation/events"; @@ -181,7 +181,7 @@ export class WindowManager extends Singleton { if (!this.mainWindow) { viewHasLoaded = new Promise(resolve => { - ipcMain.once(IpcRendererNavigationEvents.LOADED, () => resolve()); + ipcMain.once(BundledExtensionsLoaded, () => resolve()); }); await this.initMainWindow(showSplash); } diff --git a/src/renderer/api/__tests__/catalog-entity-registry.test.ts b/src/renderer/api/__tests__/catalog-entity-registry.test.ts index f4261df995..26a4aaeb03 100644 --- a/src/renderer/api/__tests__/catalog-entity-registry.test.ts +++ b/src/renderer/api/__tests__/catalog-entity-registry.test.ts @@ -20,7 +20,6 @@ */ import { CatalogEntityRegistry } from "../catalog-entity-registry"; -import "../../../common/catalog-entities"; import { catalogCategoryRegistry } from "../../../common/catalog/catalog-category-registry"; import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "../catalog-entity"; import { KubernetesCluster, WebLink } from "../../../common/catalog-entities"; diff --git a/src/renderer/api/catalog-entity-registry.ts b/src/renderer/api/catalog-entity-registry.ts index 4885c8f265..735e66a7a3 100644 --- a/src/renderer/api/catalog-entity-registry.ts +++ b/src/renderer/api/catalog-entity-registry.ts @@ -28,14 +28,21 @@ import { ClusterStore } from "../../common/cluster-store"; import { Disposer, iter } from "../utils"; import { once } from "lodash"; import logger from "../../common/logger"; -import { catalogEntityRunContext } from "./catalog-entity"; import { CatalogRunEvent } from "../../common/catalog/catalog-run-event"; import { ipcRenderer } from "electron"; import { CatalogIpcEvents } from "../../common/ipc/catalog"; +import { navigate } from "../navigation"; export type EntityFilter = (entity: CatalogEntity) => any; export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promise; +export const catalogEntityRunContext = { + navigate: (url: string) => navigate(url), + setCommandPaletteContext: (entity?: CatalogEntity) => { + catalogEntityRegistry.activeEntity = entity; + }, +}; + export class CatalogEntityRegistry { @observable protected activeEntityId: string | undefined = undefined; protected _entities = observable.map([], { deep: true }); diff --git a/src/renderer/api/catalog-entity.ts b/src/renderer/api/catalog-entity.ts index 1d07291659..debb235c8f 100644 --- a/src/renderer/api/catalog-entity.ts +++ b/src/renderer/api/catalog-entity.ts @@ -19,10 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { navigate } from "../navigation"; -import type { CatalogEntity } from "../../common/catalog"; -import { catalogEntityRegistry } from "./catalog-entity-registry"; - +export { catalogEntityRunContext } from "./catalog-entity-registry"; export { CatalogCategory, CatalogEntity } from "../../common/catalog"; export type { CatalogEntityData, @@ -33,10 +30,3 @@ export type { CatalogEntityContextMenu, CatalogEntityContextMenuContext, } from "../../common/catalog"; - -export const catalogEntityRunContext = { - navigate: (url: string) => navigate(url), - setCommandPaletteContext: (entity?: CatalogEntity) => { - catalogEntityRegistry.activeEntity = entity; - }, -}; diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 1de360d18a..6e9e9fb471 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -114,9 +114,6 @@ export async function bootstrap(comp: () => Promise, di: Dependenc logger.info(`${logPrefix} initializing KubeObjectDetailRegistry`); initializers.initKubeObjectDetailRegistry(); - logger.info(`${logPrefix} initializing WelcomeMenuRegistry`); - initializers.initWelcomeMenuRegistry(); - logger.info(`${logPrefix} initializing WorkloadsOverviewDetailRegistry`); initializers.initWorkloadsOverviewDetailRegistry(); diff --git a/src/renderer/components/+add-cluster/add-cluster.module.css b/src/renderer/components/+add-cluster/add-cluster.module.scss similarity index 98% rename from src/renderer/components/+add-cluster/add-cluster.module.css rename to src/renderer/components/+add-cluster/add-cluster.module.scss index f80ed3bdea..e4dba3519b 100644 --- a/src/renderer/components/+add-cluster/add-cluster.module.css +++ b/src/renderer/components/+add-cluster/add-cluster.module.scss @@ -38,7 +38,7 @@ border-radius: var(--border-radius); } -:global(.theme-light) { +@include theme-light { .editor { border-color: var(--borderFaintColor); } diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index f6b683f654..40257f6518 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./add-cluster.module.css"; +import styles from "./add-cluster.module.scss"; import type { KubeConfig } from "@kubernetes/client-node"; import fse from "fs-extra"; diff --git a/src/renderer/components/+catalog/catalog-entity-details.module.css b/src/renderer/components/+catalog/catalog-entity-details.module.scss similarity index 100% rename from src/renderer/components/+catalog/catalog-entity-details.module.css rename to src/renderer/components/+catalog/catalog-entity-details.module.scss diff --git a/src/renderer/components/+catalog/catalog-entity-details.tsx b/src/renderer/components/+catalog/catalog-entity-details.tsx index b3c9fc73c2..1d678a558c 100644 --- a/src/renderer/components/+catalog/catalog-entity-details.tsx +++ b/src/renderer/components/+catalog/catalog-entity-details.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./catalog-entity-details.module.css"; +import styles from "./catalog-entity-details.module.scss"; import React, { Component } from "react"; import { observer } from "mobx-react"; import { Drawer, DrawerItem } from "../drawer"; @@ -27,14 +27,15 @@ import type { CatalogCategory, CatalogEntity } from "../../../common/catalog"; import { Icon } from "../icon"; import { CatalogEntityDrawerMenu } from "./catalog-entity-drawer-menu"; import { CatalogEntityDetailRegistry } from "../../../extensions/registries"; -import type { CatalogEntityItem } from "./catalog-entity-item"; import { isDevelopment } from "../../../common/vars"; import { cssNames } from "../../utils"; import { Avatar } from "../avatar"; +import { getLabelBadges } from "./helpers"; interface Props { - item: CatalogEntityItem | null | undefined; + entity: T; hideDetails(): void; + onRun: () => void; } @observer @@ -47,32 +48,30 @@ export class CatalogEntityDetails extends Component) { - const detailItems = CatalogEntityDetailRegistry.getInstance().getItemsForKind(item.kind, item.apiVersion); - const details = detailItems.map(({ components }, index) => { - return ; - }); - - const showDetails = detailItems.find((item) => item.priority > 999) === undefined; + renderContent(entity: T) { + const { onRun, hideDetails } = this.props; + const detailItems = CatalogEntityDetailRegistry.getInstance().getItemsForKind(entity.kind, entity.apiVersion); + const details = detailItems.map(({ components }, index) => ); + const showDefaultDetails = detailItems.find((item) => item.priority > 999) === undefined; return ( <> - {showDetails && ( + {showDefaultDetails && (

item.onRun()} + background={entity.spec.icon?.background} + onClick={onRun} className={styles.avatar} > - {item.entity.spec.icon?.material && } + {entity.spec.icon?.material && } - {item?.enabled && ( + {entity.isEnabled() && (
Click to open
@@ -80,23 +79,23 @@ export class CatalogEntityDetails extends Component
- {item.name} + {entity.getName()} - {item.kind} + {entity.kind} - {item.source} + {entity.getSource()} - {item.phase} + {entity.status.phase} - {...item.getLabelBadges(this.props.hideDetails)} + {getLabelBadges(entity, hideDetails)} {isDevelopment && ( - {item.getId()} + {entity.getId()} )}
@@ -110,19 +109,18 @@ export class CatalogEntityDetails extends Component} + title={`${entity.kind}: ${entity.getName()}`} + toolbar={} onClose={hideDetails} > - {item && this.renderContent(item)} + {this.renderContent(entity)} ); } diff --git a/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx b/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx index 525afeea68..f841e4d433 100644 --- a/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx +++ b/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx @@ -29,11 +29,10 @@ import { navigate } from "../../navigation"; import { MenuItem } from "../menu"; import { ConfirmDialog } from "../confirm-dialog"; import { Icon } from "../icon"; -import type { CatalogEntityItem } from "./catalog-entity-item"; import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item"; export interface CatalogEntityDrawerMenuProps extends MenuActionsProps { - item: CatalogEntityItem | null | undefined; + entity: T; } @observer @@ -50,7 +49,7 @@ export class CatalogEntityDrawerMenu extends React.Comp menuItems: [], navigate: (url: string) => navigate(url), }; - this.props.item?.onContextMenuOpen(this.contextMenu); + this.props.entity?.onContextMenuOpen(this.contextMenu); } onMenuItemClick(menuItem: CatalogEntityContextMenu) { @@ -108,9 +107,9 @@ export class CatalogEntityDrawerMenu extends React.Comp } render() { - const { className, item: entity, ...menuProps } = this.props; + const { className, entity, ...menuProps } = this.props; - if (!this.contextMenu || !entity.enabled) { + if (!this.contextMenu || !entity.isEnabled()) { return null; } @@ -120,7 +119,7 @@ export class CatalogEntityDrawerMenu extends React.Comp toolbar {...menuProps} > - {this.getMenuItems(entity.entity)} + {this.getMenuItems(entity)} ); } diff --git a/src/renderer/components/+catalog/catalog-entity-item.tsx b/src/renderer/components/+catalog/catalog-entity-item.tsx index 9518131375..01fc46b95d 100644 --- a/src/renderer/components/+catalog/catalog-entity-item.tsx +++ b/src/renderer/components/+catalog/catalog-entity-item.tsx @@ -18,7 +18,7 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./catalog.module.css"; +import styles from "./catalog.module.scss"; import React from "react"; import { action, computed } from "mobx"; import { CatalogEntity } from "../../api/catalog-entity"; diff --git a/src/renderer/components/+catalog/catalog-entity.store.tsx b/src/renderer/components/+catalog/catalog-entity.store.tsx index 225767d78f..8b7743f470 100644 --- a/src/renderer/components/+catalog/catalog-entity.store.tsx +++ b/src/renderer/components/+catalog/catalog-entity.store.tsx @@ -25,9 +25,8 @@ import type { CatalogEntity } from "../../api/catalog-entity"; import { ItemStore } from "../../../common/item.store"; import { CatalogCategory, catalogCategoryRegistry } from "../../../common/catalog"; import { autoBind, disposer } from "../../../common/utils"; -import { CatalogEntityItem } from "./catalog-entity-item"; -export class CatalogEntityStore extends ItemStore> { +export class CatalogEntityStore extends ItemStore { constructor(private registry: CatalogEntityRegistry = catalogEntityRegistry) { super(); makeObservable(this); @@ -39,10 +38,10 @@ export class CatalogEntityStore extends ItemStore new CatalogEntityItem(entity, this.registry)); + return this.registry.filteredItems; } - return this.registry.getItemsForCategory(this.activeCategory, { filtered: true }).map(entity => new CatalogEntityItem(entity, this.registry)); + return this.registry.getItemsForCategory(this.activeCategory, { filtered: true }); } @computed get selectedItem() { @@ -68,4 +67,8 @@ export class CatalogEntityStore extends ItemStore this.entities, undefined, true); } + + onRun(entity: CatalogEntity): void { + this.registry.onRun(entity); + } } diff --git a/src/renderer/components/+catalog/catalog-menu.module.css b/src/renderer/components/+catalog/catalog-menu.module.scss similarity index 100% rename from src/renderer/components/+catalog/catalog-menu.module.css rename to src/renderer/components/+catalog/catalog-menu.module.scss diff --git a/src/renderer/components/+catalog/catalog-menu.tsx b/src/renderer/components/+catalog/catalog-menu.tsx index b2d013dfcd..c80fc1901c 100644 --- a/src/renderer/components/+catalog/catalog-menu.tsx +++ b/src/renderer/components/+catalog/catalog-menu.tsx @@ -19,8 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import treeStyles from "./catalog-tree.module.css"; -import styles from "./catalog-menu.module.css"; +import treeStyles from "./catalog-tree.module.scss"; +import styles from "./catalog-menu.module.scss"; import React from "react"; import { TreeItem, TreeItemProps, TreeView } from "@material-ui/lab"; diff --git a/src/renderer/components/+catalog/catalog-tree.module.css b/src/renderer/components/+catalog/catalog-tree.module.scss similarity index 100% rename from src/renderer/components/+catalog/catalog-tree.module.css rename to src/renderer/components/+catalog/catalog-tree.module.scss diff --git a/src/renderer/components/+catalog/catalog.module.css b/src/renderer/components/+catalog/catalog.module.scss similarity index 100% rename from src/renderer/components/+catalog/catalog.module.css rename to src/renderer/components/+catalog/catalog.module.scss diff --git a/src/renderer/components/+catalog/catalog.test.tsx b/src/renderer/components/+catalog/catalog.test.tsx index 7d3aaa23e4..0f80aa475b 100644 --- a/src/renderer/components/+catalog/catalog.test.tsx +++ b/src/renderer/components/+catalog/catalog.test.tsx @@ -29,7 +29,6 @@ import { kubernetesClusterCategory } from "../../../common/catalog-entities/kube import { catalogCategoryRegistry, CatalogCategoryRegistry, CatalogEntity, CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog"; import { CatalogEntityRegistry } from "../../../renderer/api/catalog-entity-registry"; import { CatalogEntityDetailRegistry } from "../../../extensions/registries"; -import { CatalogEntityItem } from "./catalog-entity-item"; import { CatalogEntityStore } from "./catalog-entity.store"; import { AppPaths } from "../../../common/app-paths"; @@ -130,7 +129,7 @@ describe("", () => { const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const onRun = jest.fn(); - const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); + const catalogEntityItem = createMockCatalogEntity(onRun); // mock as if there is a selected item > the detail panel opens jest @@ -166,7 +165,7 @@ describe("", () => { const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const onRun = jest.fn(); - const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); + const catalogEntityItem = createMockCatalogEntity(onRun); // mock as if there is a selected item > the detail panel opens jest @@ -200,7 +199,7 @@ describe("", () => { const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const onRun = jest.fn(); - const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); + const catalogEntityItem = createMockCatalogEntity(onRun); // mock as if there is a selected item > the detail panel opens jest @@ -235,7 +234,7 @@ describe("", () => { const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const onRun = jest.fn(() => done()); - const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); + const catalogEntityItem = createMockCatalogEntity(onRun); // mock as if there is a selected item > the detail panel opens jest @@ -265,7 +264,7 @@ describe("", () => { const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const onRun = jest.fn(); - const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); + const catalogEntityItem = createMockCatalogEntity(onRun); // mock as if there is a selected item > the detail panel opens jest @@ -302,7 +301,7 @@ describe("", () => { const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const onRun = jest.fn(); - const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); + const catalogEntityItem = createMockCatalogEntity(onRun); // mock as if there is a selected item > the detail panel opens jest diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index bb39419ed3..595ba6a81c 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -19,14 +19,13 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./catalog.module.css"; +import styles from "./catalog.module.scss"; import React from "react"; import { disposeOnUnmount, observer } from "mobx-react"; import { ItemListLayout } from "../item-object-list"; import { action, makeObservable, observable, reaction, runInAction, when } from "mobx"; import { CatalogEntityStore } from "./catalog-entity.store"; -import type { CatalogEntityItem } from "./catalog-entity-item"; import { navigate } from "../../navigation"; import { MenuItem, MenuActions } from "../menu"; import type { CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity"; @@ -45,6 +44,8 @@ import { RenderDelay } from "../render-delay/render-delay"; import { Icon } from "../icon"; import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item"; import { Avatar } from "../avatar"; +import { KubeObject } from "../../../common/k8s-api/kube-object"; +import { getLabelBadges } from "./helpers"; export const previousActiveTab = createStorage("catalog-previous-active-tab", browseCatalogTab); @@ -125,19 +126,19 @@ export class Catalog extends React.Component { })); } - addToHotbar(item: CatalogEntityItem): void { - HotbarStore.getInstance().addToHotbar(item.entity); + addToHotbar(entity: CatalogEntity): void { + HotbarStore.getInstance().addToHotbar(entity); } - removeFromHotbar(item: CatalogEntityItem): void { - HotbarStore.getInstance().removeFromHotbar(item.getId()); + removeFromHotbar(entity: CatalogEntity): void { + HotbarStore.getInstance().removeFromHotbar(entity.getId()); } - onDetails = (item: CatalogEntityItem) => { + onDetails = (entity: CatalogEntity) => { if (this.catalogEntityStore.selectedItemId) { this.catalogEntityStore.selectedItemId = null; } else { - item.onRun(); + this.catalogEntityStore.onRun(entity); } }; @@ -179,16 +180,16 @@ export class Catalog extends React.Component { ); } - renderItemMenu = (item: CatalogEntityItem) => { + renderItemMenu = (entity: CatalogEntity) => { const onOpen = () => { this.contextMenu.menuItems = []; - item.onContextMenuOpen(this.contextMenu); + entity.onContextMenuOpen(this.contextMenu); }; return ( - this.catalogEntityStore.selectedItemId = item.getId()}> + this.catalogEntityStore.selectedItemId = entity.getId()}> View Details { @@ -200,7 +201,7 @@ export class Catalog extends React.Component { } @@ -208,29 +209,29 @@ export class Catalog extends React.Component { ); }; - renderName(item: CatalogEntityItem) { - const isItemInHotbar = HotbarStore.getInstance().isAddedToActive(item.entity); + renderName(entity: CatalogEntity) { + const isItemInHotbar = HotbarStore.getInstance().isAddedToActive(entity); return ( <> - {item.entity.spec.icon?.material && } + {entity.spec.icon?.material && } - {item.name} + {entity.getName()} isItemInHotbar ? this.removeFromHotbar(item) : this.addToHotbar(item))} + onClick={prevDefault(() => isItemInHotbar ? this.removeFromHotbar(entity) : this.addToHotbar(entity))} /> ); @@ -253,13 +254,19 @@ export class Catalog extends React.Component { isConfigurable={true} store={this.catalogEntityStore} sortingCallbacks={{ - [sortBy.name]: item => item.name, - [sortBy.source]: item => item.source, - [sortBy.status]: item => item.phase, - [sortBy.kind]: item => item.kind, + [sortBy.name]: entity => entity.getName(), + [sortBy.source]: entity => entity.getSource(), + [sortBy.status]: entity => entity.status.phase, + [sortBy.kind]: entity => entity.kind, }} searchFilters={[ - entity => entity.searchFields, + entity => [ + entity.getName(), + entity.getId(), + entity.status.phase, + `source=${entity.getSource()}`, + ...KubeObject.stringifyLabels(entity.metadata.labels), + ], ]} renderTableHeader={[ { title: "Name", className: styles.entityName, sortBy: sortBy.name, id: "name" }, @@ -268,15 +275,15 @@ export class Catalog extends React.Component { { title: "Labels", className: `${styles.labelsCell} scrollable`, id: "labels" }, { title: "Status", className: styles.statusCell, sortBy: sortBy.status, id: "status" }, ].filter(Boolean)} - customizeTableRowProps={item => ({ - disabled: !item.enabled, + customizeTableRowProps={entity => ({ + disabled: !entity.isEnabled(), })} - renderTableContents={item => [ - this.renderName(item), - !activeCategory && item.kind, - item.source, - item.getLabelBadges(), - {item.phase}, + renderTableContents={entity => [ + this.renderName(entity), + !activeCategory && entity.kind, + entity.getSource(), + getLabelBadges(entity), + {entity.status.phase}, ].filter(Boolean)} onDetails={this.onDetails} renderItemMenu={this.renderItemMenu} @@ -289,16 +296,19 @@ export class Catalog extends React.Component { return null; } + const selectedEntity = this.catalogEntityStore.selectedItem; + return (
{this.renderList()}
{ - this.catalogEntityStore.selectedItem + selectedEntity ? this.catalogEntityStore.selectedItemId = null} + onRun={() => this.catalogEntityStore.onRun(selectedEntity)} /> : ( diff --git a/src/renderer/components/+catalog/helpers.tsx b/src/renderer/components/+catalog/helpers.tsx new file mode 100644 index 0000000000..319bab8d2a --- /dev/null +++ b/src/renderer/components/+catalog/helpers.tsx @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import styles from "./catalog.module.scss"; +import React from "react"; +import { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { CatalogEntity } from "../../api/catalog-entity"; +import { Badge } from "../badge"; +import { searchUrlParam } from "../input"; + +/** + * @param entity The entity to render badge labels for + */ +export function getLabelBadges(entity: CatalogEntity, onClick?: (evt: React.MouseEvent) => void) { + return KubeObject.stringifyLabels(entity.metadata.labels) + .map(label => ( + { + searchUrlParam.set(label); + onClick?.(event); + event.stopPropagation(); + }} + expandable={false} + /> + )); +} diff --git a/src/renderer/components/+cluster/cluster-issues.module.css b/src/renderer/components/+cluster/cluster-issues.module.scss similarity index 91% rename from src/renderer/components/+cluster/cluster-issues.module.css rename to src/renderer/components/+cluster/cluster-issues.module.scss index fc421f1401..969eb44e84 100644 --- a/src/renderer/components/+cluster/cluster-issues.module.css +++ b/src/renderer/components/+cluster/cluster-issues.module.scss @@ -60,15 +60,15 @@ flex-grow: 2; } - .noIssues { - .ok-title { - font-size: large; - color: var(--textColorAccent); - font-weight: bold; - } + .noIssues { + .Icon { + color: white; + } - .allGood { - color: white; - } - } + .title { + font-size: large; + color: var(--textColorAccent); + font-weight: bold; + } + } } diff --git a/src/renderer/components/+cluster/cluster-issues.tsx b/src/renderer/components/+cluster/cluster-issues.tsx index 6165fde8bd..9f0cff6917 100644 --- a/src/renderer/components/+cluster/cluster-issues.tsx +++ b/src/renderer/components/+cluster/cluster-issues.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./cluster-issues.module.css"; +import styles from "./cluster-issues.module.scss"; import React from "react"; import { observer } from "mobx-react"; @@ -149,9 +149,9 @@ export class ClusterIssues extends React.Component { if (!warnings.length) { return (
-
-
No issues found
- Everything is fine in the Cluster + +

No issues found

+

Everything is fine in the Cluster

); } diff --git a/src/renderer/components/+cluster/cluster-metrics.module.css b/src/renderer/components/+cluster/cluster-metrics.module.scss similarity index 100% rename from src/renderer/components/+cluster/cluster-metrics.module.css rename to src/renderer/components/+cluster/cluster-metrics.module.scss diff --git a/src/renderer/components/+cluster/cluster-metrics.tsx b/src/renderer/components/+cluster/cluster-metrics.tsx index f4cc72e8d2..919a96e774 100644 --- a/src/renderer/components/+cluster/cluster-metrics.tsx +++ b/src/renderer/components/+cluster/cluster-metrics.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./cluster-metrics.module.css"; +import styles from "./cluster-metrics.module.scss"; import React from "react"; import { observer } from "mobx-react"; diff --git a/src/renderer/components/+cluster/cluster-overview.module.css b/src/renderer/components/+cluster/cluster-overview.module.scss similarity index 100% rename from src/renderer/components/+cluster/cluster-overview.module.css rename to src/renderer/components/+cluster/cluster-overview.module.scss diff --git a/src/renderer/components/+cluster/cluster-overview.tsx b/src/renderer/components/+cluster/cluster-overview.tsx index d2e3e3ee9a..064b716e7e 100644 --- a/src/renderer/components/+cluster/cluster-overview.tsx +++ b/src/renderer/components/+cluster/cluster-overview.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./cluster-overview.module.css"; +import styles from "./cluster-overview.module.scss"; import React from "react"; import { reaction } from "mobx"; diff --git a/src/renderer/components/+cluster/cluster-pie-charts.module.css b/src/renderer/components/+cluster/cluster-pie-charts.module.scss similarity index 100% rename from src/renderer/components/+cluster/cluster-pie-charts.module.css rename to src/renderer/components/+cluster/cluster-pie-charts.module.scss diff --git a/src/renderer/components/+cluster/cluster-pie-charts.tsx b/src/renderer/components/+cluster/cluster-pie-charts.tsx index 80df2c1a9c..c88202c410 100644 --- a/src/renderer/components/+cluster/cluster-pie-charts.tsx +++ b/src/renderer/components/+cluster/cluster-pie-charts.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./cluster-pie-charts.module.css"; +import styles from "./cluster-pie-charts.module.scss"; import React from "react"; import { observer } from "mobx-react"; diff --git a/src/renderer/components/+entity-settings/entity-settings.module.css b/src/renderer/components/+entity-settings/entity-settings.module.scss similarity index 100% rename from src/renderer/components/+entity-settings/entity-settings.module.css rename to src/renderer/components/+entity-settings/entity-settings.module.scss diff --git a/src/renderer/components/+entity-settings/entity-settings.tsx b/src/renderer/components/+entity-settings/entity-settings.tsx index 65a953a655..ee543490eb 100644 --- a/src/renderer/components/+entity-settings/entity-settings.tsx +++ b/src/renderer/components/+entity-settings/entity-settings.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./entity-settings.module.css"; +import styles from "./entity-settings.module.scss"; import React from "react"; import { observable, makeObservable } from "mobx"; diff --git a/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.injectable.ts b/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.injectable.ts index 34fd491fe9..8c601230f1 100644 --- a/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.injectable.ts +++ b/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.injectable.ts @@ -18,20 +18,19 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; -import { attemptInstallByInfo, ExtensionInfo } from "./attempt-install-by-info"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { attemptInstallByInfo } from "./attempt-install-by-info"; import attemptInstallInjectable from "../attempt-install/attempt-install.injectable"; import getBaseRegistryUrlInjectable from "../get-base-registry-url/get-base-registry-url.injectable"; -const attemptInstallByInfoInjectable: Injectable<(extensionInfo: ExtensionInfo) => Promise, {}> = { - getDependencies: di => ({ - attemptInstall: di.inject(attemptInstallInjectable), - getBaseRegistryUrl: di.inject(getBaseRegistryUrlInjectable), - }), +const attemptInstallByInfoInjectable = getInjectable({ + instantiate: (di) => + attemptInstallByInfo({ + attemptInstall: di.inject(attemptInstallInjectable), + getBaseRegistryUrl: di.inject(getBaseRegistryUrlInjectable), + }), - instantiate: attemptInstallByInfo, lifecycle: lifecycleEnum.singleton, -}; +}); export default attemptInstallByInfoInjectable; diff --git a/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.tsx b/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.tsx index c898fa0cc0..076737c0fe 100644 --- a/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.tsx +++ b/src/renderer/components/+extensions/attempt-install-by-info/attempt-install-by-info.tsx @@ -35,7 +35,7 @@ export interface ExtensionInfo { requireConfirmation?: boolean; } -export interface Dependencies { +interface Dependencies { attemptInstall: (request: InstallRequest, d: ExtendableDisposer) => Promise; getBaseRegistryUrl: () => Promise; } diff --git a/src/renderer/components/+extensions/attempt-install/attempt-install.injectable.ts b/src/renderer/components/+extensions/attempt-install/attempt-install.injectable.ts index bb989df454..acf21a17e4 100644 --- a/src/renderer/components/+extensions/attempt-install/attempt-install.injectable.ts +++ b/src/renderer/components/+extensions/attempt-install/attempt-install.injectable.ts @@ -18,29 +18,21 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; - -import type { ExtendableDisposer } from "../../../../common/utils"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable"; import uninstallExtensionInjectable from "../uninstall-extension/uninstall-extension.injectable"; -import type { Dependencies } from "./attempt-install"; import { attemptInstall } from "./attempt-install"; -import type { InstallRequest } from "./install-request"; import unpackExtensionInjectable from "./unpack-extension/unpack-extension.injectable"; -const attemptInstallInjectable: Injectable< - (request: InstallRequest, d?: ExtendableDisposer) => Promise, - Dependencies -> = { - getDependencies: di => ({ - extensionLoader: di.inject(extensionLoaderInjectable), - uninstallExtension: di.inject(uninstallExtensionInjectable), - unpackExtension: di.inject(unpackExtensionInjectable), - }), +const attemptInstallInjectable = getInjectable({ + instantiate: (di) => + attemptInstall({ + extensionLoader: di.inject(extensionLoaderInjectable), + uninstallExtension: di.inject(uninstallExtensionInjectable), + unpackExtension: di.inject(unpackExtensionInjectable), + }), - instantiate: attemptInstall, lifecycle: lifecycleEnum.singleton, -}; +}); export default attemptInstallInjectable; diff --git a/src/renderer/components/+extensions/attempt-install/attempt-install.tsx b/src/renderer/components/+extensions/attempt-install/attempt-install.tsx index a5526ef220..f8aba4e5a9 100644 --- a/src/renderer/components/+extensions/attempt-install/attempt-install.tsx +++ b/src/renderer/components/+extensions/attempt-install/attempt-install.tsx @@ -41,7 +41,7 @@ import { import { getExtensionDestFolder } from "./get-extension-dest-folder/get-extension-dest-folder"; import type { InstallRequest } from "./install-request"; -export interface Dependencies { +interface Dependencies { extensionLoader: ExtensionLoader; uninstallExtension: (id: LensExtensionId) => Promise; unpackExtension: ( diff --git a/src/renderer/components/+extensions/attempt-install/unpack-extension/unpack-extension.injectable.tsx b/src/renderer/components/+extensions/attempt-install/unpack-extension/unpack-extension.injectable.tsx index 7bdb2bc64a..054adf45ad 100644 --- a/src/renderer/components/+extensions/attempt-install/unpack-extension/unpack-extension.injectable.tsx +++ b/src/renderer/components/+extensions/attempt-install/unpack-extension/unpack-extension.injectable.tsx @@ -18,26 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; -import { Dependencies, unpackExtension } from "./unpack-extension"; -import type { InstallRequestValidated } from "../create-temp-files-and-validate/create-temp-files-and-validate"; -import type { Disposer } from "../../../../../common/utils"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { unpackExtension } from "./unpack-extension"; import extensionLoaderInjectable from "../../../../../extensions/extension-loader/extension-loader.injectable"; -const unpackExtensionInjectable: Injectable< - ( - request: InstallRequestValidated, - disposeDownloading?: Disposer, - ) => Promise, - Dependencies -> = { - getDependencies: di => ({ - extensionLoader: di.inject(extensionLoaderInjectable), - }), +const unpackExtensionInjectable = getInjectable({ + instantiate: (di) => + unpackExtension({ + extensionLoader: di.inject(extensionLoaderInjectable), + }), - instantiate: unpackExtension, lifecycle: lifecycleEnum.singleton, -}; +}); export default unpackExtensionInjectable; diff --git a/src/renderer/components/+extensions/attempt-install/unpack-extension/unpack-extension.tsx b/src/renderer/components/+extensions/attempt-install/unpack-extension/unpack-extension.tsx index 54bc672356..73e9f15328 100644 --- a/src/renderer/components/+extensions/attempt-install/unpack-extension/unpack-extension.tsx +++ b/src/renderer/components/+extensions/attempt-install/unpack-extension/unpack-extension.tsx @@ -32,7 +32,7 @@ import fse from "fs-extra"; import { when } from "mobx"; import React from "react"; -export interface Dependencies { +interface Dependencies { extensionLoader: ExtensionLoader } diff --git a/src/renderer/components/+extensions/attempt-installs/attempt-installs.injectable.ts b/src/renderer/components/+extensions/attempt-installs/attempt-installs.injectable.ts index 10e3adbd27..9bc6cab88f 100644 --- a/src/renderer/components/+extensions/attempt-installs/attempt-installs.injectable.ts +++ b/src/renderer/components/+extensions/attempt-installs/attempt-installs.injectable.ts @@ -18,21 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; -import { attemptInstalls, Dependencies } from "./attempt-installs"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { attemptInstalls } from "./attempt-installs"; import attemptInstallInjectable from "../attempt-install/attempt-install.injectable"; -const attemptInstallsInjectable: Injectable< - (filePaths: string[]) => Promise, - Dependencies -> = { - getDependencies: di => ({ - attemptInstall: di.inject(attemptInstallInjectable), - }), +const attemptInstallsInjectable = getInjectable({ + instantiate: (di) => + attemptInstalls({ + attemptInstall: di.inject(attemptInstallInjectable), + }), - instantiate: attemptInstalls, lifecycle: lifecycleEnum.singleton, -}; +}); export default attemptInstallsInjectable; diff --git a/src/renderer/components/+extensions/attempt-installs/attempt-installs.ts b/src/renderer/components/+extensions/attempt-installs/attempt-installs.ts index 4c74ee10ff..c847b25c1d 100644 --- a/src/renderer/components/+extensions/attempt-installs/attempt-installs.ts +++ b/src/renderer/components/+extensions/attempt-installs/attempt-installs.ts @@ -22,7 +22,7 @@ import { readFileNotify } from "../read-file-notify/read-file-notify"; import path from "path"; import type { InstallRequest } from "../attempt-install/install-request"; -export interface Dependencies { +interface Dependencies { attemptInstall: (request: InstallRequest) => Promise; } diff --git a/src/renderer/components/+extensions/confirm-uninstall-extension/confirm-uninstall-extension.injectable.ts b/src/renderer/components/+extensions/confirm-uninstall-extension/confirm-uninstall-extension.injectable.ts index fa82c30905..baf8e5119d 100644 --- a/src/renderer/components/+extensions/confirm-uninstall-extension/confirm-uninstall-extension.injectable.ts +++ b/src/renderer/components/+extensions/confirm-uninstall-extension/confirm-uninstall-extension.injectable.ts @@ -18,25 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; -import { - confirmUninstallExtension, - Dependencies, -} from "./confirm-uninstall-extension"; -import type { InstalledExtension } from "../../../../extensions/extension-discovery"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { confirmUninstallExtension } from "./confirm-uninstall-extension"; import uninstallExtensionInjectable from "../uninstall-extension/uninstall-extension.injectable"; -const confirmUninstallExtensionInjectable: Injectable< - (extension: InstalledExtension) => Promise, - Dependencies -> = { - getDependencies: di => ({ - uninstallExtension: di.inject(uninstallExtensionInjectable), - }), +const confirmUninstallExtensionInjectable = getInjectable({ + instantiate: (di) => + confirmUninstallExtension({ + uninstallExtension: di.inject(uninstallExtensionInjectable), + }), - instantiate: confirmUninstallExtension, lifecycle: lifecycleEnum.singleton, -}; +}); export default confirmUninstallExtensionInjectable; diff --git a/src/renderer/components/+extensions/confirm-uninstall-extension/confirm-uninstall-extension.tsx b/src/renderer/components/+extensions/confirm-uninstall-extension/confirm-uninstall-extension.tsx index 3df8c9a749..fbf1670e29 100644 --- a/src/renderer/components/+extensions/confirm-uninstall-extension/confirm-uninstall-extension.tsx +++ b/src/renderer/components/+extensions/confirm-uninstall-extension/confirm-uninstall-extension.tsx @@ -24,8 +24,8 @@ import type { LensExtensionId } from "../../../../extensions/lens-extension"; import { extensionDisplayName } from "../../../../extensions/lens-extension"; import { ConfirmDialog } from "../../confirm-dialog"; -export interface Dependencies { - uninstallExtension: (id: LensExtensionId) => Promise; +interface Dependencies { + uninstallExtension: (id: LensExtensionId) => Promise; } export const confirmUninstallExtension = diff --git a/src/renderer/components/+extensions/disable-extension/disable-extension.injectable.ts b/src/renderer/components/+extensions/disable-extension/disable-extension.injectable.ts index eefc67980d..c965e91ab7 100644 --- a/src/renderer/components/+extensions/disable-extension/disable-extension.injectable.ts +++ b/src/renderer/components/+extensions/disable-extension/disable-extension.injectable.ts @@ -18,23 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable"; -import type { LensExtensionId } from "../../../../extensions/lens-extension"; -import { Dependencies, disableExtension } from "./disable-extension"; +import { disableExtension } from "./disable-extension"; -const disableExtensionInjectable: Injectable< - (id: LensExtensionId) => void, - Dependencies -> = { - getDependencies: di => ({ - extensionLoader: di.inject(extensionLoaderInjectable), - }), - - instantiate: disableExtension, +const disableExtensionInjectable = getInjectable({ + instantiate: (di) => + disableExtension({ + extensionLoader: di.inject(extensionLoaderInjectable), + }), lifecycle: lifecycleEnum.singleton, -}; +}); export default disableExtensionInjectable; diff --git a/src/renderer/components/+extensions/disable-extension/disable-extension.ts b/src/renderer/components/+extensions/disable-extension/disable-extension.ts index d01e85ecbc..af7cf369a2 100644 --- a/src/renderer/components/+extensions/disable-extension/disable-extension.ts +++ b/src/renderer/components/+extensions/disable-extension/disable-extension.ts @@ -21,7 +21,7 @@ import type { LensExtensionId } from "../../../../extensions/lens-extension"; import type { ExtensionLoader } from "../../../../extensions/extension-loader"; -export interface Dependencies { +interface Dependencies { extensionLoader: ExtensionLoader; } diff --git a/src/renderer/components/+extensions/enable-extension/enable-extension.injectable.ts b/src/renderer/components/+extensions/enable-extension/enable-extension.injectable.ts index e3c7833ecb..a1e4e1974f 100644 --- a/src/renderer/components/+extensions/enable-extension/enable-extension.injectable.ts +++ b/src/renderer/components/+extensions/enable-extension/enable-extension.injectable.ts @@ -18,23 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable"; -import type { LensExtensionId } from "../../../../extensions/lens-extension"; -import { Dependencies, enableExtension } from "./enable-extension"; +import { enableExtension } from "./enable-extension"; -const enableExtensionInjectable: Injectable< - (id: LensExtensionId) => void, - Dependencies -> = { - getDependencies: di => ({ - extensionLoader: di.inject(extensionLoaderInjectable), - }), - - instantiate: enableExtension, +const enableExtensionInjectable = getInjectable({ + instantiate: (di) => + enableExtension({ + extensionLoader: di.inject(extensionLoaderInjectable), + }), lifecycle: lifecycleEnum.singleton, -}; +}); export default enableExtensionInjectable; diff --git a/src/renderer/components/+extensions/enable-extension/enable-extension.ts b/src/renderer/components/+extensions/enable-extension/enable-extension.ts index 0323dec194..440062b353 100644 --- a/src/renderer/components/+extensions/enable-extension/enable-extension.ts +++ b/src/renderer/components/+extensions/enable-extension/enable-extension.ts @@ -21,7 +21,7 @@ import type { LensExtensionId } from "../../../../extensions/lens-extension"; import type { ExtensionLoader } from "../../../../extensions/extension-loader"; -export interface Dependencies { +interface Dependencies { extensionLoader: ExtensionLoader; } diff --git a/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.injectable.ts b/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.injectable.ts index 7f46d4a7b8..ec3df9cc3e 100644 --- a/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.injectable.ts +++ b/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.injectable.ts @@ -19,18 +19,20 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { Injectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { + getInjectable, + lifecycleEnum, +} from "@ogre-tools/injectable"; import { UserStore } from "../../../../common/user-store"; -import { Dependencies, getBaseRegistryUrl } from "./get-base-registry-url"; +import { getBaseRegistryUrl } from "./get-base-registry-url"; -const getBaseRegistryUrlInjectable: Injectable<() => Promise, Dependencies> = { - getDependencies: () => ({ +const getBaseRegistryUrlInjectable = getInjectable({ + instantiate: () => getBaseRegistryUrl({ // TODO: use injection getRegistryUrlPreference: () => UserStore.getInstance().extensionRegistryUrl, }), - - instantiate: getBaseRegistryUrl, + lifecycle: lifecycleEnum.singleton, -}; +}); export default getBaseRegistryUrlInjectable; diff --git a/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.tsx b/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.tsx index 0d012ccf55..c811a21319 100644 --- a/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.tsx +++ b/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.tsx @@ -24,7 +24,7 @@ import { defaultExtensionRegistryUrl, ExtensionRegistry, ExtensionRegistryLocati import { promiseExecFile } from "../../../utils"; import { Notifications } from "../../notifications"; -export interface Dependencies { +interface Dependencies { getRegistryUrlPreference: () => ExtensionRegistry, } diff --git a/src/renderer/components/+extensions/install-from-input/install-from-input.injectable.ts b/src/renderer/components/+extensions/install-from-input/install-from-input.injectable.ts index 9056f8be5d..2d0f26d786 100644 --- a/src/renderer/components/+extensions/install-from-input/install-from-input.injectable.ts +++ b/src/renderer/components/+extensions/install-from-input/install-from-input.injectable.ts @@ -18,25 +18,19 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import attemptInstallInjectable from "../attempt-install/attempt-install.injectable"; -import type { Dependencies } from "./install-from-input"; import { installFromInput } from "./install-from-input"; -import attemptInstallByInfoInjectable - from "../attempt-install-by-info/attempt-install-by-info.injectable"; +import attemptInstallByInfoInjectable from "../attempt-install-by-info/attempt-install-by-info.injectable"; -const installFromInputInjectable: Injectable< - (input: string) => Promise, - Dependencies -> = { - getDependencies: di => ({ - attemptInstall: di.inject(attemptInstallInjectable), - attemptInstallByInfo: di.inject(attemptInstallByInfoInjectable), - }), +const installFromInputInjectable = getInjectable({ + instantiate: (di) => + installFromInput({ + attemptInstall: di.inject(attemptInstallInjectable), + attemptInstallByInfo: di.inject(attemptInstallByInfoInjectable), + }), - instantiate: installFromInput, lifecycle: lifecycleEnum.singleton, -}; +}); export default installFromInputInjectable; diff --git a/src/renderer/components/+extensions/install-from-input/install-from-input.tsx b/src/renderer/components/+extensions/install-from-input/install-from-input.tsx index 59e9cc267a..05ad14b087 100644 --- a/src/renderer/components/+extensions/install-from-input/install-from-input.tsx +++ b/src/renderer/components/+extensions/install-from-input/install-from-input.tsx @@ -30,7 +30,7 @@ import { readFileNotify } from "../read-file-notify/read-file-notify"; import type { InstallRequest } from "../attempt-install/install-request"; import type { ExtensionInfo } from "../attempt-install-by-info/attempt-install-by-info"; -export interface Dependencies { +interface Dependencies { attemptInstall: (request: InstallRequest, disposer?: ExtendableDisposer) => Promise, attemptInstallByInfo: (extensionInfo: ExtensionInfo) => Promise } diff --git a/src/renderer/components/+extensions/install-from-select-file-dialog/install-from-select-file-dialog.injectable.ts b/src/renderer/components/+extensions/install-from-select-file-dialog/install-from-select-file-dialog.injectable.ts index 43c4dc6565..34af624c46 100644 --- a/src/renderer/components/+extensions/install-from-select-file-dialog/install-from-select-file-dialog.injectable.ts +++ b/src/renderer/components/+extensions/install-from-select-file-dialog/install-from-select-file-dialog.injectable.ts @@ -18,18 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; -import { Dependencies, installFromSelectFileDialog } from "./install-from-select-file-dialog"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { installFromSelectFileDialog } from "./install-from-select-file-dialog"; import attemptInstallsInjectable from "../attempt-installs/attempt-installs.injectable"; -const installFromSelectFileDialogInjectable: Injectable<() => Promise, Dependencies> = { - getDependencies: di => ({ - attemptInstalls: di.inject(attemptInstallsInjectable), - }), - - instantiate: installFromSelectFileDialog, +const installFromSelectFileDialogInjectable = getInjectable({ + instantiate: (di) => + installFromSelectFileDialog({ + attemptInstalls: di.inject(attemptInstallsInjectable), + }), + lifecycle: lifecycleEnum.singleton, -}; +}); export default installFromSelectFileDialogInjectable; diff --git a/src/renderer/components/+extensions/install-from-select-file-dialog/install-from-select-file-dialog.ts b/src/renderer/components/+extensions/install-from-select-file-dialog/install-from-select-file-dialog.ts index 26c509f9f6..bb8e12e17e 100644 --- a/src/renderer/components/+extensions/install-from-select-file-dialog/install-from-select-file-dialog.ts +++ b/src/renderer/components/+extensions/install-from-select-file-dialog/install-from-select-file-dialog.ts @@ -22,7 +22,7 @@ import { dialog } from "../../../remote-helpers"; import { AppPaths } from "../../../../common/app-paths"; import { supportedExtensionFormats } from "../supported-extension-formats"; -export interface Dependencies { +interface Dependencies { attemptInstalls: (filePaths: string[]) => Promise } diff --git a/src/renderer/components/+extensions/install-on-drop/install-on-drop.injectable.ts b/src/renderer/components/+extensions/install-on-drop/install-on-drop.injectable.ts index 6fcdc90893..5fb139c45f 100644 --- a/src/renderer/components/+extensions/install-on-drop/install-on-drop.injectable.ts +++ b/src/renderer/components/+extensions/install-on-drop/install-on-drop.injectable.ts @@ -18,21 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; -import { Dependencies, installOnDrop } from "./install-on-drop"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { installOnDrop } from "./install-on-drop"; import attemptInstallsInjectable from "../attempt-installs/attempt-installs.injectable"; -const installOnDropInjectable: Injectable< - (files: File[]) => Promise, - Dependencies -> = { - getDependencies: di => ({ - attemptInstalls: di.inject(attemptInstallsInjectable), - }), +const installOnDropInjectable = getInjectable({ + instantiate: (di) => + installOnDrop({ + attemptInstalls: di.inject(attemptInstallsInjectable), + }), - instantiate: installOnDrop, lifecycle: lifecycleEnum.singleton, -}; +}); export default installOnDropInjectable; diff --git a/src/renderer/components/+extensions/install-on-drop/install-on-drop.tsx b/src/renderer/components/+extensions/install-on-drop/install-on-drop.tsx index fe96f5ab7e..1e79d618dd 100644 --- a/src/renderer/components/+extensions/install-on-drop/install-on-drop.tsx +++ b/src/renderer/components/+extensions/install-on-drop/install-on-drop.tsx @@ -20,7 +20,7 @@ */ import logger from "../../../../main/logger"; -export interface Dependencies { +interface Dependencies { attemptInstalls: (filePaths: string[]) => Promise; } diff --git a/src/renderer/components/+extensions/install.module.css b/src/renderer/components/+extensions/install.module.scss similarity index 100% rename from src/renderer/components/+extensions/install.module.css rename to src/renderer/components/+extensions/install.module.scss diff --git a/src/renderer/components/+extensions/install.tsx b/src/renderer/components/+extensions/install.tsx index 36ec5be2f9..2f81d7e950 100644 --- a/src/renderer/components/+extensions/install.tsx +++ b/src/renderer/components/+extensions/install.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./install.module.css"; +import styles from "./install.module.scss"; import React from "react"; import { prevDefault } from "../../utils"; import { Button } from "../button"; diff --git a/src/renderer/components/+extensions/installed-extensions.module.css b/src/renderer/components/+extensions/installed-extensions.module.scss similarity index 100% rename from src/renderer/components/+extensions/installed-extensions.module.css rename to src/renderer/components/+extensions/installed-extensions.module.scss diff --git a/src/renderer/components/+extensions/installed-extensions.tsx b/src/renderer/components/+extensions/installed-extensions.tsx index 14e490bed5..83c12b8603 100644 --- a/src/renderer/components/+extensions/installed-extensions.tsx +++ b/src/renderer/components/+extensions/installed-extensions.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./installed-extensions.module.css"; +import styles from "./installed-extensions.module.scss"; import React, { useMemo } from "react"; import { ExtensionDiscovery, InstalledExtension } from "../../../extensions/extension-discovery"; import { Icon } from "../icon"; diff --git a/src/renderer/components/+extensions/notice.module.css b/src/renderer/components/+extensions/notice.module.scss similarity index 100% rename from src/renderer/components/+extensions/notice.module.css rename to src/renderer/components/+extensions/notice.module.scss diff --git a/src/renderer/components/+extensions/notice.tsx b/src/renderer/components/+extensions/notice.tsx index 511388a09b..e72fa55eef 100644 --- a/src/renderer/components/+extensions/notice.tsx +++ b/src/renderer/components/+extensions/notice.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./notice.module.css"; +import styles from "./notice.module.scss"; import React, { DOMAttributes } from "react"; import { cssNames } from "../../utils"; diff --git a/src/renderer/components/+extensions/uninstall-extension/uninstall-extension.injectable.ts b/src/renderer/components/+extensions/uninstall-extension/uninstall-extension.injectable.ts index 5ffe8784c6..3660c58454 100644 --- a/src/renderer/components/+extensions/uninstall-extension/uninstall-extension.injectable.ts +++ b/src/renderer/components/+extensions/uninstall-extension/uninstall-extension.injectable.ts @@ -18,23 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; -import type { LensExtensionId } from "../../../../extensions/lens-extension"; -import extensionLoaderInjectable - from "../../../../extensions/extension-loader/extension-loader.injectable"; -import { Dependencies, uninstallExtension } from "./uninstall-extension"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable"; +import { uninstallExtension } from "./uninstall-extension"; -const uninstallExtensionInjectable: Injectable< - (extensionId: LensExtensionId) => Promise, - Dependencies -> = { - getDependencies: di => ({ - extensionLoader: di.inject(extensionLoaderInjectable), - }), +const uninstallExtensionInjectable = getInjectable({ + instantiate: (di) => + uninstallExtension({ + extensionLoader: di.inject(extensionLoaderInjectable), + }), - instantiate: uninstallExtension, lifecycle: lifecycleEnum.singleton, -}; +}); export default uninstallExtensionInjectable; diff --git a/src/renderer/components/+extensions/uninstall-extension/uninstall-extension.tsx b/src/renderer/components/+extensions/uninstall-extension/uninstall-extension.tsx index cc941adf1a..5e796e72de 100644 --- a/src/renderer/components/+extensions/uninstall-extension/uninstall-extension.tsx +++ b/src/renderer/components/+extensions/uninstall-extension/uninstall-extension.tsx @@ -28,7 +28,7 @@ import React from "react"; import { when } from "mobx"; import { getMessageFromError } from "../get-message-from-error/get-message-from-error"; -export interface Dependencies { +interface Dependencies { extensionLoader: ExtensionLoader } diff --git a/src/renderer/components/+extensions/user-extensions/user-extensions.injectable.ts b/src/renderer/components/+extensions/user-extensions/user-extensions.injectable.ts index ecaa50e454..f9506f3d71 100644 --- a/src/renderer/components/+extensions/user-extensions/user-extensions.injectable.ts +++ b/src/renderer/components/+extensions/user-extensions/user-extensions.injectable.ts @@ -18,24 +18,18 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { Injectable, lifecycleEnum } from "@ogre-tools/injectable"; -import { computed, IComputedValue } from "mobx"; -import type { InstalledExtension } from "../../../../extensions/extension-discovery"; -import type { ExtensionLoader } from "../../../../extensions/extension-loader"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable"; -const userExtensionsInjectable: Injectable< - IComputedValue, - { extensionLoader: ExtensionLoader } -> = { - getDependencies: di => ({ - extensionLoader: di.inject(extensionLoaderInjectable), - }), - +const userExtensionsInjectable = getInjectable({ lifecycle: lifecycleEnum.singleton, - instantiate: ({ extensionLoader }) => - computed(() => [...extensionLoader.userExtensions.values()]), -}; + instantiate: (di) => { + const extensionLoader = di.inject(extensionLoaderInjectable); + + return computed(() => [...extensionLoader.userExtensions.values()]); + }, +}); export default userExtensionsInjectable; diff --git a/src/renderer/components/+namespaces/namespace-select-filter.scss b/src/renderer/components/+namespaces/namespace-select-filter.scss index 69aa0bff9a..00e067ddf5 100644 --- a/src/renderer/components/+namespaces/namespace-select-filter.scss +++ b/src/renderer/components/+namespaces/namespace-select-filter.scss @@ -1,11 +1,13 @@ +@include theme-light { + .NamespaceSelectFilter { + --gradientColor: white; + } +} + .NamespaceSelectFilter { --gradientColor: var(--select-menu-bgc); .Select { - &.theme-light { - --gradientColor: white; - } - &__placeholder { width: 100%; white-space: nowrap; @@ -34,12 +36,12 @@ } &::before { - left: 0px; + left: 0; background: linear-gradient(to right, var(--gradientColor) 0px, transparent); } &::after { - right: 0px; + right: 0; background: linear-gradient(to left, var(--gradientColor) 0px, transparent); } } diff --git a/src/renderer/components/+network-policies/network-policy-details.module.css b/src/renderer/components/+network-policies/network-policy-details.module.scss similarity index 100% rename from src/renderer/components/+network-policies/network-policy-details.module.css rename to src/renderer/components/+network-policies/network-policy-details.module.scss diff --git a/src/renderer/components/+network-policies/network-policy-details.tsx b/src/renderer/components/+network-policies/network-policy-details.tsx index ff870bc2cc..21ca1f1191 100644 --- a/src/renderer/components/+network-policies/network-policy-details.tsx +++ b/src/renderer/components/+network-policies/network-policy-details.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./network-policy-details.module.css"; +import styles from "./network-policy-details.module.scss"; import React from "react"; import { DrawerItem, DrawerTitle } from "../drawer"; diff --git a/src/renderer/components/+network-port-forwards/port-forward-menu.tsx b/src/renderer/components/+network-port-forwards/port-forward-menu.tsx index ce2fa65416..f988621503 100644 --- a/src/renderer/components/+network-port-forwards/port-forward-menu.tsx +++ b/src/renderer/components/+network-port-forwards/port-forward-menu.tsx @@ -21,11 +21,10 @@ import React from "react"; import { boundMethod, cssNames } from "../../utils"; -import { openPortForward, PortForwardItem, removePortForward, startPortForward, stopPortForward } from "../../port-forward"; +import { openPortForward, PortForwardItem, removePortForward, PortForwardDialog, startPortForward, stopPortForward } from "../../port-forward"; import { MenuActions, MenuActionsProps } from "../menu/menu-actions"; import { MenuItem } from "../menu"; import { Icon } from "../icon"; -import { PortForwardDialog } from "../../port-forward"; import { Notifications } from "../notifications"; interface Props extends MenuActionsProps { diff --git a/src/renderer/components/+preferences/application.tsx b/src/renderer/components/+preferences/application.tsx index 40859cd5e0..1e3c75b68f 100644 --- a/src/renderer/components/+preferences/application.tsx +++ b/src/renderer/components/+preferences/application.tsx @@ -27,7 +27,7 @@ import { ThemeStore } from "../../theme.store"; import { UserStore } from "../../../common/user-store"; import { Input } from "../input"; import { isWindows } from "../../../common/vars"; -import { FormSwitch, Switcher } from "../switch"; +import { Switch } from "../switch"; import moment from "moment-timezone"; import { CONSTANTS, defaultExtensionRegistryUrl, ExtensionRegistryLocation } from "../../../common/user-store/preferences-helpers"; import { action } from "mobx"; @@ -86,16 +86,12 @@ export const Application = observer(() => {
- userStore.terminalCopyOnSelect = v.target.checked} - name="terminalCopyOnSelect" - /> - } - /> + userStore.terminalCopyOnSelect = !userStore.terminalCopyOnSelect} + > + Copy on select and paste on right-click +

@@ -135,16 +131,9 @@ export const Application = observer(() => {
- userStore.openAtLogin = v.target.checked} - name="startup" - /> - } - label="Automatically start Lens on login" - /> + userStore.openAtLogin = !userStore.openAtLogin}> + Automatically start Lens on login +

diff --git a/src/renderer/components/+preferences/editor.tsx b/src/renderer/components/+preferences/editor.tsx index 6f346eb63d..523f9bd278 100644 --- a/src/renderer/components/+preferences/editor.tsx +++ b/src/renderer/components/+preferences/editor.tsx @@ -21,7 +21,7 @@ import { observer } from "mobx-react"; import React from "react"; import { UserStore } from "../../../common/user-store"; -import { FormSwitch, Switcher } from "../switch"; +import { Switch } from "../switch"; import { Select } from "../select"; import { SubTitle } from "../layout/sub-title"; import { SubHeader } from "../layout/sub-header"; @@ -45,15 +45,12 @@ export const Editor = observer(() => {
- Show minimap} - control={ - editorConfiguration.minimap.enabled = checked} - /> - } - /> + editorConfiguration.minimap.enabled = !editorConfiguration.minimap.enabled} + > + Show minimap +
Position diff --git a/src/renderer/components/+preferences/helm-charts.module.css b/src/renderer/components/+preferences/helm-charts.module.scss similarity index 100% rename from src/renderer/components/+preferences/helm-charts.module.css rename to src/renderer/components/+preferences/helm-charts.module.scss diff --git a/src/renderer/components/+preferences/helm-charts.tsx b/src/renderer/components/+preferences/helm-charts.tsx index 5119e3d86c..028b3bc763 100644 --- a/src/renderer/components/+preferences/helm-charts.tsx +++ b/src/renderer/components/+preferences/helm-charts.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./helm-charts.module.css"; +import styles from "./helm-charts.module.scss"; import React from "react"; import { action, computed, observable, makeObservable } from "mobx"; diff --git a/src/renderer/components/+preferences/kubectl-binaries.tsx b/src/renderer/components/+preferences/kubectl-binaries.tsx index bffc69e3c0..13a0299ee3 100644 --- a/src/renderer/components/+preferences/kubectl-binaries.tsx +++ b/src/renderer/components/+preferences/kubectl-binaries.tsx @@ -26,7 +26,7 @@ import { getDefaultKubectlDownloadPath, UserStore } from "../../../common/user-s import { observer } from "mobx-react"; import { bundledKubectlPath } from "../../../main/kubectl"; import { SelectOption, Select } from "../select"; -import { FormSwitch, Switcher } from "../switch"; +import { Switch } from "../switch"; import { packageMirrors } from "../../../common/user-store/preferences-helpers"; export const KubectlBinaries = observer(() => { @@ -48,16 +48,12 @@ export const KubectlBinaries = observer(() => { <>
- userStore.downloadKubectlBinaries = v.target.checked} - name="kubectl-download" - /> - } - label="Download kubectl binaries matching the Kubernetes cluster version" - /> + userStore.downloadKubectlBinaries = !userStore.downloadKubectlBinaries} + > + Download kubectl binaries matching the Kubernetes cluster version +
diff --git a/src/renderer/components/+preferences/proxy.tsx b/src/renderer/components/+preferences/proxy.tsx index b85bd5e0e1..f25c1fbc9b 100644 --- a/src/renderer/components/+preferences/proxy.tsx +++ b/src/renderer/components/+preferences/proxy.tsx @@ -24,10 +24,11 @@ import React from "react"; import { UserStore } from "../../../common/user-store"; import { Input } from "../input"; import { SubTitle } from "../layout/sub-title"; -import { FormSwitch, Switcher } from "../switch"; +import { Switch } from "../switch"; export const LensProxy = observer(() => { const [proxy, setProxy] = React.useState(UserStore.getInstance().httpsProxy || ""); + const store = UserStore.getInstance(); return (
@@ -50,16 +51,9 @@ export const LensProxy = observer(() => {
- UserStore.getInstance().allowUntrustedCAs = v.target.checked} - name="startup" - /> - } - label="Allow untrusted Certificate Authorities" - /> + store.allowUntrustedCAs = !store.allowUntrustedCAs}> + Allow untrusted Certificate Authorities + This will make Lens to trust ANY certificate authority without any validations.{" "} Needed with some corporate proxies that do certificate re-writing.{" "} diff --git a/src/renderer/components/+preferences/removable-item.module.css b/src/renderer/components/+preferences/removable-item.module.scss similarity index 100% rename from src/renderer/components/+preferences/removable-item.module.css rename to src/renderer/components/+preferences/removable-item.module.scss diff --git a/src/renderer/components/+preferences/removable-item.tsx b/src/renderer/components/+preferences/removable-item.tsx index 131974f4d1..aeb68c1d33 100644 --- a/src/renderer/components/+preferences/removable-item.tsx +++ b/src/renderer/components/+preferences/removable-item.tsx @@ -18,7 +18,7 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./removable-item.module.css"; +import styles from "./removable-item.module.scss"; import React, { DOMAttributes } from "react"; diff --git a/src/renderer/components/+welcome/__test__/welcome.test.tsx b/src/renderer/components/+welcome/__test__/welcome.test.tsx index 2024d69c7e..74cf65b20d 100644 --- a/src/renderer/components/+welcome/__test__/welcome.test.tsx +++ b/src/renderer/components/+welcome/__test__/welcome.test.tsx @@ -20,45 +20,55 @@ */ import React from "react"; -import { render, screen } from "@testing-library/react"; +import { screen } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; -import { Welcome } from "../welcome"; -import { TopBarRegistry, WelcomeMenuRegistry, WelcomeBannerRegistry } from "../../../../extensions/registries"; -import { defaultWidth } from "../welcome"; +import { defaultWidth, Welcome } from "../welcome"; +import { computed } from "mobx"; +import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import type { DiRender } from "../../test-utils/renderFor"; +import { renderFor } from "../../test-utils/renderFor"; +import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable"; +import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable"; +import { LensRendererExtension } from "../../../../extensions/lens-renderer-extension"; +import type { WelcomeBannerRegistration } from "../welcome-banner-items/welcome-banner-registration"; -jest.mock( - "electron", - () => ({ - ipcRenderer: { - on: jest.fn(), - }, - app: { - getPath: () => "tmp", - }, - }), -); +jest.mock("electron", () => ({ + ipcRenderer: { + on: jest.fn(), + }, + app: { + getPath: () => "tmp", + }, +})); describe("", () => { - beforeEach(() => { - TopBarRegistry.createInstance(); - WelcomeMenuRegistry.createInstance(); - WelcomeBannerRegistry.createInstance(); - }); + let render: DiRender; + let di: ConfigurableDependencyInjectionContainer; + let welcomeBannersStub: WelcomeBannerRegistration[]; - afterEach(() => { - TopBarRegistry.resetInstance(); - WelcomeMenuRegistry.resetInstance(); - WelcomeBannerRegistry.resetInstance(); + beforeEach(() => { + di = getDiForUnitTesting(); + + render = renderFor(di); + + welcomeBannersStub = []; + + di.override(rendererExtensionsInjectable, () => + computed(() => [ + new TestExtension({ + id: "some-id", + welcomeBanners: welcomeBannersStub, + }), + ]), + ); }); it("renders registered in WelcomeBannerRegistry and hide logo", async () => { const testId = "testId"; - WelcomeBannerRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [ - { - Banner: () =>
, - }, - ]); + welcomeBannersStub.push({ + Banner: () =>
, + }); const { container } = render(); @@ -67,16 +77,15 @@ describe("", () => { }); it("calculates max width from WelcomeBanner.width registered in WelcomeBannerRegistry", async () => { - WelcomeBannerRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [ - { - width: 100, - Banner: () =>
, - }, - { - width: 800, - Banner: () =>
, - }, - ]); + welcomeBannersStub.push({ + width: 100, + Banner: () =>
, + }); + + welcomeBannersStub.push({ + width: 800, + Banner: () =>
, + }); render(); @@ -92,3 +101,25 @@ describe("", () => { }); }); }); + +class TestExtension extends LensRendererExtension { + constructor({ + id, + welcomeBanners, + }: { + id: string; + welcomeBanners: WelcomeBannerRegistration[]; + }) { + super({ + id, + absolutePath: "irrelevant", + isBundled: false, + isCompatible: false, + isEnabled: false, + manifest: { name: id, version: "some-version" }, + manifestPath: "irrelevant", + }); + + this.welcomeBanners = welcomeBanners; + } +} diff --git a/src/renderer/initializers/welcome-menu-registry.ts b/src/renderer/components/+welcome/welcome-banner-items/welcome-banner-items.injectable.ts similarity index 67% rename from src/renderer/initializers/welcome-menu-registry.ts rename to src/renderer/components/+welcome/welcome-banner-items/welcome-banner-items.injectable.ts index f658fe5715..288bb540ce 100644 --- a/src/renderer/initializers/welcome-menu-registry.ts +++ b/src/renderer/components/+welcome/welcome-banner-items/welcome-banner-items.injectable.ts @@ -18,18 +18,20 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable"; +import { computed } from "mobx"; -import { catalogURL } from "../../common/routes"; -import { WelcomeMenuRegistry } from "../../extensions/registries"; -import { navigate } from "../navigation"; +const welcomeBannerItemsInjectable = getInjectable({ + instantiate: (di) => { + const extensions = di.inject(rendererExtensionsInjectable); -export function initWelcomeMenuRegistry() { - WelcomeMenuRegistry.getInstance() - .add([ - { - title: "Browse Clusters in Catalog", - icon: "view_list", - click: () => navigate(catalogURL({ params: { group: "entity.k8slens.dev", kind: "KubernetesCluster" }} )), - }, + return computed(() => [ + ...extensions.get().flatMap((extension) => extension.welcomeBanners), ]); -} + }, + + lifecycle: lifecycleEnum.singleton, +}); + +export default welcomeBannerItemsInjectable; diff --git a/src/extensions/registries/welcome-banner-registry.ts b/src/renderer/components/+welcome/welcome-banner-items/welcome-banner-registration.d.ts similarity index 91% rename from src/extensions/registries/welcome-banner-registry.ts rename to src/renderer/components/+welcome/welcome-banner-items/welcome-banner-registration.d.ts index 1102dc8a3f..ec7cf3cbdb 100644 --- a/src/extensions/registries/welcome-banner-registry.ts +++ b/src/renderer/components/+welcome/welcome-banner-items/welcome-banner-registration.d.ts @@ -19,8 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { BaseRegistry } from "./base-registry"; - /** * WelcomeBannerRegistration is for an extension to register * Provide a Banner component to be renderered in the welcome screen. @@ -35,5 +33,3 @@ export interface WelcomeBannerRegistration { */ width?: number } - -export class WelcomeBannerRegistry extends BaseRegistry { } diff --git a/src/renderer/components/+welcome/welcome-menu-items/get-welcome-menu-items.ts b/src/renderer/components/+welcome/welcome-menu-items/get-welcome-menu-items.ts new file mode 100644 index 0000000000..c766704cef --- /dev/null +++ b/src/renderer/components/+welcome/welcome-menu-items/get-welcome-menu-items.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import { computed, IComputedValue } from "mobx"; +import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension"; +import { navigate } from "../../../navigation"; +import { catalogURL } from "../../../../common/routes"; + +interface Dependencies { + extensions: IComputedValue; +} + +export const getWelcomeMenuItems = ({ extensions }: Dependencies) => { + const browseClusters = { + title: "Browse Clusters in Catalog", + icon: "view_list", + click: () => + navigate( + catalogURL({ + params: { group: "entity.k8slens.dev", kind: "KubernetesCluster" }, + }), + ), + }; + + return computed(() => [ + browseClusters, + ...extensions.get().flatMap((extension) => extension.welcomeMenus), + ]); +}; diff --git a/src/renderer/components/+welcome/welcome-menu-items/welcome-menu-items.injectable.ts b/src/renderer/components/+welcome/welcome-menu-items/welcome-menu-items.injectable.ts new file mode 100644 index 0000000000..384b0b07bc --- /dev/null +++ b/src/renderer/components/+welcome/welcome-menu-items/welcome-menu-items.injectable.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable"; +import { getWelcomeMenuItems } from "./get-welcome-menu-items"; + +const welcomeMenuItemsInjectable = getInjectable({ + instantiate: (di) => + getWelcomeMenuItems({ + extensions: di.inject(rendererExtensionsInjectable), + }), + + lifecycle: lifecycleEnum.singleton, +}); + +export default welcomeMenuItemsInjectable; diff --git a/src/extensions/registries/welcome-menu-registry.ts b/src/renderer/components/+welcome/welcome-menu-items/welcome-menu-registration.d.ts similarity index 90% rename from src/extensions/registries/welcome-menu-registry.ts rename to src/renderer/components/+welcome/welcome-menu-items/welcome-menu-registration.d.ts index 7092028459..8f4d9833b3 100644 --- a/src/extensions/registries/welcome-menu-registry.ts +++ b/src/renderer/components/+welcome/welcome-menu-items/welcome-menu-registration.d.ts @@ -19,12 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { BaseRegistry } from "./base-registry"; - export interface WelcomeMenuRegistration { title: string | (() => string); icon: string; click: () => void | Promise; } - -export class WelcomeMenuRegistry extends BaseRegistry {} diff --git a/src/renderer/components/+welcome/welcome.tsx b/src/renderer/components/+welcome/welcome.tsx index 669e768782..56aefbfc3c 100644 --- a/src/renderer/components/+welcome/welcome.tsx +++ b/src/renderer/components/+welcome/welcome.tsx @@ -22,78 +22,129 @@ import "./welcome.scss"; import React from "react"; import { observer } from "mobx-react"; +import type { IComputedValue } from "mobx"; import Carousel from "react-material-ui-carousel"; import { Icon } from "../icon"; import { productName, slackUrl } from "../../../common/vars"; -import { WelcomeMenuRegistry } from "../../../extensions/registries"; -import { WelcomeBannerRegistry } from "../../../extensions/registries"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import welcomeMenuItemsInjectable from "./welcome-menu-items/welcome-menu-items.injectable"; +import type { WelcomeMenuRegistration } from "./welcome-menu-items/welcome-menu-registration"; +import welcomeBannerItemsInjectable from "./welcome-banner-items/welcome-banner-items.injectable"; +import type { WelcomeBannerRegistration } from "./welcome-banner-items/welcome-banner-registration"; export const defaultWidth = 320; -@observer -export class Welcome extends React.Component { - render() { - const welcomeBanner = WelcomeBannerRegistry.getInstance().getItems(); +interface Dependencies { + welcomeMenuItems: IComputedValue + welcomeBannerItems: IComputedValue +} - // if there is banner with specified width, use it to calculate the width of the container - const maxWidth = welcomeBanner.reduce((acc, curr) => { - const currWidth = curr.width ?? 0; +const NonInjectedWelcome: React.FC = ({ welcomeMenuItems, welcomeBannerItems }) => { + const welcomeBanners = welcomeBannerItems.get(); - if (acc > currWidth) { - return acc; - } + // if there is banner with specified width, use it to calculate the width of the container + const maxWidth = welcomeBanners.reduce((acc, curr) => { + const currWidth = curr.width ?? 0; - return currWidth; - }, defaultWidth); + if (acc > currWidth) { + return acc; + } - return ( -
-
- {welcomeBanner.length > 0 ? ( - 1} - autoPlay={true} - navButtonsAlwaysInvisible={true} - indicatorIconButtonProps={{ - style: { - color: "var(--iconActiveBackground)", - }, - }} - activeIndicatorIconButtonProps={{ - style: { - color: "var(--iconActiveColor)", - }, - }} - interval={8000} + return currWidth; + }, defaultWidth); + + return ( +
+
+ {welcomeBanners.length > 0 ? ( + 1} + autoPlay={true} + navButtonsAlwaysInvisible={true} + indicatorIconButtonProps={{ + style: { + color: "var(--iconActiveBackground)", + }, + }} + activeIndicatorIconButtonProps={{ + style: { + color: "var(--iconActiveColor)", + }, + }} + interval={8000} + > + {welcomeBanners.map((item, index) => ( + + ))} + + ) : ( + + )} + +
+
+

Welcome to {productName} 5!

+ +

+ To get you started we have auto-detected your clusters in your + kubeconfig file and added them to the catalog, your centralized + view for managing all your cloud-native resources. +
+
+ If you have any questions or feedback, please join our{" "} + + Lens Community slack channel + + . +

+ +
- ); - } -} +
+ ); +}; + +export const Welcome = withInjectables( + observer(NonInjectedWelcome), + + { + getProps: (di) => ({ + welcomeMenuItems: di.inject(welcomeMenuItemsInjectable), + welcomeBannerItems: di.inject(welcomeBannerItemsInjectable), + }), + }, +); diff --git a/src/renderer/components/app.scss b/src/renderer/components/app.scss index feba4d8666..40ab38ffdb 100755 --- a/src/renderer/components/app.scss +++ b/src/renderer/components/app.scss @@ -19,9 +19,12 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -@import "tailwindcss/utilities"; -@import "~flex.box"; -@import "fonts"; +@tailwind base; +@tailwind components; +@tailwind utilities; + +@import "~flex.box"; // todo: replace with tailwind's flexbox classes +@import "./fonts"; @import "../themes/theme-vars"; :root { @@ -88,6 +91,15 @@ html, body { overflow: hidden; } +#terminal-init { + position: absolute; + top: 0; + left: 0; + height: 0; + visibility: hidden; + overflow: hidden; +} + #app { height: 100%; min-height: 100%; diff --git a/src/renderer/components/avatar/avatar.module.css b/src/renderer/components/avatar/avatar.module.scss similarity index 100% rename from src/renderer/components/avatar/avatar.module.css rename to src/renderer/components/avatar/avatar.module.scss diff --git a/src/renderer/components/avatar/avatar.tsx b/src/renderer/components/avatar/avatar.tsx index 605652e99a..597c5c36f7 100644 --- a/src/renderer/components/avatar/avatar.tsx +++ b/src/renderer/components/avatar/avatar.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./avatar.module.css"; +import styles from "./avatar.module.scss"; import React, { HTMLAttributes, ImgHTMLAttributes } from "react"; import randomColor from "randomcolor"; diff --git a/src/renderer/components/badge/badge.module.css b/src/renderer/components/badge/badge.module.scss similarity index 100% rename from src/renderer/components/badge/badge.module.css rename to src/renderer/components/badge/badge.module.scss diff --git a/src/renderer/components/badge/badge.tsx b/src/renderer/components/badge/badge.tsx index fe722c3032..05588db58a 100644 --- a/src/renderer/components/badge/badge.tsx +++ b/src/renderer/components/badge/badge.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./badge.module.css"; +import styles from "./badge.module.scss"; import React from "react"; import { computed, makeObservable, observable } from "mobx"; diff --git a/src/renderer/components/checkbox/checkbox.scss b/src/renderer/components/checkbox/checkbox.scss index edc605c925..b5088bc47f 100644 --- a/src/renderer/components/checkbox/checkbox.scss +++ b/src/renderer/components/checkbox/checkbox.scss @@ -19,6 +19,12 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +@include theme-light { + .Checkbox { + --checkbox-color-active: var(--blue); + --checkbox-bgc-active: none; + } +} .Checkbox { --checkbox-color: var(--textColorPrimary); // tick color [√] @@ -27,16 +33,6 @@ flex-shrink: 0; - &.theme { - &-dark { - // default - } - &-light { - --checkbox-color-active: var(--blue); - --checkbox-bgc-active: none; - } - } - &:hover { input:not(:checked):not(:disabled) { ~ .tick:after { @@ -47,25 +43,30 @@ input[type="checkbox"] { display: none; + &:checked { ~ .box { color: var(--checkbox-color-active); background: var(--checkbox-bgc-active); border-color: var(--checkbox-bgc-active); + &:after { opacity: 1; } } } + &:disabled { ~ .box { color: var(--checkbox-color); } + ~ * { opacity: .5; pointer-events: none; } } + &:not(:disabled) ~ * { cursor: pointer; } @@ -104,4 +105,4 @@ margin-left: .5em; } } -} \ No newline at end of file +} diff --git a/src/renderer/components/checkbox/checkbox.tsx b/src/renderer/components/checkbox/checkbox.tsx index a6669a0ca7..14bf9816b1 100644 --- a/src/renderer/components/checkbox/checkbox.tsx +++ b/src/renderer/components/checkbox/checkbox.tsx @@ -24,7 +24,6 @@ import React from "react"; import { boundMethod, cssNames } from "../../utils"; export interface CheckboxProps { - theme?: "dark" | "light"; className?: string; label?: React.ReactNode; inline?: boolean; @@ -50,12 +49,11 @@ export class Checkbox extends React.PureComponent { } render() { - const { label, inline, className, value, theme, children, ...inputProps } = this.props; + const { label, inline, className, value, children, ...inputProps } = this.props; const componentClass = cssNames("Checkbox flex align-center", className, { inline, checked: value, disabled: this.props.disabled, - [`theme-${theme}`]: theme, }); return ( diff --git a/src/renderer/components/cluster-manager/bottom-bar.module.css b/src/renderer/components/cluster-manager/bottom-bar.module.scss similarity index 100% rename from src/renderer/components/cluster-manager/bottom-bar.module.css rename to src/renderer/components/cluster-manager/bottom-bar.module.scss diff --git a/src/renderer/components/cluster-manager/bottom-bar.tsx b/src/renderer/components/cluster-manager/bottom-bar.tsx index 27b5a10f3f..3213bcc0cc 100644 --- a/src/renderer/components/cluster-manager/bottom-bar.tsx +++ b/src/renderer/components/cluster-manager/bottom-bar.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./bottom-bar.module.css"; +import styles from "./bottom-bar.module.scss"; import React from "react"; import { observer } from "mobx-react"; diff --git a/src/renderer/components/cluster-manager/cluster-manager.tsx b/src/renderer/components/cluster-manager/cluster-manager.tsx index 04af444c30..300c9b2eb4 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.tsx +++ b/src/renderer/components/cluster-manager/cluster-manager.tsx @@ -39,8 +39,8 @@ import { DeleteClusterDialog } from "../delete-cluster-dialog"; import { reaction } from "mobx"; import { navigation } from "../../navigation"; import { setEntityOnRouteMatch } from "../../../main/catalog-sources/helpers/general-active-sync"; -import { TopBar } from "../layout/topbar"; import { catalogURL, getPreviousTabUrl } from "../../../common/routes"; +import { TopBar } from "../layout/top-bar/top-bar"; @observer export class ClusterManager extends React.Component { diff --git a/src/renderer/components/cluster-manager/cluster-status.module.css b/src/renderer/components/cluster-manager/cluster-status.module.scss similarity index 100% rename from src/renderer/components/cluster-manager/cluster-status.module.css rename to src/renderer/components/cluster-manager/cluster-status.module.scss diff --git a/src/renderer/components/cluster-manager/cluster-status.tsx b/src/renderer/components/cluster-manager/cluster-status.tsx index 79df15c412..5b7e73510c 100644 --- a/src/renderer/components/cluster-manager/cluster-status.tsx +++ b/src/renderer/components/cluster-manager/cluster-status.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./cluster-status.module.css"; +import styles from "./cluster-status.module.scss"; import { computed, observable, makeObservable } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; diff --git a/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx b/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx index 8270e6e31a..36c9ddbc22 100644 --- a/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx +++ b/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx @@ -23,7 +23,7 @@ import { KubeConfig } from "@kubernetes/client-node"; import { fireEvent, render } from "@testing-library/react"; import mockFs from "mock-fs"; import React from "react"; -import selectEvent from "react-select-event"; +import * as selectEvent from "react-select-event"; import { Cluster } from "../../../../main/cluster"; import { DeleteClusterDialog } from "../delete-cluster-dialog"; diff --git a/src/renderer/components/delete-cluster-dialog/delete-cluster-dialog.module.css b/src/renderer/components/delete-cluster-dialog/delete-cluster-dialog.module.scss similarity index 100% rename from src/renderer/components/delete-cluster-dialog/delete-cluster-dialog.module.css rename to src/renderer/components/delete-cluster-dialog/delete-cluster-dialog.module.scss diff --git a/src/renderer/components/delete-cluster-dialog/delete-cluster-dialog.tsx b/src/renderer/components/delete-cluster-dialog/delete-cluster-dialog.tsx index c677a8995c..e6a03444b6 100644 --- a/src/renderer/components/delete-cluster-dialog/delete-cluster-dialog.tsx +++ b/src/renderer/components/delete-cluster-dialog/delete-cluster-dialog.tsx @@ -18,7 +18,7 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./delete-cluster-dialog.module.css"; +import styles from "./delete-cluster-dialog.module.scss"; import { computed, makeObservable, observable } from "mobx"; import { observer } from "mobx-react"; @@ -244,7 +244,6 @@ export class DeleteClusterDialog extends React.Component {
Select current-context{" "} diff --git a/src/renderer/components/dock/__test__/log-resource-selector.test.tsx b/src/renderer/components/dock/__test__/log-resource-selector.test.tsx index b87adba0a7..9fe75a9756 100644 --- a/src/renderer/components/dock/__test__/log-resource-selector.test.tsx +++ b/src/renderer/components/dock/__test__/log-resource-selector.test.tsx @@ -22,7 +22,7 @@ import React from "react"; import "@testing-library/jest-dom/extend-expect"; import { render } from "@testing-library/react"; -import selectEvent from "react-select-event"; +import * as selectEvent from "react-select-event"; import { Pod } from "../../../../common/k8s-api/endpoints"; import { LogResourceSelector } from "../log-resource-selector"; diff --git a/src/renderer/components/dock/editor-panel.module.css b/src/renderer/components/dock/editor-panel.module.scss similarity index 100% rename from src/renderer/components/dock/editor-panel.module.css rename to src/renderer/components/dock/editor-panel.module.scss diff --git a/src/renderer/components/dock/editor-panel.tsx b/src/renderer/components/dock/editor-panel.tsx index dcf3197b2f..df9cc15e10 100644 --- a/src/renderer/components/dock/editor-panel.tsx +++ b/src/renderer/components/dock/editor-panel.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./editor-panel.module.css"; +import styles from "./editor-panel.module.scss"; import throttle from "lodash/throttle"; import React from "react"; import { makeObservable, observable, reaction } from "mobx"; diff --git a/src/renderer/components/dock/terminal.ts b/src/renderer/components/dock/terminal.ts index e88bfc067e..383accab20 100644 --- a/src/renderer/components/dock/terminal.ts +++ b/src/renderer/components/dock/terminal.ts @@ -34,17 +34,9 @@ import { clipboard } from "electron"; import logger from "../../../common/logger"; export class Terminal { - public static readonly spawningPool = (() => { - // terminal element must be in DOM before attaching via xterm.open(elem) - // https://xtermjs.org/docs/api/terminal/classes/terminal/#open - const pool = document.createElement("div"); - - pool.className = "terminal-init"; - pool.style.cssText = "position: absolute; top: 0; left: 0; height: 0; visibility: hidden; overflow: hidden"; - document.body.appendChild(pool); - - return pool; - })(); + public static get spawningPool() { + return document.getElementById("terminal-init"); + } static async preloadFonts() { const fontPath = require("../fonts/roboto-mono-nerd.ttf").default; // eslint-disable-line @typescript-eslint/no-var-requires diff --git a/src/renderer/components/hotbar/hotbar-entity-icon.module.css b/src/renderer/components/hotbar/hotbar-entity-icon.module.scss similarity index 100% rename from src/renderer/components/hotbar/hotbar-entity-icon.module.css rename to src/renderer/components/hotbar/hotbar-entity-icon.module.scss diff --git a/src/renderer/components/hotbar/hotbar-entity-icon.tsx b/src/renderer/components/hotbar/hotbar-entity-icon.tsx index e889306c41..56826a747b 100644 --- a/src/renderer/components/hotbar/hotbar-entity-icon.tsx +++ b/src/renderer/components/hotbar/hotbar-entity-icon.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./hotbar-entity-icon.module.css"; +import styles from "./hotbar-entity-icon.module.scss"; import React, { HTMLAttributes } from "react"; import { makeObservable, observable } from "mobx"; diff --git a/src/renderer/components/hotbar/hotbar-icon.module.css b/src/renderer/components/hotbar/hotbar-icon.module.scss similarity index 100% rename from src/renderer/components/hotbar/hotbar-icon.module.css rename to src/renderer/components/hotbar/hotbar-icon.module.scss diff --git a/src/renderer/components/hotbar/hotbar-icon.tsx b/src/renderer/components/hotbar/hotbar-icon.tsx index 0cd746b72f..6e2eead049 100644 --- a/src/renderer/components/hotbar/hotbar-icon.tsx +++ b/src/renderer/components/hotbar/hotbar-icon.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./hotbar-icon.module.css"; +import styles from "./hotbar-icon.module.scss"; import React, { useState } from "react"; diff --git a/src/renderer/components/kube-object-details/kube-object-details.scss b/src/renderer/components/kube-object-details/kube-object-details.scss index 6e9232616d..b7e2b0c6a0 100644 --- a/src/renderer/components/kube-object-details/kube-object-details.scss +++ b/src/renderer/components/kube-object-details/kube-object-details.scss @@ -21,4 +21,10 @@ .KubeObjectDetails { z-index: $zIndex-drawer + 1 !important; + + .drawer-title { + .Menu { + border: none; + } + } } \ No newline at end of file diff --git a/src/renderer/components/kube-object-details/kube-object-details.tsx b/src/renderer/components/kube-object-details/kube-object-details.tsx index be888c6505..e01c109fc5 100644 --- a/src/renderer/components/kube-object-details/kube-object-details.tsx +++ b/src/renderer/components/kube-object-details/kube-object-details.tsx @@ -31,7 +31,6 @@ import { apiManager } from "../../../common/k8s-api/api-manager"; import { crdStore } from "../+custom-resources/crd.store"; import { KubeObjectMenu } from "../kube-object-menu"; import { KubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; -import logger from "../../../main/logger"; import { CrdResourceDetails } from "../+custom-resources"; import { KubeObjectMeta } from "../kube-object-meta"; import { hideDetails, kubeDetailsUrlParam } from "../kube-detail-params"; @@ -62,7 +61,7 @@ export class KubeObjectDetails extends React.Component { .getStore(this.path) ?.getByPath(this.path); } catch (error) { - logger.error(`[KUBE-OBJECT-DETAILS]: failed to get store or object: ${error}`, { path: this.path }); + console.error(`[KUBE-OBJECT-DETAILS]: failed to get store or object: ${error}`, { path: this.path }); return undefined; } diff --git a/src/renderer/components/kube-object-menu/dependencies/api-manager.injectable.ts b/src/renderer/components/kube-object-menu/dependencies/api-manager.injectable.ts index ecc4614788..3fdd8ee997 100644 --- a/src/renderer/components/kube-object-menu/dependencies/api-manager.injectable.ts +++ b/src/renderer/components/kube-object-menu/dependencies/api-manager.injectable.ts @@ -18,15 +18,12 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { ApiManager } from "../../../../common/k8s-api/api-manager"; import { apiManager } from "../../../../common/k8s-api/api-manager"; -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; -const apiManagerInjectable: Injectable = { - getDependencies: () => ({}), +const apiManagerInjectable = getInjectable({ instantiate: () => apiManager, lifecycle: lifecycleEnum.singleton, -}; +}); export default apiManagerInjectable; diff --git a/src/renderer/components/kube-object-menu/dependencies/cluster-name.injectable.ts b/src/renderer/components/kube-object-menu/dependencies/cluster-name.injectable.ts index 992ceb27be..21776611c3 100644 --- a/src/renderer/components/kube-object-menu/dependencies/cluster-name.injectable.ts +++ b/src/renderer/components/kube-object-menu/dependencies/cluster-name.injectable.ts @@ -19,23 +19,17 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; -import type { Cluster } from "../../../../main/cluster"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import clusterInjectable from "./cluster.injectable"; -interface Dependencies { - cluster: Cluster; -} +const clusterNameInjectable = getInjectable({ + instantiate: (di) => { + const cluster = di.inject(clusterInjectable); -const clusterNameInjectable: Injectable = { - getDependencies: di => ({ - cluster: di.inject(clusterInjectable), - }), - - instantiate: ({ cluster }) => cluster?.name, + return cluster?.name; + }, lifecycle: lifecycleEnum.transient, -}; +}); export default clusterNameInjectable; diff --git a/src/renderer/components/kube-object-menu/dependencies/cluster.injectable.ts b/src/renderer/components/kube-object-menu/dependencies/cluster.injectable.ts index 17c4bab8d3..fc1d883746 100644 --- a/src/renderer/components/kube-object-menu/dependencies/cluster.injectable.ts +++ b/src/renderer/components/kube-object-menu/dependencies/cluster.injectable.ts @@ -20,14 +20,11 @@ */ import { getActiveClusterEntity } from "../../../api/catalog-entity-registry"; -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; -import type { Cluster } from "../../../../main/cluster"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; -const clusterInjectable: Injectable = { - getDependencies: () => ({}), +const clusterInjectable = getInjectable({ instantiate: () => getActiveClusterEntity(), lifecycle: lifecycleEnum.transient, -}; +}); export default clusterInjectable; diff --git a/src/renderer/components/kube-object-menu/dependencies/edit-resource-tab.injectable.ts b/src/renderer/components/kube-object-menu/dependencies/edit-resource-tab.injectable.ts index d11a4b95a6..69b2c0a8e1 100644 --- a/src/renderer/components/kube-object-menu/dependencies/edit-resource-tab.injectable.ts +++ b/src/renderer/components/kube-object-menu/dependencies/edit-resource-tab.injectable.ts @@ -19,13 +19,11 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import { editResourceTab } from "../../dock/edit-resource.store"; -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; -const editResourceTabInjectable: Injectable = { - getDependencies: () => ({}), +const editResourceTabInjectable = getInjectable({ instantiate: () => editResourceTab, lifecycle: lifecycleEnum.singleton, -}; +}); export default editResourceTabInjectable; diff --git a/src/renderer/components/kube-object-menu/dependencies/hide-details.injectable.ts b/src/renderer/components/kube-object-menu/dependencies/hide-details.injectable.ts index 1f86eafc23..fab0639190 100644 --- a/src/renderer/components/kube-object-menu/dependencies/hide-details.injectable.ts +++ b/src/renderer/components/kube-object-menu/dependencies/hide-details.injectable.ts @@ -19,13 +19,11 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import { hideDetails } from "../../kube-detail-params"; -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; -export const hideDetailsInjectable: Injectable = { - getDependencies: () => ({}), +const hideDetailsInjectable = getInjectable({ instantiate: () => hideDetails, lifecycle: lifecycleEnum.singleton, -}; +}); export default hideDetailsInjectable; diff --git a/src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/get-kube-object-menu-items.ts b/src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/get-kube-object-menu-items.ts index f027b8667c..b4eb1305a8 100644 --- a/src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/get-kube-object-menu-items.ts +++ b/src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/get-kube-object-menu-items.ts @@ -21,23 +21,18 @@ import type { KubeObjectMenuRegistry } from "../../../../../extensions/registries"; import type { KubeObject } from "../../../../../common/k8s-api/kube-object"; -export interface Dependencies { +export const getKubeObjectMenuItems = ({ + kubeObjectMenuRegistry, + kubeObject, +}: { kubeObjectMenuRegistry: KubeObjectMenuRegistry; -} - -export interface InstantiationParameter { kubeObject: KubeObject; -} - -export const getKubeObjectMenuItems = ( - { kubeObjectMenuRegistry }: Dependencies, - { kubeObject }: InstantiationParameter, -) => { +}) => { if (!kubeObject) { return []; } return kubeObjectMenuRegistry .getItemsForKind(kubeObject.kind, kubeObject.apiVersion) - .map(item => item.components.MenuItem); + .map((item) => item.components.MenuItem); }; diff --git a/src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-items.injectable.ts b/src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-items.injectable.ts index fd4b28c93d..660e1cf2e5 100644 --- a/src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-items.injectable.ts +++ b/src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-items.injectable.ts @@ -18,27 +18,20 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { Injectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import kubeObjectMenuRegistryInjectable from "./kube-object-menu-registry.injectable"; -import { - InstantiationParameter, - Dependencies, - getKubeObjectMenuItems, -} from "./get-kube-object-menu-items"; +import { getKubeObjectMenuItems } from "./get-kube-object-menu-items"; +import type { KubeObject } from "../../../../../common/k8s-api/kube-object"; -const kubeObjectMenuItemsInjectable: Injectable< - ReturnType, - Dependencies, - InstantiationParameter -> = { - getDependencies: di => ({ - kubeObjectMenuRegistry: di.inject(kubeObjectMenuRegistryInjectable), - }), - - instantiate: getKubeObjectMenuItems, +const kubeObjectMenuItemsInjectable = getInjectable({ + instantiate: (di, { kubeObject }: { kubeObject: KubeObject }) => + getKubeObjectMenuItems({ + kubeObjectMenuRegistry: di.inject(kubeObjectMenuRegistryInjectable), + kubeObject, + }), lifecycle: lifecycleEnum.transient, -}; +}); export default kubeObjectMenuItemsInjectable; diff --git a/src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registry.injectable.ts b/src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registry.injectable.ts index cb52cd7322..a8bb700662 100644 --- a/src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registry.injectable.ts +++ b/src/renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registry.injectable.ts @@ -19,13 +19,11 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import { KubeObjectMenuRegistry } from "../../../../../extensions/registries"; -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; -const kubeObjectMenuRegistryInjectable: Injectable = { - getDependencies: () => ({}), +const kubeObjectMenuRegistryInjectable = getInjectable({ instantiate: () => KubeObjectMenuRegistry.getInstance(), lifecycle: lifecycleEnum.singleton, -}; +}); export default kubeObjectMenuRegistryInjectable; diff --git a/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx b/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx index d626065004..02042850d8 100644 --- a/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx +++ b/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx @@ -55,18 +55,18 @@ describe("kube-object-menu", () => { render = renderFor(di); - di.override(clusterInjectable, { + di.override(clusterInjectable, () => ({ name: "Some name", - } as Cluster); + }) as Cluster); - di.override(apiManagerInjectable, { + di.override(apiManagerInjectable, () => ({ // eslint-disable-next-line unused-imports/no-unused-vars-ts getStore: api => undefined, - } as ApiManager); + }) as ApiManager); - di.override(hideDetailsInjectable, () => {}); + di.override(hideDetailsInjectable, () => () => {}); - di.override(editResourceTabInjectable, () => ({ + di.override(editResourceTabInjectable, () => () => ({ id: "irrelevant", kind: TabKind.TERMINAL, pinned: false, @@ -93,7 +93,7 @@ describe("kube-object-menu", () => { }); it("given no cluster, does not crash", () => { - di.override(clusterInjectable, null); + di.override(clusterInjectable, () => null); expect(() => { render(); @@ -110,7 +110,7 @@ describe("kube-object-menu", () => { describe("given kube object", () => { let baseElement: Element; - let removeActionMock: AsyncFnMock; + let removeActionMock: AsyncFnMock<() => void>; beforeEach(async () => { const objectStub = KubeObject.create({ diff --git a/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.module.css b/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.module.scss similarity index 98% rename from src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.module.css rename to src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.module.scss index 364d10ad01..23eff78a6f 100644 --- a/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.module.css +++ b/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.module.scss @@ -33,7 +33,7 @@ } } -:global(.theme-light) { +@include theme-light { .editor { border: 1px solid gainsboro; border-radius: var(--border-radius); diff --git a/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx b/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx index 02a240043a..8ea0c420f8 100644 --- a/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx +++ b/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./kubeconfig-dialog.module.css"; +import styles from "./kubeconfig-dialog.module.scss"; import React from "react"; import { makeObservable, observable } from "mobx"; import { observer } from "mobx-react"; diff --git a/src/renderer/components/layout/close-button.module.scss b/src/renderer/components/layout/close-button.module.scss new file mode 100644 index 0000000000..57a7296339 --- /dev/null +++ b/src/renderer/components/layout/close-button.module.scss @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +.closeButton { + width: 35px; + height: 35px; + display: grid; + place-items: center; + cursor: pointer; + border: 2px solid var(--textColorDimmed); + border-radius: 50%; + + &:hover { + background-color: #72767d25; + } + + &:active { + transform: translateY(1px); + } +} + +.icon { + color: var(--textColorAccent); + opacity: 0.6; +} + +.esc { + text-align: center; + margin-top: var(--margin); + font-weight: bold; + user-select: none; + color: var(--textColorDimmed); + pointer-events: none; +} \ No newline at end of file diff --git a/src/renderer/components/layout/close-button.tsx b/src/renderer/components/layout/close-button.tsx new file mode 100644 index 0000000000..9bdf78f0bd --- /dev/null +++ b/src/renderer/components/layout/close-button.tsx @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import styles from "./close-button.module.scss"; + +import React, { HTMLAttributes } from "react"; +import { Icon } from "../icon"; + +interface Props extends HTMLAttributes { +} + +export function CloseButton(props: Props) { + return ( +
+
+ +
+ +
+ ); +} diff --git a/src/renderer/components/layout/main-layout.module.css b/src/renderer/components/layout/main-layout.module.scss similarity index 100% rename from src/renderer/components/layout/main-layout.module.css rename to src/renderer/components/layout/main-layout.module.scss diff --git a/src/renderer/components/layout/main-layout.tsx b/src/renderer/components/layout/main-layout.tsx index e8316bfd3c..2c409d9ca2 100755 --- a/src/renderer/components/layout/main-layout.tsx +++ b/src/renderer/components/layout/main-layout.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./main-layout.module.css"; +import styles from "./main-layout.module.scss"; import React from "react"; import { observer } from "mobx-react"; diff --git a/src/renderer/components/layout/setting-layout.scss b/src/renderer/components/layout/setting-layout.scss index 03472f5579..abb8db3e48 100644 --- a/src/renderer/components/layout/setting-layout.scss +++ b/src/renderer/components/layout/setting-layout.scss @@ -138,41 +138,7 @@ } > .toolsRegion { - .fixedTools { - position: fixed; - top: 60px; - - .closeBtn { - width: 35px; - height: 35px; - display: grid; - place-items: center; - border: 2px solid var(--textColorDimmed); - border-radius: 50%; - cursor: pointer; - - &:hover { - background-color: #72767d4d; - } - - &:active { - transform: translateY(1px); - } - - .Icon { - color: var(--textColorTertiary); - } - } - - .esc { - text-align: center; - margin-top: 4px; - font-weight: 600; - font-size: 14px; - color: var(--textColorDimmed); - pointer-events: none; - } - } + width: 45px; } } diff --git a/src/renderer/components/layout/setting-layout.tsx b/src/renderer/components/layout/setting-layout.tsx index 282eed0dcb..96fad8ea1f 100644 --- a/src/renderer/components/layout/setting-layout.tsx +++ b/src/renderer/components/layout/setting-layout.tsx @@ -25,8 +25,8 @@ import React from "react"; import { observer } from "mobx-react"; import { cssNames, IClassName } from "../../utils"; import { navigation } from "../../navigation"; -import { Icon } from "../icon"; import { catalogURL } from "../../../common/routes"; +import { CloseButton } from "./close-button"; export interface SettingLayoutProps extends React.DOMAttributes { className?: IClassName; @@ -104,13 +104,8 @@ export class SettingLayout extends React.Component {
{ this.props.provideBackButtonNavigation && ( -
-
- -
- +
+
) } diff --git a/src/renderer/components/layout/sidebar-cluster.module.css b/src/renderer/components/layout/sidebar-cluster.module.scss similarity index 100% rename from src/renderer/components/layout/sidebar-cluster.module.css rename to src/renderer/components/layout/sidebar-cluster.module.scss diff --git a/src/renderer/components/layout/sidebar-cluster.tsx b/src/renderer/components/layout/sidebar-cluster.tsx index a4479f1a24..83a337a590 100644 --- a/src/renderer/components/layout/sidebar-cluster.tsx +++ b/src/renderer/components/layout/sidebar-cluster.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./sidebar-cluster.module.css"; +import styles from "./sidebar-cluster.module.scss"; import { observable } from "mobx"; import React, { useState } from "react"; import { HotbarStore } from "../../../common/hotbar-store"; diff --git a/src/renderer/components/layout/sidebar.module.css b/src/renderer/components/layout/sidebar.module.scss similarity index 100% rename from src/renderer/components/layout/sidebar.module.css rename to src/renderer/components/layout/sidebar.module.scss diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index bd4c4ec661..58d388242c 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./sidebar.module.css"; +import styles from "./sidebar.module.scss"; import type { TabLayoutRoute } from "./tab-layout"; import React from "react"; diff --git a/src/renderer/components/layout/top-bar/top-bar-items/top-bar-items.injectable.ts b/src/renderer/components/layout/top-bar/top-bar-items/top-bar-items.injectable.ts new file mode 100644 index 0000000000..9c5bd3d1e8 --- /dev/null +++ b/src/renderer/components/layout/top-bar/top-bar-items/top-bar-items.injectable.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import rendererExtensionsInjectable from "../../../../../extensions/renderer-extensions.injectable"; + +const topBarItemsInjectable = getInjectable({ + instantiate: (di) => { + const extensions = di.inject(rendererExtensionsInjectable); + + return computed(() => + extensions.get().flatMap((extension) => extension.topBarItems), + ); + }, + + lifecycle: lifecycleEnum.singleton, +}); + +export default topBarItemsInjectable; diff --git a/src/extensions/registries/topbar-registry.ts b/src/renderer/components/layout/top-bar/top-bar-registration.d.ts similarity index 88% rename from src/extensions/registries/topbar-registry.ts rename to src/renderer/components/layout/top-bar/top-bar-registration.d.ts index 37b55faaaa..5e470d4d26 100644 --- a/src/extensions/registries/topbar-registry.ts +++ b/src/renderer/components/layout/top-bar/top-bar-registration.d.ts @@ -18,10 +18,6 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -import type React from "react"; -import { BaseRegistry } from "./base-registry"; - interface TopBarComponents { Item: React.ComponentType; } @@ -29,6 +25,3 @@ interface TopBarComponents { export interface TopBarRegistration { components: TopBarComponents; } - -export class TopBarRegistry extends BaseRegistry { -} diff --git a/src/renderer/components/layout/__tests__/topbar-win-linux.test.tsx b/src/renderer/components/layout/top-bar/top-bar-win-linux.test.tsx similarity index 83% rename from src/renderer/components/layout/__tests__/topbar-win-linux.test.tsx rename to src/renderer/components/layout/top-bar/top-bar-win-linux.test.tsx index 1307e181cd..6d8014004e 100644 --- a/src/renderer/components/layout/__tests__/topbar-win-linux.test.tsx +++ b/src/renderer/components/layout/top-bar/top-bar-win-linux.test.tsx @@ -20,23 +20,29 @@ */ import React from "react"; -import { render, fireEvent } from "@testing-library/react"; +import { fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; -import { TopBar } from "../topbar"; -import { TopBarRegistry } from "../../../../extensions/registries"; +import { TopBar } from "./top-bar"; import { IpcMainWindowEvents } from "../../../../main/window-manager"; import { broadcastMessage } from "../../../../common/ipc"; import * as vars from "../../../../common/vars"; +import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import { DiRender, renderFor } from "../../test-utils/renderFor"; -const mockConfig = vars as { isWindows: boolean, isLinux: boolean }; +const mockConfig = vars as { isWindows: boolean; isLinux: boolean }; jest.mock("../../../../common/ipc"); jest.mock("../../../../common/vars", () => { + const SemVer = require("semver").SemVer; + + const versionStub = new SemVer("1.0.0"); + return { __esModule: true, isWindows: null, isLinux: null, + appSemVer: versionStub, }; }); @@ -57,20 +63,20 @@ jest.mock("@electron/remote", () => { }; }); -describe(" in Windows and Linux", () => { - beforeEach(() => { - TopBarRegistry.createInstance(); - }); +describe(" in Windows and Linux", () => { + let render: DiRender; - afterEach(() => { - TopBarRegistry.resetInstance(); + beforeEach(() => { + const di = getDiForUnitTesting(); + + render = renderFor(di); }); it("shows window controls on Windows", () => { mockConfig.isWindows = true; mockConfig.isLinux = false; - const { getByTestId } = render(); + const { getByTestId } = render(); expect(getByTestId("window-menu")).toBeInTheDocument(); expect(getByTestId("window-minimize")).toBeInTheDocument(); @@ -82,7 +88,7 @@ describe(" in Windows and Linux", () => { mockConfig.isWindows = false; mockConfig.isLinux = true; - const { getByTestId } = render(); + const { getByTestId } = render(); expect(getByTestId("window-menu")).toBeInTheDocument(); expect(getByTestId("window-minimize")).toBeInTheDocument(); @@ -93,7 +99,7 @@ describe(" in Windows and Linux", () => { it("triggers ipc events on click", () => { mockConfig.isWindows = true; - const { getByTestId } = render(); + const { getByTestId } = render(); const menu = getByTestId("window-menu"); const minimize = getByTestId("window-minimize"); diff --git a/src/renderer/components/layout/topbar.module.css b/src/renderer/components/layout/top-bar/top-bar.module.scss similarity index 100% rename from src/renderer/components/layout/topbar.module.css rename to src/renderer/components/layout/top-bar/top-bar.module.scss diff --git a/src/renderer/components/layout/__tests__/topbar.test.tsx b/src/renderer/components/layout/top-bar/top-bar.test.tsx similarity index 84% rename from src/renderer/components/layout/__tests__/topbar.test.tsx rename to src/renderer/components/layout/top-bar/top-bar.test.tsx index 110ddef800..a2a93c0c4c 100644 --- a/src/renderer/components/layout/__tests__/topbar.test.tsx +++ b/src/renderer/components/layout/top-bar/top-bar.test.tsx @@ -20,14 +20,23 @@ */ import React from "react"; -import { render, fireEvent } from "@testing-library/react"; +import { fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; -import { TopBar } from "../topbar"; -import { TopBarRegistry } from "../../../../extensions/registries"; +import { TopBar } from "./top-bar"; +import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable"; +import { DiRender, renderFor } from "../../test-utils/renderFor"; +import topBarItemsInjectable from "./top-bar-items/top-bar-items.injectable"; +import { computed } from "mobx"; jest.mock("../../../../common/vars", () => { + const SemVer = require("semver").SemVer; + + const versionStub = new SemVer("1.0.0"); + return { isMac: true, + appSemVer: versionStub, }; }); @@ -76,12 +85,13 @@ jest.mock("@electron/remote", () => { }); describe("", () => { - beforeEach(() => { - TopBarRegistry.createInstance(); - }); + let di: ConfigurableDependencyInjectionContainer; + let render: DiRender; - afterEach(() => { - TopBarRegistry.resetInstance(); + beforeEach(() => { + di = getDiForUnitTesting(); + + render = renderFor(di); }); it("renders w/o errors", () => { @@ -129,13 +139,13 @@ describe("", () => { const testId = "testId"; const text = "an item"; - TopBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [ + di.override(topBarItemsInjectable, () => computed(() => [ { components: { Item: () => {text}, }, }, - ]); + ])); const { getByTestId } = render(); diff --git a/src/renderer/components/layout/topbar.tsx b/src/renderer/components/layout/top-bar/top-bar.tsx similarity index 76% rename from src/renderer/components/layout/topbar.tsx rename to src/renderer/components/layout/top-bar/top-bar.tsx index af47fd2548..a0a785666b 100644 --- a/src/renderer/components/layout/topbar.tsx +++ b/src/renderer/components/layout/top-bar/top-bar.tsx @@ -19,22 +19,28 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./topbar.module.css"; +import styles from "./top-bar.module.scss"; import React, { useEffect, useMemo, useRef } from "react"; import { observer } from "mobx-react"; -import { TopBarRegistry } from "../../../extensions/registries"; -import { Icon } from "../icon"; +import type { IComputedValue } from "mobx"; +import { Icon } from "../../icon"; import { webContents, getCurrentWindow } from "@electron/remote"; import { observable } from "mobx"; -import { broadcastMessage, ipcRendererOn } from "../../../common/ipc"; -import { watchHistoryState } from "../../remote-helpers/history-updater"; -import { isActiveRoute, navigate } from "../../navigation"; -import { catalogRoute, catalogURL } from "../../../common/routes"; -import { IpcMainWindowEvents } from "../../../main/window-manager"; -import { isLinux, isWindows } from "../../../common/vars"; -import { cssNames } from "../../utils"; +import { broadcastMessage, ipcRendererOn } from "../../../../common/ipc"; +import { watchHistoryState } from "../../../remote-helpers/history-updater"; +import { isActiveRoute, navigate } from "../../../navigation"; +import { catalogRoute, catalogURL } from "../../../../common/routes"; +import { IpcMainWindowEvents } from "../../../../main/window-manager"; +import { isLinux, isWindows } from "../../../../common/vars"; +import { cssNames } from "../../../utils"; +import topBarItemsInjectable from "./top-bar-items/top-bar-items.injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { TopBarRegistration } from "./top-bar-registration"; -interface Props extends React.HTMLAttributes { +interface Props extends React.HTMLAttributes {} + +interface Dependencies { + items: IComputedValue; } const prevEnabled = observable.box(false); @@ -48,34 +54,10 @@ ipcRendererOn("history:can-go-forward", (event, state: boolean) => { nextEnabled.set(state); }); -export const TopBar = observer(({ children, ...rest }: Props) => { +const NonInjectedTopBar = (({ items, children, ...rest }: Props & Dependencies) => { const elem = useRef(); const window = useMemo(() => getCurrentWindow(), []); - const renderRegisteredItems = () => { - const items = TopBarRegistry.getInstance().getItems(); - - if (!Array.isArray(items)) { - return null; - } - - return ( -
- {items.map((registration, index) => { - if (!registration?.components?.Item) { - return null; - } - - return ( -
- -
- ); - })} -
- ); - }; - const openContextMenu = () => { broadcastMessage(IpcMainWindowEvents.OPEN_CONTEXT_MENU); }; @@ -156,7 +138,7 @@ export const TopBar = observer(({ children, ...rest }: Props) => { />
- {renderRegisteredItems()} + {renderRegisteredItems(items.get())} {children} {(isWindows || isLinux) && (
@@ -174,3 +156,29 @@ export const TopBar = observer(({ children, ...rest }: Props) => {
); }); + +const renderRegisteredItems = (items: TopBarRegistration[]) => ( +
+ {items.map((registration, index) => { + if (!registration?.components?.Item) { + return null; + } + + return ( +
+ +
+ ); + })} +
+); + + + +export const TopBar = withInjectables(observer(NonInjectedTopBar), { + getProps: (di, props) => ({ + items: di.inject(topBarItemsInjectable), + + ...props, + }), +}); diff --git a/src/renderer/components/list/list.module.css b/src/renderer/components/list/list.module.scss similarity index 100% rename from src/renderer/components/list/list.module.css rename to src/renderer/components/list/list.module.scss diff --git a/src/renderer/components/list/list.tsx b/src/renderer/components/list/list.tsx index 0cff4c7e2e..38ce0f3a37 100644 --- a/src/renderer/components/list/list.tsx +++ b/src/renderer/components/list/list.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./list.module.css"; +import styles from "./list.module.scss"; import React, { useState } from "react"; import { SearchInput } from "../input"; diff --git a/src/renderer/components/menu/menu-actions.tsx b/src/renderer/components/menu/menu-actions.tsx index 7c2ed5b7be..957147cd1b 100644 --- a/src/renderer/components/menu/menu-actions.tsx +++ b/src/renderer/components/menu/menu-actions.tsx @@ -27,7 +27,7 @@ import { observer } from "mobx-react"; import { boundMethod, cssNames } from "../../utils"; import { ConfirmDialog } from "../confirm-dialog"; import { Icon, IconProps } from "../icon"; -import { Menu, MenuItem, MenuProps } from "../menu"; +import { Menu, MenuItem, MenuProps } from "./menu"; import uniqueId from "lodash/uniqueId"; import isString from "lodash/isString"; diff --git a/src/renderer/components/menu/menu.scss b/src/renderer/components/menu/menu.scss index b9403436d5..103dd90172 100644 --- a/src/renderer/components/menu/menu.scss +++ b/src/renderer/components/menu/menu.scss @@ -119,7 +119,7 @@ } } -.theme-light { +@include theme-light { .Menu { border: 1px solid var(--borderColor); } diff --git a/src/renderer/components/mixins.scss b/src/renderer/components/mixins.scss index a3bff39a6b..5cf7458ae0 100755 --- a/src/renderer/components/mixins.scss +++ b/src/renderer/components/mixins.scss @@ -64,3 +64,14 @@ -webkit-app-region: no-drag; } } + +@mixin theme-light() { + $selector: "theme-light"; // keep in sync with class in `theme.store.ts` + + body.#{$selector} { + @content; + } + :global(body.#{$selector}) { + @content; // css-modules (*.module.scss) + } +} diff --git a/src/renderer/components/monaco-editor/monaco-editor.module.css b/src/renderer/components/monaco-editor/monaco-editor.module.scss similarity index 100% rename from src/renderer/components/monaco-editor/monaco-editor.module.css rename to src/renderer/components/monaco-editor/monaco-editor.module.scss diff --git a/src/renderer/components/monaco-editor/monaco-editor.tsx b/src/renderer/components/monaco-editor/monaco-editor.tsx index e01ce37479..4a6a4320ba 100644 --- a/src/renderer/components/monaco-editor/monaco-editor.tsx +++ b/src/renderer/components/monaco-editor/monaco-editor.tsx @@ -19,12 +19,13 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./monaco-editor.module.css"; +import styles from "./monaco-editor.module.scss"; import React from "react"; import { observer } from "mobx-react"; import { action, computed, makeObservable, observable, reaction } from "mobx"; import { editor, Uri } from "monaco-editor"; -import { MonacoTheme, MonacoValidator, monacoValidators } from "./index"; +import type { MonacoTheme } from "./monaco-themes"; +import { MonacoValidator, monacoValidators } from "./monaco-validators"; import { debounce, merge } from "lodash"; import { cssNames, disposer } from "../../utils"; import { UserStore } from "../../../common/user-store"; diff --git a/src/renderer/components/select/select.scss b/src/renderer/components/select/select.scss index 8f5a3fa0e9..7e731a9aca 100644 --- a/src/renderer/components/select/select.scss +++ b/src/renderer/components/select/select.scss @@ -228,10 +228,15 @@ html { } .Select { + &__value-container { + margin-top: 2px; + margin-bottom: 2px; + } + &__control { box-shadow: 0 0 0 1px var(--inputControlBorder); background: var(--inputControlBackground); - border-radius: 5px; + border-radius: var(--border-radius); } &__single-value { diff --git a/src/renderer/components/switch/__tests__/switch.test.tsx b/src/renderer/components/switch/__tests__/switch.test.tsx new file mode 100644 index 0000000000..d5187e1417 --- /dev/null +++ b/src/renderer/components/switch/__tests__/switch.test.tsx @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import React from "react"; +import { fireEvent, render } from "@testing-library/react"; +import "@testing-library/jest-dom/extend-expect"; +import { Switch } from ".."; + +describe("", () => { + it("renders w/o errors", () => { + const { container } = render(); + + expect(container).toBeInstanceOf(HTMLElement); + }); + + it("render label text", () => { + const { getByLabelText } = render(Test label); + + expect(getByLabelText("Test label")).toBeTruthy(); + }); + + it("passes disabled and checked attributes to input", () => { + const { container } = render(); + const checkbox = container.querySelector("input[type=checkbox]"); + + expect(checkbox).toHaveAttribute("disabled"); + expect(checkbox).toHaveAttribute("checked"); + }); + + it("onClick event fired", () => { + const onClick = jest.fn(); + const { getByTestId } = render(); + const switcher = getByTestId("switch"); + + fireEvent.click(switcher); + + expect(onClick).toHaveBeenCalled(); + }); + + it("onClick event not fired for disabled item", () => { + const onClick = jest.fn(); + const { getByTestId } = render(); + const switcher = getByTestId("switch"); + + fireEvent.click(switcher); + + expect(onClick).not.toHaveBeenCalled(); + }); +}); diff --git a/src/renderer/components/switch/form-switcher.tsx b/src/renderer/components/switch/form-switcher.tsx index 14df95f676..6499fb0b0f 100644 --- a/src/renderer/components/switch/form-switcher.tsx +++ b/src/renderer/components/switch/form-switcher.tsx @@ -35,6 +35,9 @@ const useStyles = makeStyles({ }, }); +/** + * @deprecated Use instead from "../switch.tsx". + */ export function FormSwitch(props: FormControlLabelProps) { const classes = useStyles(); diff --git a/src/renderer/components/switch/index.ts b/src/renderer/components/switch/index.ts index 60d44f3324..6987ce5b38 100644 --- a/src/renderer/components/switch/index.ts +++ b/src/renderer/components/switch/index.ts @@ -21,3 +21,4 @@ export * from "./switcher"; export * from "./form-switcher"; +export * from "./switch"; diff --git a/src/renderer/components/switch/switch.module.scss b/src/renderer/components/switch/switch.module.scss new file mode 100644 index 0000000000..b890919c61 --- /dev/null +++ b/src/renderer/components/switch/switch.module.scss @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +.Switch { + --thumb-size: 2rem; + --thumb-color: hsl(0 0% 100%); + --thumb-color-highlight: hsl(0 0% 100% / 25%); + + --track-size: calc(var(--thumb-size) * 2); + --track-padding: 2px; + --track-color-inactive: hsl(80 0% 35%); + --track-color-active: hsl(110, 60%, 60%); + + --thumb-position: 0%; + --thumb-transition-duration: .25s; + + --hover-highlight-size: 0; + + display: flex; + align-items: center; + gap: 2ch; + justify-content: space-between; + cursor: pointer; + user-select: none; + color: var(--textColorAccent); + font-weight: 500; + + & > input { + padding: var(--track-padding); + background: var(--track-color-inactive); + inline-size: var(--track-size); + block-size: var(--thumb-size); + border-radius: var(--track-size); + + appearance: none; + pointer-events: none; + border: none; + outline-offset: 5px; + box-sizing: content-box; + + flex-shrink: 0; + display: grid; + align-items: center; + grid: [track] 1fr / [track] 1fr; + + transition: background-color .25s ease; + + &::before { + content: ""; + cursor: pointer; + pointer-events: auto; + grid-area: track; + inline-size: var(--thumb-size); + block-size: var(--thumb-size); + background: var(--thumb-color); + box-shadow: 0 0 0 var(--hover-highlight-size) var(--thumb-color-highlight); + border-radius: 50%; + transform: translateX(var(--thumb-position)); + transition: + transform var(--thumb-transition-duration) ease, + box-shadow .25s ease; + } + + &:not(:disabled):hover::before { + --hover-highlight-size: .5rem; + } + + &:checked { + background: var(--track-color-active); + --thumb-position: 100%; + } + + &:disabled { + --track-color-inactive: hsl(80 0% 30%); + --thumb-color: transparent; + + &::before { + cursor: not-allowed; + box-shadow: inset 0 0 0 2px hsl(0 0% 0% / 40%); + } + } + + &:focus-visible { + box-shadow: 0 0 0 2px var(--blue); + } + } + + &.disabled { + cursor: not-allowed; + } +} + +@include theme-light { + .Switch { + --thumb-color-highlight: hsl(0 0% 0% / 25%); + + & > input { + &:disabled { + --track-color-inactive: hsl(80 0% 80%); + } + } + } +} diff --git a/src/renderer/components/switch/switch.tsx b/src/renderer/components/switch/switch.tsx new file mode 100644 index 0000000000..7bf5f2b3be --- /dev/null +++ b/src/renderer/components/switch/switch.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import styles from "./switch.module.scss"; + +import React, { ChangeEvent, HTMLProps } from "react"; +import { cssNames } from "../../utils"; + +interface Props extends Omit, "onChange"> { + onChange?: (checked: boolean, event: ChangeEvent) => void; +} + +export function Switch({ children, disabled, onChange, ...props }: Props) { + return ( + + ); +} diff --git a/src/renderer/components/switch/switcher.tsx b/src/renderer/components/switch/switcher.tsx index 749e5134c9..136175e394 100644 --- a/src/renderer/components/switch/switcher.tsx +++ b/src/renderer/components/switch/switcher.tsx @@ -31,6 +31,9 @@ interface Props extends SwitchProps { classes: Styles; } +/** + * @deprecated Use instead from "../switch.tsx". + */ export const Switcher = withStyles((theme: Theme) => createStyles({ root: { diff --git a/src/renderer/components/table/react-table.module.css b/src/renderer/components/table/react-table.module.scss similarity index 100% rename from src/renderer/components/table/react-table.module.css rename to src/renderer/components/table/react-table.module.scss diff --git a/src/renderer/components/table/react-table.tsx b/src/renderer/components/table/react-table.tsx index 27b19f50dd..e5cedff78f 100644 --- a/src/renderer/components/table/react-table.tsx +++ b/src/renderer/components/table/react-table.tsx @@ -19,9 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import styles from "./react-table.module.css"; -import React from "react"; -import { useCallback, useMemo } from "react"; +import styles from "./react-table.module.scss"; +import React, { useCallback, useMemo } from "react"; import { useFlexLayout, useSortBy, useTable, UseTableOptions } from "react-table"; import { Icon } from "../icon"; import { cssNames } from "../../utils"; diff --git a/src/renderer/initializers/index.ts b/src/renderer/initializers/index.ts index 55d1bbbb1b..b865368988 100644 --- a/src/renderer/initializers/index.ts +++ b/src/renderer/initializers/index.ts @@ -27,7 +27,6 @@ export * from "./ipc"; export * from "./kube-object-detail-registry"; export * from "./kube-object-menu-registry"; export * from "./registries"; -export * from "./welcome-menu-registry"; export * from "./workloads-overview-detail-registry"; export * from "./catalog-category-registry"; export * from "./status-bar-registry"; diff --git a/src/renderer/initializers/registries.ts b/src/renderer/initializers/registries.ts index 3063213bc8..a27c47153b 100644 --- a/src/renderer/initializers/registries.ts +++ b/src/renderer/initializers/registries.ts @@ -33,8 +33,5 @@ export function initRegistries() { registries.KubeObjectMenuRegistry.createInstance(); registries.KubeObjectStatusRegistry.createInstance(); registries.StatusBarRegistry.createInstance(); - registries.WelcomeMenuRegistry.createInstance(); - registries.WelcomeBannerRegistry.createInstance(); registries.WorkloadsOverviewDetailRegistry.createInstance(); - registries.TopBarRegistry.createInstance(); } diff --git a/src/renderer/port-forward/port-forward-dialog.tsx b/src/renderer/port-forward/port-forward-dialog.tsx index d63464880b..8a72f2c5ac 100644 --- a/src/renderer/port-forward/port-forward-dialog.tsx +++ b/src/renderer/port-forward/port-forward-dialog.tsx @@ -30,7 +30,8 @@ import { Input } from "../components/input"; import { cssNames, noop } from "../utils"; import { addPortForward, getPortForwards, modifyPortForward } from "./port-forward.store"; import type { ForwardedPort } from "./port-forward-item"; -import { aboutPortForwarding, notifyErrorPortForwarding, openPortForward } from "."; +import { openPortForward } from "./port-forward-utils"; +import { aboutPortForwarding, notifyErrorPortForwarding } from "./port-forward-notify"; import { Checkbox } from "../components/checkbox"; import logger from "../../common/logger"; @@ -156,14 +157,12 @@ export class PortForwardDialog extends Component {
dialogState.useHttps = value} /> dialogState.openInBrowser = value} diff --git a/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.injectable.ts b/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.injectable.ts index c4eb3d9a58..ce39eb6989 100644 --- a/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.injectable.ts +++ b/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.injectable.ts @@ -18,24 +18,21 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import attemptInstallByInfoInjectable from "../../components/+extensions/attempt-install-by-info/attempt-install-by-info.injectable"; -import { - bindProtocolAddRouteHandlers, - Dependencies, -} from "./bind-protocol-add-route-handlers"; -import lensProtocolRouterRendererInjectable - from "../lens-protocol-router-renderer/lens-protocol-router-renderer.injectable"; +import { bindProtocolAddRouteHandlers } from "./bind-protocol-add-route-handlers"; +import lensProtocolRouterRendererInjectable from "../lens-protocol-router-renderer/lens-protocol-router-renderer.injectable"; -const bindProtocolAddRouteHandlersInjectable: Injectable<() => void, Dependencies> = { - getDependencies: di => ({ - attemptInstallByInfo: di.inject(attemptInstallByInfoInjectable), - lensProtocolRouterRenderer: di.inject(lensProtocolRouterRendererInjectable), - }), +const bindProtocolAddRouteHandlersInjectable = getInjectable({ + instantiate: (di) => + bindProtocolAddRouteHandlers({ + attemptInstallByInfo: di.inject(attemptInstallByInfoInjectable), + lensProtocolRouterRenderer: di.inject( + lensProtocolRouterRendererInjectable, + ), + }), - instantiate: bindProtocolAddRouteHandlers, lifecycle: lifecycleEnum.singleton, -}; +}); export default bindProtocolAddRouteHandlersInjectable; diff --git a/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.tsx b/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.tsx index 6ff367f654..e247b3dde9 100644 --- a/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.tsx +++ b/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.tsx @@ -33,7 +33,7 @@ import { Notifications } from "../../components/notifications"; import * as routes from "../../../common/routes"; import type { ExtensionInfo } from "../../components/+extensions/attempt-install-by-info/attempt-install-by-info"; -export interface Dependencies { +interface Dependencies { attemptInstallByInfo: (extensionInfo: ExtensionInfo) => Promise; lensProtocolRouterRenderer: LensProtocolRouterRenderer; } diff --git a/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts b/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts index c46c129350..f96f33fcb6 100644 --- a/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts +++ b/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts @@ -18,23 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Injectable } from "@ogre-tools/injectable"; -import { lifecycleEnum } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable"; -import type { Dependencies } from "./lens-protocol-router-renderer"; import { LensProtocolRouterRenderer } from "./lens-protocol-router-renderer"; -const lensProtocolRouterRendererInjectable: Injectable< - LensProtocolRouterRenderer, - Dependencies -> = { - getDependencies: di => ({ - extensionLoader: di.inject(extensionLoaderInjectable), - }), - - instantiate: dependencies => new LensProtocolRouterRenderer(dependencies), +const lensProtocolRouterRendererInjectable = getInjectable({ + instantiate: (di) => + new LensProtocolRouterRenderer({ + extensionLoader: di.inject(extensionLoaderInjectable), + }), lifecycle: lifecycleEnum.singleton, -}; +}); export default lensProtocolRouterRendererInjectable; diff --git a/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.tsx b/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.tsx index 0caf1f1258..bd3edc1bd5 100644 --- a/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.tsx +++ b/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.tsx @@ -47,7 +47,7 @@ function verifyIpcArgs(args: unknown[]): args is [string, RouteAttempt] { } } -export interface Dependencies { +interface Dependencies { extensionLoader: ExtensionLoader } diff --git a/src/renderer/root-frame.tsx b/src/renderer/root-frame.tsx index c403af4bed..33132570c0 100644 --- a/src/renderer/root-frame.tsx +++ b/src/renderer/root-frame.tsx @@ -29,7 +29,7 @@ import { ErrorBoundary } from "./components/error-boundary"; import { Notifications } from "./components/notifications"; import { ConfirmDialog } from "./components/confirm-dialog"; import type { ExtensionLoader } from "../extensions/extension-loader"; -import { broadcastMessage } from "../common/ipc"; +import { broadcastMessage, BundledExtensionsLoaded } from "../common/ipc"; import { CommandContainer } from "./components/command-palette/command-container"; import { registerIpcListeners } from "./ipc"; import { ipcRenderer } from "electron"; @@ -39,6 +39,7 @@ import logger from "../common/logger"; import { unmountComponentAtNode } from "react-dom"; import { ClusterFrameHandler } from "./components/cluster-manager/lens-views"; import type { LensProtocolRouterRenderer } from "./protocol-handler"; +import { delay } from "./utils"; injectSystemCAs(); @@ -54,8 +55,21 @@ export class RootFrame extends React.Component { lensProtocolRouterRendererInjectable: LensProtocolRouterRenderer, ) { catalogEntityRegistry.init(); - extensionLoader.loadOnClusterManagerRenderer(); + + try { + // maximum time to let bundled extensions finish loading + const timeout = delay(10000); + + const loadingExtensions = extensionLoader.loadOnClusterManagerRenderer(); + const loadingBundledExtensions = loadingExtensions.filter(e => e.isBundled).map(e => e.loaded); + const bundledExtensionsFinished = Promise.all(loadingBundledExtensions); + + await Promise.race([bundledExtensionsFinished, timeout]); + } finally { + ipcRenderer.send(BundledExtensionsLoaded); + } lensProtocolRouterRendererInjectable.init(); + bindProtocolAddRouteHandlers(); window.addEventListener("offline", () => broadcastMessage("network:offline")); diff --git a/src/renderer/template.html b/src/renderer/template.html index c7df1ee507..fd4d35c5a6 100755 --- a/src/renderer/template.html +++ b/src/renderer/template.html @@ -6,6 +6,7 @@
+
diff --git a/src/renderer/theme.store.ts b/src/renderer/theme.store.ts index 144ecf7d47..15aa055f92 100644 --- a/src/renderer/theme.store.ts +++ b/src/renderer/theme.store.ts @@ -27,6 +27,7 @@ import lensDarkThemeJson from "./themes/lens-dark.json"; import lensLightThemeJson from "./themes/lens-light.json"; import type { SelectOption } from "./components/select"; import type { MonacoEditorProps } from "./components/monaco-editor"; +import { defaultTheme } from "../common/vars"; export type ThemeId = string; @@ -40,7 +41,6 @@ export interface Theme { } export class ThemeStore extends Singleton { - static readonly defaultTheme = "lens-dark"; protected styles: HTMLStyleElement; // bundled themes from `themes/${themeId}.json` @@ -54,7 +54,7 @@ export class ThemeStore extends Singleton { } @computed get activeTheme(): Theme { - return this.themes.get(this.activeThemeId) ?? this.themes.get(ThemeStore.defaultTheme); + return this.themes.get(this.activeThemeId) ?? this.themes.get(defaultTheme); } @computed get themeOptions(): SelectOption[] { diff --git a/src/renderer/utils/storageHelper.ts b/src/renderer/utils/storageHelper.ts index 805296b8d9..b5d5e6abde 100755 --- a/src/renderer/utils/storageHelper.ts +++ b/src/renderer/utils/storageHelper.ts @@ -21,7 +21,7 @@ // Helper for working with storages (e.g. window.localStorage, NodeJS/file-system, etc.) import { action, comparer, makeObservable, observable, toJS, when } from "mobx"; -import produce, { Draft, isDraft } from "immer"; +import { produce, Draft, isDraft } from "immer"; import { isEqual, isPlainObject } from "lodash"; import logger from "../../main/logger"; import { getHostedClusterId } from "../../common/utils"; diff --git a/tailwind.config.js b/tailwind.config.js index 040f2dce23..7357650c87 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -20,14 +20,20 @@ */ module.exports = { - mode: "jit", - purge: ["src/**/*.tsx"], + content: ["src/**/*.tsx"], darkMode: "class", theme: { fontFamily: { sans: ["Roboto", "Helvetica", "Arial", "sans-serif"], }, - extend: {}, + extend: { + colors: { + textAccent: "var(--textColorAccent)", + textPrimary: "var(--textColorPrimary)", + textTertiary: "var(--textColorTertiary)", + textDimmed: "var(--textColorDimmed)", + }, + }, }, variants: { extend: {}, diff --git a/types/mocks.d.ts b/types/mocks.d.ts index 57e2812dcb..8b26cb3a03 100644 --- a/types/mocks.d.ts +++ b/types/mocks.d.ts @@ -29,6 +29,14 @@ declare const __static: string; // Support import for custom module extensions // https://www.typescriptlang.org/docs/handbook/modules.html#wildcard-module-declarations +declare module "*.module.scss" { + const classes: { [key: string]: string }; + export default classes; +} +declare module "*.module.css" { + const classes: { [key: string]: string }; + export default classes; +} declare module "*.scss" { const content: string; export = content; @@ -37,7 +45,3 @@ declare module "*.ttf" { const content: string; export = content; } -declare module "*.module.css" { - const classes: { [key: string]: string }; - export default classes; -} diff --git a/webpack.extensions.ts b/webpack.extensions.ts index 7b7e876fdc..c8d058401e 100644 --- a/webpack.extensions.ts +++ b/webpack.extensions.ts @@ -89,14 +89,13 @@ export default function generateExtensionTypes(): webpack.Configuration { { test: /\.s?css$/, use: [ - // creates `style` nodes from JS strings "style-loader", - // translates CSS into CommonJS "css-loader", + "postcss-loader", { loader: "sass-loader", options: { - prependData: `@import "${path.basename(sassCommonVars)}";`, + additionalData: `@import "${path.basename(sassCommonVars)}";`, sassOptions: { includePaths: [ path.dirname(sassCommonVars), diff --git a/webpack.main.ts b/webpack.main.ts index f18dba4c0b..a3967dd071 100755 --- a/webpack.main.ts +++ b/webpack.main.ts @@ -27,6 +27,7 @@ import nodeExternals from "webpack-node-externals"; import ProgressBarPlugin from "progress-bar-webpack-plugin"; import * as vars from "./src/common/vars"; import getTSLoader from "./src/common/getTSLoader"; +import CircularDependencyPlugin from "circular-dependency-plugin"; const configs: { (): webpack.Configuration }[] = []; @@ -64,6 +65,12 @@ configs.push((): webpack.Configuration => { plugins: [ new ProgressBarPlugin(), new ForkTsCheckerPlugin(), + + new CircularDependencyPlugin({ + cwd: __dirname, + exclude: /node_modules/, + failOnError: true, + }), ].filter(Boolean), }; }); diff --git a/webpack.renderer.ts b/webpack.renderer.ts index 5ade45b6d3..8be6eb35dc 100755 --- a/webpack.renderer.ts +++ b/webpack.renderer.ts @@ -30,6 +30,7 @@ import ProgressBarPlugin from "progress-bar-webpack-plugin"; import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin"; import MonacoWebpackPlugin from "monaco-editor-webpack-plugin"; import getTSLoader from "./src/common/getTSLoader"; +import CircularDependencyPlugin from "circular-dependency-plugin"; export default [ webpackLensRenderer, @@ -119,21 +120,30 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura { loader: "css-loader", options: { + sourceMap: isDevelopment, modules: { - auto: true, - mode: "local", + auto: /\.module\./i, // https://github.com/webpack-contrib/css-loader#auto + mode: "local", // :local(.selector) by default localIdentName: "[name]__[local]--[hash:base64:5]", }, }, }, { loader: "postcss-loader", + options: { + sourceMap: isDevelopment, + postcssOptions: { + plugins: [ + "tailwindcss", + ], + }, + }, }, { loader: "sass-loader", options: { sourceMap: isDevelopment, - prependData: `@import "${path.basename(sassCommonVars)}";`, + additionalData: `@import "${path.basename(sassCommonVars)}";`, sassOptions: { includePaths: [ path.dirname(sassCommonVars), @@ -158,27 +168,18 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura globalAPI: isDevelopment, }), - // todo: fix remain warnings about circular dependencies - // new CircularDependencyPlugin({ - // cwd: __dirname, - // exclude: /node_modules/, - // allowAsyncCycles: true, - // failOnError: false, - // }), - - // todo: check if this actually works in mode=production files - // new webpack.DllReferencePlugin({ - // context: process.cwd(), - // manifest: manifestPath, - // sourceType: libraryTarget, - // }), - new HtmlWebpackPlugin({ filename: `${appName}.html`, template: htmlTemplate, inject: true, }), + new CircularDependencyPlugin({ + cwd: __dirname, + exclude: /node_modules/, + failOnError: true, + }), + new MiniCssExtractPlugin({ filename: "[name].css", }), diff --git a/yarn.lock b/yarn.lock index e791116113..70fb22d9fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.1.1.tgz#9274ec7460652f9c632c59addf24efb1684ef876" integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ== -"@async-fn/jest@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@async-fn/jest/-/jest-1.5.0.tgz#dda820de3e48eca6d5b17b00a01bdb060dfcc233" - integrity sha512-IeuTZj1TdoBS64fpNfHG9efw6dW3UQuIXfEv0qhxyNro3368kKz9E/eO46zvPmQBxbBCox3RCbKexAJH1WAY+Q== +"@async-fn/jest@1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@async-fn/jest/-/jest-1.5.3.tgz#42be6c0e8ba5ccd737e006ca600e7e319fe2a591" + integrity sha512-iQ9gAPZFW5U6TNcFS99ffwYYsB9LNecTnvG73BaDc/zAD0qOWctY1imEACC1pLymmm/xaf/OUq9I9QenfkatTA== "@babel/code-frame@7.12.11": version "7.12.11" @@ -972,28 +972,28 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" -"@ogre-tools/fp@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@ogre-tools/fp/-/fp-1.4.0.tgz#94c50378c5bc51ea1571f775e4428256f22c61b5" - integrity sha512-Eh/pK67CoYU/tJPWHeuNFEp+YdE8RPAAxZlSDAoXUDAd8sta3e+1vG7OEJlkYIJW4L8sCGKLWZu2DZ8uI6URhA== +"@ogre-tools/fp@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@ogre-tools/fp/-/fp-2.0.0.tgz#751b280959e7b7132e85ae031a96ea59c951178f" + integrity sha512-L3UWyIHuA1z746jSsHV4iwy5Yc/qz8g/0YktiMgYjU6aU5jFtX0vaVVS7MRJWMnOAFg5JcJyg3KqgyYXSe5GKA== dependencies: lodash "^4.17.21" -"@ogre-tools/injectable-react@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@ogre-tools/injectable-react/-/injectable-react-1.5.2.tgz#7df925ca5abda86210f527333774ddbf027f0693" - integrity sha512-n26NyGLYjwyIbfwsLj1tg0uNPK6SI/H0vGisMNpNfrcdci2QiLQBSKemnLMMRn7LF/+JhA/NQClTetiMkcSCuw== +"@ogre-tools/injectable-react@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@ogre-tools/injectable-react/-/injectable-react-2.0.0.tgz#4a0990426dba0485d6c71e9693ba8882aa42a5d0" + integrity sha512-yhYi7jhhi2CitO07xVPHOWBCOESmTgmGcSILWoY6IPjoKdzl88PqMkTem8kO+k6YO3sTQQexEQcCi9NEmR9hOg== dependencies: - "@ogre-tools/fp" "^1.4.0" - "@ogre-tools/injectable" "^1.5.0" + "@ogre-tools/fp" "^2.0.0" + "@ogre-tools/injectable" "^2.0.0" lodash "^4.17.21" -"@ogre-tools/injectable@1.5.0", "@ogre-tools/injectable@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@ogre-tools/injectable/-/injectable-1.5.0.tgz#edf911f360e73bb5f10ac669f147108d1465fc45" - integrity sha512-k0Wgc8QqB+p/gHcWPVWJV8N8xX5cMpagEjZ1C5bPVeRyB+73od/yHgb1HOgL2pPzlE6qOtTdkiUrWuTAoqnqUw== +"@ogre-tools/injectable@2.0.0", "@ogre-tools/injectable@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@ogre-tools/injectable/-/injectable-2.0.0.tgz#16e6255062443ae8aca9a2d149c5b591533b63a8" + integrity sha512-9R8J9vJp/obaKKTGSDiJ+PTodJtZ2JCUdzGexL2lQK4s/xvVTjpBhCgQZVCG87uA/6d0oB6rxov/Dkv001rwCw== dependencies: - "@ogre-tools/fp" "^1.4.0" + "@ogre-tools/fp" "^2.0.0" lodash "^4.17.21" "@panva/asn1.js@^1.0.0": @@ -1358,6 +1358,13 @@ dependencies: moment "^2.10.2" +"@types/circular-dependency-plugin@5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/circular-dependency-plugin/-/circular-dependency-plugin-5.0.4.tgz#c5ccbd1d2bbb39b60e9859b39c6b826f60567ef2" + integrity sha512-J4XkMJfkGv3o3q2Ca821cufIBNBFms45fz+xD9tEESR0YqL5BlwETOwm2desSCdki2zdcPRhG9ZQCm/WITCEPQ== + dependencies: + "@types/webpack" "^4" + "@types/clean-css@*": version "4.2.1" resolved "https://registry.yarnpkg.com/@types/clean-css/-/clean-css-4.2.1.tgz#cb0134241ec5e6ede1b5344bc829668fd9871a8d" @@ -2711,6 +2718,15 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array.prototype.flat@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" + integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + array.prototype.flatmap@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz#908dc82d8a406930fdf38598d51e7411d18d4446" @@ -3356,7 +3372,7 @@ bytes@3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= -bytes@3.1.0, bytes@^3.0.0: +bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== @@ -3415,17 +3431,17 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" -cacheable-request@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" - integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== +cacheable-request@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" + integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== dependencies: clone-response "^1.0.2" get-stream "^5.1.0" http-cache-semantics "^4.0.0" keyv "^4.0.0" lowercase-keys "^2.0.0" - normalize-url "^4.1.0" + normalize-url "^6.0.1" responselike "^2.0.0" call-bind@^1.0.0: @@ -3747,15 +3763,6 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" @@ -3915,11 +3922,6 @@ commander@^5.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== -commander@^6.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" - integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== - commander@^8.2.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" @@ -4283,11 +4285,6 @@ css-box-model@^1.2.0: dependencies: tiny-invariant "^1.0.6" -css-color-names@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" - integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= - css-loader@^5.2.7: version "5.2.7" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae" @@ -4329,11 +4326,6 @@ css-selector-tokenizer@^0.7.0: cssesc "^3.0.0" fastparse "^1.1.2" -css-unit-converter@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21" - integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA== - css-vendor@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d" @@ -4455,21 +4447,28 @@ debug@3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: +debug@4, debug@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: ms "2.1.2" -debug@4.3.1: +debug@4.3.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: ms "2.1.2" -debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.6: +debug@^3.0.0, debug@^3.1.1, debug@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^3.1.0, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -4877,10 +4876,10 @@ domhandler@^2.3.0: dependencies: domelementtype "1" -dompurify@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.3.tgz#c1af3eb88be47324432964d8abc75cf4b98d634c" - integrity sha512-dqnqRkPMAjOZE0FogZ+ceJNM2dZ3V/yNOuFB7+39qpO93hHhfRpHw3heYQC7DPK9FqbQTfBKUJhiSfz4MvXYwg== +dompurify@^2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.4.tgz#1cf5cf0105ccb4debdf6db162525bd41e6ddacc6" + integrity sha512-6BVcgOAVFXjI0JTjEvZy901Rghm+7fDQOrNIcxB4+gdhj6Kwp6T9VBhBY/AbagKHJocRkDYGd6wvI+p4/10xtQ== domutils@1.5.1: version "1.5.1" @@ -5460,11 +5459,47 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" +eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== + dependencies: + debug "^3.2.7" + resolve "^1.20.0" + +eslint-module-utils@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c" + integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ== + dependencies: + debug "^3.2.7" + find-up "^2.1.0" + pkg-dir "^2.0.0" + eslint-plugin-header@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz#6ce512432d57675265fac47292b50d1eff11acd6" integrity sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg== +eslint-plugin-import@^2.25.3: + version "2.25.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz#a554b5f66e08fb4f6dc99221866e57cfff824766" + integrity sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg== + dependencies: + array-includes "^3.1.4" + array.prototype.flat "^1.2.5" + debug "^2.6.9" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.7.1" + has "^1.0.3" + is-core-module "^2.8.0" + is-glob "^4.0.3" + minimatch "^3.0.4" + object.values "^1.1.5" + resolve "^1.20.0" + tsconfig-paths "^3.11.0" + eslint-plugin-react-hooks@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz#318dbf312e06fab1c835a4abef00121751ac1172" @@ -6063,7 +6098,7 @@ find-root@^1.1.0: resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== -find-up@^2.0.0: +find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= @@ -6478,7 +6513,7 @@ glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob-parent@^6.0.1: +glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== @@ -6617,17 +6652,17 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -got@^11.8.0, got@^11.8.2: - version "11.8.2" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" - integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== +got@^11.8.0, got@^11.8.3: + version "11.8.3" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.3.tgz#f496c8fdda5d729a90b4905d2b07dbd148170770" + integrity sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg== dependencies: "@sindresorhus/is" "^4.0.0" "@szmarczak/http-timer" "^4.0.5" "@types/cacheable-request" "^6.0.1" "@types/responselike" "^1.0.0" cacheable-lookup "^5.0.3" - cacheable-request "^7.0.1" + cacheable-request "^7.0.2" decompress-response "^6.0.0" http2-wrapper "^1.0.0-beta.5.2" lowercase-keys "^2.0.0" @@ -6820,11 +6855,6 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -hex-color-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" - integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== - history@^4.10.1, history@^4.9.0: version "4.10.1" resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" @@ -6882,16 +6912,6 @@ hpack.js@^2.1.6: readable-stream "^2.0.1" wbuf "^1.1.0" -hsl-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" - integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= - -hsla-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" - integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= - html-encoding-sniffer@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" @@ -6922,11 +6942,6 @@ html-minifier-terser@^5.0.1: relateurl "^0.2.7" terser "^4.6.3" -html-tags@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" - integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== - html-webpack-plugin@^4.5.2: version "4.5.2" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz#76fc83fa1a0f12dd5f7da0404a54e2699666bc12" @@ -7476,18 +7491,6 @@ is-cidr@^3.0.0: dependencies: cidr-regex "^2.0.10" -is-color-stop@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" - integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= - dependencies: - css-color-names "^0.0.4" - hex-color-regex "^1.1.0" - hsl-regex "^1.0.0" - hsla-regex "^1.0.0" - rgb-regex "^1.0.1" - rgba-regex "^1.0.0" - is-core-module@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" @@ -7495,6 +7498,13 @@ is-core-module@^2.2.0: dependencies: has "^1.0.3" +is-core-module@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" + integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -8686,9 +8696,9 @@ kleur@^3.0.3: integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== klona@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" - integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== + version "2.0.5" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" + integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== kuler@^2.0.0: version "2.0.0" @@ -9059,11 +9069,6 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.topath@^4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009" - integrity sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak= - lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" @@ -9583,11 +9588,6 @@ mock-fs@^5.1.2: resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-5.1.2.tgz#6fa486e06d00f8793a8d2228de980eff93ce6db7" integrity sha512-YkjQkdLulFrz0vD4BfNQdQRVmgycXTV7ykuHMlyv+C8WCHazpkiQRDthwa02kSyo8wKnY9wRptHfQLgmf0eR+A== -modern-normalize@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.1.0.tgz#da8e80140d9221426bd4f725c6e11283d34f90b7" - integrity sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA== - moment-timezone@^0.5.34: version "0.5.34" resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c" @@ -9727,6 +9727,11 @@ neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -9757,13 +9762,6 @@ node-addon-api@^4.2.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.2.0.tgz#117cbb5a959dff0992e1c586ae0393573e4d2a87" integrity sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q== -node-emoji@^1.11.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" - integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== - dependencies: - lodash "^4.17.21" - node-fetch-npm@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz#6507d0e17a9ec0be3bec516958a497cec54bf5a4" @@ -9953,6 +9951,11 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + npm-audit-report@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/npm-audit-report/-/npm-audit-report-1.3.3.tgz#8226deeb253b55176ed147592a3995442f2179ed" @@ -10891,6 +10894,13 @@ pirates@^4.0.1: dependencies: node-modules-regexp "^1.0.0" +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= + dependencies: + find-up "^2.1.0" + pkg-dir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" @@ -11018,7 +11028,7 @@ postcss-load-config@^3.0.1, postcss-load-config@^3.1.0: lilconfig "^2.0.3" yaml "^1.10.2" -postcss-loader@4.3.0: +postcss-loader@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-4.3.0.tgz#2c4de9657cd4f07af5ab42bd60a673004da1b8cc" integrity sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q== @@ -11072,6 +11082,14 @@ postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-selector-parser@^6.0.7: + version "6.0.7" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.7.tgz#48404830a635113a71fd79397de8209ed05a66fc" + integrity sha512-U+b/Deoi4I/UmE6KOVPpnhS7I7AYdKbhGcat+qTQ27gycvaACvNEw11ba6RrkwVmDVRW7sigWgLj4/KbbJjeDA== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss-value-parser@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" @@ -11082,6 +11100,11 @@ postcss-value-parser@^4.1.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== +postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + postcss@^6.0.14, postcss@^6.0.2: version "6.0.23" resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" @@ -11091,7 +11114,7 @@ postcss@^6.0.14, postcss@^6.0.2: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^8.1.6, postcss@^8.2.1, postcss@^8.2.15, postcss@^8.3.0, postcss@^8.4.5: +postcss@^8.1.6, postcss@^8.2.15, postcss@^8.3.0, postcss@^8.4.5: version "8.4.5" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95" integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg== @@ -11100,11 +11123,6 @@ postcss@^8.1.6, postcss@^8.2.1, postcss@^8.2.15, postcss@^8.3.0, postcss@^8.4.5: picocolors "^1.0.0" source-map-js "^1.0.1" -postinstall-postinstall@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" - integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== - prebuild-install@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.0.tgz#3c5ce3902f1cb9d6de5ae94ca53575e4af0c1574" @@ -11172,11 +11190,6 @@ pretty-format@^27.0.2: ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-hrtime@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" - integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= - process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -11365,16 +11378,6 @@ pupa@^2.1.1: dependencies: escape-goat "^2.0.0" -purgecss@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-4.0.3.tgz#8147b429f9c09db719e05d64908ea8b672913742" - integrity sha512-PYOIn5ibRIP34PBU9zohUcCI09c7drPJJtTDAc0Q6QlRz2/CHQ8ywGLdE7ZhxU2VTqB7p5wkvj5Qcm05Rz3Jmw== - dependencies: - commander "^6.0.0" - glob "^7.0.0" - postcss "^8.2.1" - postcss-selector-parser "^6.0.2" - qrcode-terminal@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" @@ -11814,14 +11817,6 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -reduce-css-calc@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03" - integrity sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg== - dependencies: - css-unit-converter "^1.1.1" - postcss-value-parser "^3.3.0" - redux@^4.0.0, redux@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" @@ -12113,16 +12108,6 @@ rfc6902@^4.0.2: resolved "https://registry.yarnpkg.com/rfc6902/-/rfc6902-4.0.2.tgz#ce99d3562b9e3287d403462e6bcc81eead8fcea0" integrity sha512-MJOC4iDSv3Qn5/QvhPbrNoRongti6moXSShcRmtbNqOk0WPxlviEdMV4bb9PaULhSxLUXzWd4AjAMKQ3j3y54w== -rgb-regex@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" - integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= - -rgba-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" - integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= - rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -12225,18 +12210,18 @@ sanitize-filename@^1.6.2, sanitize-filename@^1.6.3: dependencies: truncate-utf8-bytes "^1.0.0" -sass-loader@^8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d" - integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ== +sass-loader@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.2.0.tgz#3d64c1590f911013b3fa48a0b22a83d5e1494716" + integrity sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw== dependencies: - clone-deep "^4.0.1" - loader-utils "^1.2.3" - neo-async "^2.6.1" - schema-utils "^2.6.1" - semver "^6.3.0" + klona "^2.0.4" + loader-utils "^2.0.0" + neo-async "^2.6.2" + schema-utils "^3.0.0" + semver "^7.3.2" -sass@^1.32.13, sass@^1.44.0: +sass@^1.32.13: version "1.44.0" resolved "https://registry.yarnpkg.com/sass/-/sass-1.44.0.tgz#619aa0a2275c097f9af5e6b8fe8a95e3056430fb" integrity sha512-0hLREbHFXGQqls/K8X+koeP+ogFRPF4ZqetVB19b7Cst9Er8cOR0rc6RU7MaI4W1JmUShd1BPgPoeqmmgMMYFw== @@ -12244,6 +12229,15 @@ sass@^1.32.13, sass@^1.44.0: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" +sass@^1.45.1: + version "1.45.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.45.1.tgz#fa03951f924d1ba5762949567eaf660e608a1ab0" + integrity sha512-pwPRiq29UR0o4X3fiQyCtrESldXvUQAAE0QmcJTpsI4kuHHcLzZ54M1oNBVIXybQv8QF2zfkpFcTxp8ta97dUA== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -12282,7 +12276,7 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -schema-utils@^2.6.1, schema-utils@^2.6.5: +schema-utils@^2.6.5: version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== @@ -12454,13 +12448,6 @@ sha@^3.0.0: dependencies: graceful-fs "^4.1.2" -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - sharp@^0.29.3: version "0.29.3" resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.3.tgz#0da183d626094c974516a48fab9b3e4ba92eb5c2" @@ -12704,7 +12691,7 @@ source-list-map@^2.0.0, source-list-map@^2.0.1: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-js@^1.0.1: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf" integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA== @@ -13259,41 +13246,30 @@ table@^6.0.9: string-width "^4.2.3" strip-ansi "^6.0.1" -tailwindcss@^2.2.19: - version "2.2.19" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.2.19.tgz#540e464832cd462bb9649c1484b0a38315c2653c" - integrity sha512-6Ui7JSVtXadtTUo2NtkBBacobzWiQYVjYW0ZnKaP9S1ZCKQ0w7KVNz+YSDI/j7O7KCMHbOkz94ZMQhbT9pOqjw== +tailwindcss@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.7.tgz#15936881f042a7eb8d6f2b6a454bac9f51181bbd" + integrity sha512-rZdKNHtC64jcQncLoWOuCzj4lQDTAgLtgK3WmQS88tTdpHh9OwLqULTQxI3tw9AMJsqSpCKlmcjW/8CSnni6zQ== dependencies: arg "^5.0.1" - bytes "^3.0.0" chalk "^4.1.2" chokidar "^3.5.2" - color "^4.0.1" + color-name "^1.1.4" cosmiconfig "^7.0.1" detective "^5.2.0" didyoumean "^1.2.2" dlv "^1.1.3" fast-glob "^3.2.7" - fs-extra "^10.0.0" - glob-parent "^6.0.1" - html-tags "^3.1.0" - is-color-stop "^1.1.0" - is-glob "^4.0.1" - lodash "^4.17.21" - lodash.topath "^4.5.2" - modern-normalize "^1.1.0" - node-emoji "^1.11.0" + glob-parent "^6.0.2" + is-glob "^4.0.3" normalize-path "^3.0.0" object-hash "^2.2.0" postcss-js "^3.0.3" postcss-load-config "^3.1.0" postcss-nested "5.0.6" - postcss-selector-parser "^6.0.6" - postcss-value-parser "^4.1.0" - pretty-hrtime "^1.0.3" - purgecss "^4.0.3" + postcss-selector-parser "^6.0.7" + postcss-value-parser "^4.2.0" quick-lru "^5.1.1" - reduce-css-calc "^2.1.8" resolve "^1.20.0" tmp "^0.2.1" @@ -13629,9 +13605,9 @@ truncate-utf8-bytes@^1.0.0: utf8-byte-length "^1.0.1" ts-essentials@^7.0.2: - version "7.0.3" - resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38" - integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ== + version "7.0.2" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.2.tgz#e21142df8034dbd444cb9573ed204d0b85fc64fb" + integrity sha512-qWPVC1xZGdefbsgFP7tPo+bsgSA2ZIXL1XeEe5M2WoMZxIOr/HbsHxP/Iv75IFhiMHMDGL7cOOwi5SXcgx9mHw== ts-jest@26.5.6: version "26.5.6" @@ -13678,6 +13654,16 @@ ts-node@^10.4.0: make-error "^1.1.1" yn "3.1.1" +tsconfig-paths@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" + integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + tsconfig-paths@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" @@ -13776,7 +13762,12 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-fest@^1.0.2, type-fest@^1.4.0: +type-fest@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.0.2.tgz#3f9c39982859f385c77c38b7e5f1432b8a3661c6" + integrity sha512-a720oz3Kjbp3ll0zkeN9qjRhO7I34MKMhPGQiQJAmaZQZQ1lo+NWThK322f7sXV+kTg9B1Ybt16KgBXWgteT8w== + +type-fest@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==