diff --git a/Makefile b/Makefile index 6763e7e42a..3f77546354 100644 --- a/Makefile +++ b/Makefile @@ -103,8 +103,12 @@ publish-npm: node_modules build-npm cd src/extensions/npm/extensions && npm publish --access=public --tag=$(NPM_RELEASE_TAG) git restore src/extensions/npm/extensions/package.json +.PHONY: build-docs +build-docs: + yarn typedocs-extensions-api + .PHONY: docs -docs: +docs: build-docs yarn mkdocs-serve-local .PHONY: clean-extensions diff --git a/docs/extensions/guides/README.md b/docs/extensions/guides/README.md index 09a0bce0a1..cca497a95d 100644 --- a/docs/extensions/guides/README.md +++ b/docs/extensions/guides/README.md @@ -14,26 +14,28 @@ Each guide or code sample includes the following: ## Guides -| Guide | APIs | -| ----- | ----- | -| [Generate new extension project](generator.md) || -| [Main process extension](main-extension.md) | Main.LensExtension | -| [Renderer process extension](renderer-extension.md) | Renderer.LensExtension | -| [Resource stack (cluster feature)](resource-stack.md) | | -| [Extending KubernetesCluster)](extending-kubernetes-cluster.md) | | -| [Stores](stores.md) | | -| [Components](components.md) | | -| [KubeObjectListLayout](kube-object-list-layout.md) | | -| [Working with mobx](working-with-mobx.md) | | -| [Protocol Handlers](protocol-handlers.md) | | -| [Sending Data between main and renderer](ipc.md) | | +| Guide | APIs | +| ------------------------------------------------------------------ | ---------------------- | +| [Generate new extension project](generator.md) | | +| [Main process extension](main-extension.md) | Main.LensExtension | +| [Renderer process extension](renderer-extension.md) | Renderer.LensExtension | +| [Resource stack (cluster feature)](resource-stack.md) | | +| [Extending KubernetesCluster)](extending-kubernetes-cluster.md) | | +| [Stores](stores.md) | | +| [Components](components.md) | | +| [KubeObjectListLayout](kube-object-list-layout.md) | | +| [Working with mobx](working-with-mobx.md) | | +| [Protocol Handlers](protocol-handlers.md) | | +| [Sending Data between main and renderer](ipc.md) | | +| [Catalog Entities and Categories](catalog.md) | | +| [Adding Custom Columns for Categories](custom-category-columns.md) | | ## Samples -| Sample | APIs | -| ----- | ----- | -[hello-world](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension
LensRendererExtension
Renderer.Component.Icon
Renderer.Component.IconProps | -[styling-css-modules-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample) | LensMainExtension
LensRendererExtension
Renderer.Component.Icon
Renderer.Component.IconProps | -[styling-emotion-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) | LensMainExtension
LensRendererExtension
Renderer.Component.Icon
Renderer.Component.IconProps | -[styling-sass-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-sass-sample) | LensMainExtension
LensRendererExtension
Renderer.Component.Icon
Renderer.Component.IconProps | -[custom-resource-page](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page) | LensRendererExtension
Renderer.K8sApi.KubeApi
Renderer.K8sApi.KubeObjectStore
Renderer.Component.KubeObjectListLayout
Renderer.Component.KubeObjectDetailsProps
Renderer.Component.IconProps | +| Sample | APIs | +| ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [hello-world](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension
LensRendererExtension
Renderer.Component.Icon
Renderer.Component.IconProps | +| [styling-css-modules-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample) | LensMainExtension
LensRendererExtension
Renderer.Component.Icon
Renderer.Component.IconProps | +| [styling-emotion-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) | LensMainExtension
LensRendererExtension
Renderer.Component.Icon
Renderer.Component.IconProps | +| [styling-sass-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-sass-sample) | LensMainExtension
LensRendererExtension
Renderer.Component.Icon
Renderer.Component.IconProps | +| [custom-resource-page](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page) | LensRendererExtension
Renderer.K8sApi.KubeApi
Renderer.K8sApi.KubeObjectStore
Renderer.Component.KubeObjectListLayout
Renderer.Component.KubeObjectDetailsProps
Renderer.Component.IconProps | diff --git a/docs/extensions/guides/catalog.md b/docs/extensions/guides/catalog.md index 5425382638..74da8b0124 100644 --- a/docs/extensions/guides/catalog.md +++ b/docs/extensions/guides/catalog.md @@ -1,5 +1,27 @@ # Catalog (WIP) -## CatalogCategoryRegistry +This guide is a brief overview about how the catalog works within Lens. +The catalog should be thought of as the single source of truth about data within Lens. -## CatalogEntityRegistry \ No newline at end of file +The data flow is unidirectional, it only flows from the main side to the renderer side. +All data is public within the catalog. + +## Categories + +A cateogry is the declaration to the catalog of a specific kind of entity. +It declares the currently supported versions of that kind of entity but providing the constructors for the entity classes. + +To declare a new category class you must create a new class that extends [Common.Catalog.CatalogCategory](../api/classes/Common.Catalog.CatalogCategory.md) and implement all of the abstract fields. + +The categories provided by Lens itself have the following names: + +- `KubernetesClusters` +- `WebLinks` +- `General` + +To register a category, call the `Main.Catalog.catalogCategories.add()` and `Renderer.Catalog.catalogCategories.add()` with instances of your class. + +## Entities + +An entity is the data within the catalog. +All entities are typed and the class instances will be recreated on the renderer side by the catalog and the category registrations. diff --git a/docs/extensions/guides/custom-category-columns.md b/docs/extensions/guides/custom-category-columns.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/extensions/guides/main-extension.md b/docs/extensions/guides/main-extension.md index a0e20880bf..d05368529c 100644 --- a/docs/extensions/guides/main-extension.md +++ b/docs/extensions/guides/main-extension.md @@ -16,11 +16,11 @@ import { Main } from "@k8slens/extensions"; export default class ExampleExtensionMain extends Main.LensExtension { onActivate() { - console.log('custom main process extension code started'); + console.log("custom main process extension code started"); } onDeactivate() { - console.log('custom main process extension de-activated'); + console.log("custom main process extension de-activated"); } } ``` @@ -33,21 +33,21 @@ Implementing `onDeactivate()` gives you the opportunity to clean up after your e Disable extensions from the Lens Extensions page: 1. Navigate to **File** > **Extensions** in the top menu bar. -(On Mac, it is **Lens** > **Extensions**.) + (On Mac, it is **Lens** > **Extensions**.) 2. Click **Disable** on the extension you want to disable. The example above logs messages when the extension is enabled and disabled. To see standard output from the main process there must be a console connected to it. Achieve this by starting Lens from the command prompt. -For more details on accessing Lens state data, please see the [Stores](../stores) guide. +For more details on accessing Lens state data, please see the [Stores](stores.md) guide. ### `appMenus` The Main Extension API allows you to customize the UI application menu. The following example demonstrates adding an item to the **Help** menu. -``` typescript +```typescript import { Main } from "@k8slens/extensions"; export default class SamplePageMainExtension extends Main.LensExtension { @@ -57,9 +57,9 @@ export default class SamplePageMainExtension extends Main.LensExtension { label: "Sample", click() { console.log("Sample clicked"); - } - } - ] + }, + }, + ]; } ``` @@ -67,18 +67,18 @@ export default class SamplePageMainExtension extends Main.LensExtension { `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. -Valid values include: `"file"`, `"edit"`, `"view"`, and `"help"`. -`"lens"` is valid on Mac only. -* `label` is the name of your menu item. -* `click()` is called when the menu item is selected. -In this example, we simply log a message. -However, you would typically have this navigate to a specific page or perform another operation. -Note that pages are associated with the [`Renderer.LensExtension`](renderer-extension.md) class and can be defined in the process of extending it. +- `parentId` is the name of the menu where your new menu item will be listed. + Valid values include: `"file"`, `"edit"`, `"view"`, and `"help"`. + `"lens"` is valid on Mac only. +- `label` is the name of your menu item. +- `click()` is called when the menu item is selected. + In this example, we simply log a message. + However, you would typically have this navigate to a specific page or perform another operation. + Note that pages are associated with the [`Renderer.LensExtension`](renderer-extension.md) class and can be defined in the process of extending it. The following example demonstrates how an application menu can be used to navigate to such a page: -``` typescript +```typescript import { Main } from "@k8slens/extensions"; export default class SamplePageMainExtension extends Main.LensExtension { @@ -86,9 +86,9 @@ export default class SamplePageMainExtension extends Main.LensExtension { { parentId: "help", label: "Sample", - click: () => this.navigate("myGlobalPage") - } - ] + click: () => this.navigate("myGlobalPage"), + }, + ]; } ``` @@ -99,32 +99,36 @@ This page would be defined in your extension's `Renderer.LensExtension` implemen `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 +```typescript interface TrayMenuRegistration { label?: string; click?: (menuItem: TrayMenuRegistration) => void; id?: string; - type?: "normal" | "separator" | "submenu" + type?: "normal" | "separator" | "submenu"; toolTip?: string; enabled?: boolean; - submenu?: TrayMenuRegistration[] + submenu?: TrayMenuRegistration[]; } ``` The following example demonstrates how tray menus can be added from extension: -``` typescript +```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!") } - }] + 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). -See the [`Catalog`](catalog.md) documentation for full details about the catalog. \ No newline at end of file +See the [`Catalog`](catalog.md) documentation for full details about the catalog. diff --git a/src/common/catalog/catalog-entity.ts b/src/common/catalog/catalog-entity.ts index feb13ed51b..46ce8228ff 100644 --- a/src/common/catalog/catalog-entity.ts +++ b/src/common/catalog/catalog-entity.ts @@ -84,35 +84,91 @@ export interface CatalogCategorySpec { } /** - * If the filter returns true, the menu item is displayed + * If the filter return a thruthy value, the menu item is displayed */ export type AddMenuFilter = (menu: CatalogEntityAddMenu) => any; export interface CatalogCategoryEvents { + /** + * This event will be emitted when the category is loaded in the catalog + * view. + */ load: () => void; + + /** + * This event will be emitted when the catalog add menu is opened and is the + * way to added entries to that menu. + */ catalogAddMenu: (context: CatalogEntityAddMenuContext) => void; + + /** + * This event will be emitted when the context menu for an entity is declared + * by this category is opened. + */ contextMenuOpen: (entity: CatalogEntity, context: CatalogEntityContextMenuContext) => void; } export abstract class CatalogCategory extends (EventEmitter as new () => TypedEmitter) { + /** + * The version of category that you are wanting to declare. + * + * Currently supported values: + * + * - `"catalog.k8slens.dev/v1alpha1"` + */ abstract readonly apiVersion: string; + + /** + * The kind of item you wish to declare. + * + * Currently supported values: + * + * - `"CatalogCategory"` + */ abstract readonly kind: string; - abstract metadata: { + + /** + * The data about the category itself + */ + abstract readonly metadata: { + /** + * The name of your category. The category can be searched for by this + * value. This will also be used for the catalog menu. + */ name: string; + + /** + * Either an `` or the name of an icon from {@link IconProps} + */ icon: string; }; + + /** + * The most important part of a category, as it is where entity versions are declared. + */ abstract spec: CatalogCategorySpec; + /** + * @internal + */ protected filters = observable.set([], { deep: false, }); - static parseId(id = ""): { group?: string, kind?: string } { + /** + * Parse a category ID into parts. + * @param id The id of a category is parse + * @returns The group and kind parts of the ID + */ + public static parseId(id: string): { group?: string, kind?: string } { const [group, kind] = id.split("/") ?? []; return { group, kind }; } + /** + * Get the ID of this category + */ public getId(): string { return `${this.spec.group}/${this.spec.names.kind}`; } diff --git a/src/common/utils/collection-functions.ts b/src/common/utils/collection-functions.ts index be796876b6..205f7f0cb1 100644 --- a/src/common/utils/collection-functions.ts +++ b/src/common/utils/collection-functions.ts @@ -4,16 +4,16 @@ */ /** - * Get the value behind `key`. If it was not pressent, first insert `value` + * Get the value behind `key`. If it was not present, first insert `value` * @param map The map to interact with * @param key The key to insert into the map with * @param value The value to optional add to the map * @returns The value in the map */ export function getOrInsert(map: Map, key: K, value: V): V { - if (map.has(key)) { - return map.get(key); + if (!map.has(key)) { + map.set(key, value); } - return map.set(key, value).get(key); + return map.get(key); } diff --git a/src/extensions/renderer-api/components.ts b/src/extensions/renderer-api/components.ts index 3181e7144a..79372c71dd 100644 --- a/src/extensions/renderer-api/components.ts +++ b/src/extensions/renderer-api/components.ts @@ -31,6 +31,11 @@ export * from "../../renderer/components/input/input"; // command-overlay export const CommandOverlay = asLegacyGlobalObjectForExtensionApi(commandOverlayInjectable); +export type { + CategoryColumnRegistration, + AdditionalCategoryColumnRegistration, +} from "../../renderer/components/+catalog/custom-category-columns"; + // other components export * from "../../renderer/components/icon"; export * from "../../renderer/components/tooltip"; diff --git a/src/renderer/components/+catalog/__tests__/custom-columns.test.ts b/src/renderer/components/+catalog/__tests__/custom-columns.test.ts index f7111a7183..00fd5e033b 100644 --- a/src/renderer/components/+catalog/__tests__/custom-columns.test.ts +++ b/src/renderer/components/+catalog/__tests__/custom-columns.test.ts @@ -14,9 +14,12 @@ import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } import getCategoryColumnsInjectable, { CategoryColumns, GetCategoryColumnsParams } from "../get-category-columns.injectable"; class TestCategory extends CatalogCategory { - apiVersion: string; - kind: string; - metadata: { name: string; icon: string; }; + apiVersion = "catalog.k8slens.dev/v1alpha1"; + kind = "CatalogCategory"; + metadata: { + name: "Test"; + icon: "question_mark"; + }; spec: CatalogCategorySpec = { group: "foo.bar.bat", names: { diff --git a/src/renderer/components/markdown-viewer/markdown-viewer.tsx b/src/renderer/components/markdown-viewer/markdown-viewer.tsx index ca131c7cbb..c105d8411a 100644 --- a/src/renderer/components/markdown-viewer/markdown-viewer.tsx +++ b/src/renderer/components/markdown-viewer/markdown-viewer.tsx @@ -8,7 +8,7 @@ import "./markdown-viewer.scss"; import React, { Component } from "react"; -import { marked } from "marked"; +import marked from "marked"; import DOMPurify from "dompurify"; import { cssNames } from "../../utils";