mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into turn-on-strict
This commit is contained in:
commit
d972ca2aa5
@ -293,11 +293,22 @@ export interface CatalogEntitySettingsMenu {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CatalogEntityContextMenuNavigate {
|
||||||
|
/**
|
||||||
|
* @param pathname The location to navigate to in the main iframe
|
||||||
|
*/
|
||||||
|
(pathname: string, forceMainFrame?: boolean): void;
|
||||||
|
/**
|
||||||
|
* @param pathname The location to navigate to in the current iframe. Useful for when called within the cluster frame
|
||||||
|
*/
|
||||||
|
(pathname: string, forceMainFrame: false): void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CatalogEntityContextMenuContext {
|
export interface CatalogEntityContextMenuContext {
|
||||||
/**
|
/**
|
||||||
* Navigate to the specified pathname
|
* Navigate to the specified pathname
|
||||||
*/
|
*/
|
||||||
navigate: (pathname: string) => void;
|
navigate: CatalogEntityContextMenuNavigate;
|
||||||
menuItems: CatalogEntityContextMenu[];
|
menuItems: CatalogEntityContextMenu[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@ export * from "./n-fircate";
|
|||||||
export * from "./openBrowser";
|
export * from "./openBrowser";
|
||||||
export * from "./paths";
|
export * from "./paths";
|
||||||
export * from "./promise-exec";
|
export * from "./promise-exec";
|
||||||
|
export * from "./readonly";
|
||||||
export * from "./reject-promise";
|
export * from "./reject-promise";
|
||||||
export * from "./singleton";
|
export * from "./singleton";
|
||||||
export * from "./sort-compare";
|
export * from "./sort-compare";
|
||||||
|
|||||||
@ -133,6 +133,10 @@ export function bindPredicate<FnArgs extends any[], T>(fn: (arg1: unknown, ...ar
|
|||||||
return (arg1: unknown): arg1 is T => fn(arg1, ...boundArgs);
|
return (arg1: unknown): arg1 is T => fn(arg1, ...boundArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hasDefiniteField<Field extends keyof T, T>(field: Field): (val: T) => val is T & { [f in Field]-?: NonNullable<T[Field]> } {
|
||||||
|
return (val): val is T & { [f in Field]-?: NonNullable<T[Field]> } => val[field] != null;
|
||||||
|
}
|
||||||
|
|
||||||
export function isErrnoException(error: unknown): error is NodeJS.ErrnoException {
|
export function isErrnoException(error: unknown): error is NodeJS.ErrnoException {
|
||||||
return isObject(error)
|
return isObject(error)
|
||||||
&& hasOptionalTypedProperty(error, "code", isString)
|
&& hasOptionalTypedProperty(error, "code", isString)
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
export type { StatusBarRegistration } from "../../renderer/components/status-bar/status-bar-registration";
|
export type { StatusBarRegistration } from "../../renderer/components/status-bar/status-bar-registration";
|
||||||
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration";
|
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../../renderer/components/kube-object-menu/kube-object-menu-registration";
|
||||||
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../renderer/components/+preferences/app-preferences/app-preference-registration";
|
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../renderer/components/+preferences/app-preferences/app-preference-registration";
|
||||||
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry";
|
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry";
|
||||||
export type { KubeObjectStatusRegistration } from "../../renderer/components/kube-object-status-icon/kube-object-status-registration";
|
export type { KubeObjectStatusRegistration } from "../../renderer/components/kube-object-status-icon/kube-object-status-registration";
|
||||||
@ -12,3 +12,4 @@ export type { ClusterPageMenuRegistration, ClusterPageMenuComponents } from "../
|
|||||||
export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../registries/protocol-handler";
|
export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../registries/protocol-handler";
|
||||||
export type { CustomCategoryViewProps, CustomCategoryViewComponents, CustomCategoryViewRegistration } from "../../renderer/components/+catalog/custom-views";
|
export type { CustomCategoryViewProps, CustomCategoryViewComponents, CustomCategoryViewRegistration } from "../../renderer/components/+catalog/custom-views";
|
||||||
export type { ShellEnvModifier, ShellEnvContext } from "../../main/shell-session/shell-env-modifier/shell-env-modifier-registration";
|
export type { ShellEnvModifier, ShellEnvContext } from "../../main/shell-session/shell-env-modifier/shell-env-modifier-registration";
|
||||||
|
export type { KubeObjectContextMenuItem, KubeObjectOnContextMenuOpenContext, KubeObjectOnContextMenuOpen, KubeObjectHandlers, KubeObjectHandlerRegistration } from "../../renderer/kube-object/handler";
|
||||||
|
|||||||
@ -18,13 +18,14 @@ import type { AppPreferenceRegistration } from "../renderer/components/+preferen
|
|||||||
import type { AdditionalCategoryColumnRegistration } from "../renderer/components/+catalog/custom-category-columns";
|
import type { AdditionalCategoryColumnRegistration } from "../renderer/components/+catalog/custom-category-columns";
|
||||||
import type { CustomCategoryViewRegistration } from "../renderer/components/+catalog/custom-views";
|
import type { CustomCategoryViewRegistration } from "../renderer/components/+catalog/custom-views";
|
||||||
import type { StatusBarRegistration } from "../renderer/components/status-bar/status-bar-registration";
|
import type { StatusBarRegistration } from "../renderer/components/status-bar/status-bar-registration";
|
||||||
import type { KubeObjectMenuRegistration } from "../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration";
|
import type { KubeObjectMenuRegistration } from "../renderer/components/kube-object-menu/kube-object-menu-registration";
|
||||||
import type { WorkloadsOverviewDetailRegistration } from "../renderer/components/+workloads-overview/workloads-overview-detail-registration";
|
import type { WorkloadsOverviewDetailRegistration } from "../renderer/components/+workloads-overview/workloads-overview-detail-registration";
|
||||||
import type { KubeObjectStatusRegistration } from "../renderer/components/kube-object-status-icon/kube-object-status-registration";
|
import type { KubeObjectStatusRegistration } from "../renderer/components/kube-object-status-icon/kube-object-status-registration";
|
||||||
import { fromPairs, map, matches, toPairs } from "lodash/fp";
|
import { fromPairs, map, matches, toPairs } from "lodash/fp";
|
||||||
import { pipeline } from "@ogre-tools/fp";
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
import { getExtensionRoutePath } from "../renderer/routes/for-extension";
|
import { getExtensionRoutePath } from "../renderer/routes/for-extension";
|
||||||
import type { LensRendererExtensionDependencies } from "./lens-extension-set-dependencies";
|
import type { LensRendererExtensionDependencies } from "./lens-extension-set-dependencies";
|
||||||
|
import type { KubeObjectHandlerRegistration } from "../renderer/kube-object/handler";
|
||||||
|
|
||||||
export class LensRendererExtension extends LensExtension<LensRendererExtensionDependencies> {
|
export class LensRendererExtension extends LensExtension<LensRendererExtensionDependencies> {
|
||||||
globalPages: registries.PageRegistration[] = [];
|
globalPages: registries.PageRegistration[] = [];
|
||||||
@ -44,6 +45,7 @@ export class LensRendererExtension extends LensExtension<LensRendererExtensionDe
|
|||||||
topBarItems: TopBarRegistration[] = [];
|
topBarItems: TopBarRegistration[] = [];
|
||||||
additionalCategoryColumns: AdditionalCategoryColumnRegistration[] = [];
|
additionalCategoryColumns: AdditionalCategoryColumnRegistration[] = [];
|
||||||
customCategoryViews: CustomCategoryViewRegistration[] = [];
|
customCategoryViews: CustomCategoryViewRegistration[] = [];
|
||||||
|
kubeObjectHandlers: KubeObjectHandlerRegistration[] = [];
|
||||||
|
|
||||||
async navigate(pageId?: string, params: object = {}) {
|
async navigate(pageId?: string, params: object = {}) {
|
||||||
const routes = this[lensExtensionDependencies].routes.get();
|
const routes = this[lensExtensionDependencies].routes.get();
|
||||||
|
|||||||
@ -15,6 +15,10 @@ import sendCommandInjectable from "../../renderer/components/dock/terminal/send-
|
|||||||
import renameTabInjectable from "../../renderer/components/dock/dock/rename-tab.injectable";
|
import renameTabInjectable from "../../renderer/components/dock/dock/rename-tab.injectable";
|
||||||
import { asLegacyGlobalObjectForExtensionApiWithModifications } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications";
|
import { asLegacyGlobalObjectForExtensionApiWithModifications } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications";
|
||||||
import { podStore } from "../../renderer/components/+workloads-pods/legacy-store";
|
import { podStore } from "../../renderer/components/+workloads-pods/legacy-store";
|
||||||
|
import { ConfirmDialog as _ConfirmDialog } from "../../renderer/components/confirm-dialog";
|
||||||
|
import type { ConfirmDialogBooleanParams, ConfirmDialogParams, ConfirmDialogProps } from "../../renderer/components/confirm-dialog";
|
||||||
|
import openConfirmDialogInjectable from "../../renderer/components/confirm-dialog/open.injectable";
|
||||||
|
import confirmInjectable from "../../renderer/components/confirm-dialog/confirm.injectable";
|
||||||
|
|
||||||
// layouts
|
// layouts
|
||||||
export * from "../../renderer/components/layout/main-layout";
|
export * from "../../renderer/components/layout/main-layout";
|
||||||
@ -41,6 +45,16 @@ export type {
|
|||||||
} from "../../renderer/components/+catalog/custom-category-columns";
|
} from "../../renderer/components/+catalog/custom-category-columns";
|
||||||
|
|
||||||
// other components
|
// other components
|
||||||
|
export type {
|
||||||
|
ConfirmDialogBooleanParams,
|
||||||
|
ConfirmDialogParams,
|
||||||
|
ConfirmDialogProps,
|
||||||
|
};
|
||||||
|
export const ConfirmDialog = Object.assign(_ConfirmDialog, {
|
||||||
|
open: asLegacyGlobalFunctionForExtensionApi(openConfirmDialogInjectable),
|
||||||
|
confirm: asLegacyGlobalFunctionForExtensionApi(confirmInjectable),
|
||||||
|
});
|
||||||
|
|
||||||
export * from "../../renderer/components/icon";
|
export * from "../../renderer/components/icon";
|
||||||
export * from "../../renderer/components/tooltip";
|
export * from "../../renderer/components/tooltip";
|
||||||
export * from "../../renderer/components/tabs";
|
export * from "../../renderer/components/tabs";
|
||||||
@ -48,7 +62,6 @@ export * from "../../renderer/components/table";
|
|||||||
export * from "../../renderer/components/badge";
|
export * from "../../renderer/components/badge";
|
||||||
export * from "../../renderer/components/drawer";
|
export * from "../../renderer/components/drawer";
|
||||||
export * from "../../renderer/components/dialog";
|
export * from "../../renderer/components/dialog";
|
||||||
export * from "../../renderer/components/confirm-dialog";
|
|
||||||
export * from "../../renderer/components/line-progress";
|
export * from "../../renderer/components/line-progress";
|
||||||
export * from "../../renderer/components/menu";
|
export * from "../../renderer/components/menu";
|
||||||
export * from "../../renderer/components/notifications";
|
export * from "../../renderer/components/notifications";
|
||||||
|
|||||||
43
src/renderer/catalog/normalize-menu-item.injectable.ts
Normal file
43
src/renderer/catalog/normalize-menu-item.injectable.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { CatalogEntityContextMenu } from "../api/catalog-entity";
|
||||||
|
import withConfirmationInjectable from "../components/confirm-dialog/with-confirm.injectable";
|
||||||
|
|
||||||
|
export interface NormalizedCatalogEntityContextMenu {
|
||||||
|
title: string;
|
||||||
|
icon?: string;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NormalizeCatalogEntityContextMenu = (menuItem: CatalogEntityContextMenu) => NormalizedCatalogEntityContextMenu;
|
||||||
|
|
||||||
|
const normalizeCatalogEntityContextMenuInjectable = getInjectable({
|
||||||
|
id: "normalize-catalog-entity-context-menu",
|
||||||
|
instantiate: (di): NormalizeCatalogEntityContextMenu => {
|
||||||
|
const withConfirmation = di.inject(withConfirmationInjectable);
|
||||||
|
|
||||||
|
return (menuItem) => {
|
||||||
|
if (menuItem.confirm) {
|
||||||
|
return {
|
||||||
|
title: menuItem.title,
|
||||||
|
icon: menuItem.icon,
|
||||||
|
onClick: withConfirmation({
|
||||||
|
message: menuItem.confirm.message,
|
||||||
|
ok: menuItem.onClick,
|
||||||
|
okButtonProps: {
|
||||||
|
primary: false,
|
||||||
|
accent: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return menuItem;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default normalizeCatalogEntityContextMenuInjectable;
|
||||||
@ -4,81 +4,61 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames, hasDefiniteField } from "../../utils";
|
||||||
import type { MenuActionsProps } from "../menu/menu-actions";
|
import type { MenuActionsProps } from "../menu/menu-actions";
|
||||||
import { MenuActions } from "../menu/menu-actions";
|
import { MenuActions } from "../menu/menu-actions";
|
||||||
import type { CatalogEntity, CatalogEntityContextMenu } from "../../api/catalog-entity";
|
import type { CatalogEntity, CatalogEntityContextMenu } from "../../api/catalog-entity";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import { navigate } from "../../navigation";
|
|
||||||
import { MenuItem } from "../menu";
|
import { MenuItem } from "../menu";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
|
import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
|
||||||
import type { OnContextMenuOpen } from "../../../common/catalog/on-context-menu-open.injectable";
|
import type { OnContextMenuOpen } from "../../../common/catalog/on-context-menu-open.injectable";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
|
||||||
import onContextMenuOpenInjectable from "../../../common/catalog/on-context-menu-open.injectable";
|
import onContextMenuOpenInjectable from "../../../common/catalog/on-context-menu-open.injectable";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import type { Navigate } from "../../navigation/navigate.injectable";
|
||||||
|
import navigateInjectable from "../../navigation/navigate.injectable";
|
||||||
|
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
|
||||||
|
import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable";
|
||||||
|
|
||||||
export interface CatalogEntityDrawerMenuProps<Entity extends CatalogEntity> extends MenuActionsProps {
|
export interface CatalogEntityDrawerMenuProps<Entity extends CatalogEntity> extends MenuActionsProps {
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
|
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
|
||||||
|
navigate: Navigate;
|
||||||
onContextMenuOpen: OnContextMenuOpen;
|
onContextMenuOpen: OnContextMenuOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class NonInjectedCatalogEntityDrawerMenu<Entity extends CatalogEntity> extends React.Component<Dependencies & CatalogEntityDrawerMenuProps<Entity>> {
|
class NonInjectedCatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Component<CatalogEntityDrawerMenuProps<T> & Dependencies> {
|
||||||
private readonly menuItems = observable.array<CatalogEntityContextMenu>();
|
private readonly menuItems = observable.array<CatalogEntityContextMenu>();
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.onContextMenuOpen(this.props.entity, {
|
this.props.onContextMenuOpen(this.props.entity, {
|
||||||
menuItems: this.menuItems,
|
menuItems: this.menuItems,
|
||||||
navigate: (url) => navigate(url),
|
navigate: this.props.navigate,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
getMenuItems(entity: T): React.ReactChild[] {
|
||||||
if (menuItem.confirm) {
|
|
||||||
ConfirmDialog.open({
|
|
||||||
okButtonProps: {
|
|
||||||
primary: false,
|
|
||||||
accent: true,
|
|
||||||
},
|
|
||||||
ok: () => {
|
|
||||||
menuItem.onClick();
|
|
||||||
},
|
|
||||||
message: menuItem.confirm.message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
menuItem.onClick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getMenuItems(entity: Entity): React.ReactChild[] {
|
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const items: React.ReactChild[] = [];
|
const items = this.menuItems
|
||||||
|
.map(this.props.normalizeMenuItem)
|
||||||
for (const menuItem of this.menuItems) {
|
.filter(hasDefiniteField("icon"))
|
||||||
if (!menuItem.icon) {
|
.map(menuItem => (
|
||||||
continue;
|
<MenuItem key={menuItem.title} onClick={menuItem.onClick}>
|
||||||
}
|
|
||||||
|
|
||||||
const key = Icon.isSvg(menuItem.icon) ? "svg" : "material";
|
|
||||||
|
|
||||||
items.push(
|
|
||||||
<MenuItem key={menuItem.title} onClick={() => this.onMenuItemClick(menuItem)}>
|
|
||||||
<Icon
|
<Icon
|
||||||
interactive
|
interactive
|
||||||
tooltip={menuItem.title}
|
tooltip={menuItem.title}
|
||||||
{...{ [key]: menuItem.icon }}
|
{...{ [Icon.isSvg(menuItem.icon) ? "svg" : "material"]: menuItem.icon }}
|
||||||
/>
|
/>
|
||||||
</MenuItem>,
|
</MenuItem>
|
||||||
);
|
));
|
||||||
}
|
|
||||||
|
|
||||||
items.push(
|
items.push(
|
||||||
<HotbarToggleMenuItem
|
<HotbarToggleMenuItem
|
||||||
@ -129,5 +109,7 @@ export const CatalogEntityDrawerMenu = withInjectables<Dependencies, CatalogEnti
|
|||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
...props,
|
...props,
|
||||||
onContextMenuOpen: di.inject(onContextMenuOpenInjectable),
|
onContextMenuOpen: di.inject(onContextMenuOpenInjectable),
|
||||||
|
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
|
||||||
|
navigate: di.inject(navigateInjectable),
|
||||||
}),
|
}),
|
||||||
}) as <Entity extends CatalogEntity>(props: CatalogEntityDrawerMenuProps<Entity>) => JSX.Element;
|
}) as <Entity extends CatalogEntity>(props: CatalogEntityDrawerMenuProps<Entity>) => JSX.Element;
|
||||||
|
|||||||
@ -11,10 +11,8 @@ import { ItemListLayout } from "../item-object-list";
|
|||||||
import type { IComputedValue } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
import { action, computed, makeObservable, observable, reaction, runInAction, when } from "mobx";
|
import { action, computed, makeObservable, observable, reaction, runInAction, when } from "mobx";
|
||||||
import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store";
|
import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store";
|
||||||
import { navigate } from "../../navigation";
|
|
||||||
import { MenuItem, MenuActions } from "../menu";
|
import { MenuItem, MenuActions } from "../menu";
|
||||||
import type { CatalogEntityContextMenu } from "../../api/catalog-entity";
|
import type { CatalogEntityContextMenu } from "../../api/catalog-entity";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
|
||||||
import type { CatalogCategory, CatalogCategoryRegistry, CatalogEntity } from "../../../common/catalog";
|
import type { CatalogCategory, CatalogCategoryRegistry, CatalogEntity } from "../../../common/catalog";
|
||||||
import { CatalogAddButton } from "./catalog-add-button";
|
import { CatalogAddButton } from "./catalog-add-button";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
@ -46,6 +44,10 @@ import type { HotbarStore } from "../../../common/hotbars/store";
|
|||||||
import type { OnContextMenuOpen } from "../../../common/catalog/on-context-menu-open.injectable";
|
import type { OnContextMenuOpen } from "../../../common/catalog/on-context-menu-open.injectable";
|
||||||
import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable";
|
import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable";
|
||||||
import onContextMenuOpenInjectable from "../../../common/catalog/on-context-menu-open.injectable";
|
import onContextMenuOpenInjectable from "../../../common/catalog/on-context-menu-open.injectable";
|
||||||
|
import type { Navigate } from "../../navigation/navigate.injectable";
|
||||||
|
import navigateInjectable from "../../navigation/navigate.injectable";
|
||||||
|
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
|
||||||
|
import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
catalogPreviousActiveTabStorage: StorageLayer<string | null>;
|
catalogPreviousActiveTabStorage: StorageLayer<string | null>;
|
||||||
@ -61,6 +63,8 @@ interface Dependencies {
|
|||||||
hotbarStore: HotbarStore;
|
hotbarStore: HotbarStore;
|
||||||
catalogCategoryRegistry: CatalogCategoryRegistry;
|
catalogCategoryRegistry: CatalogCategoryRegistry;
|
||||||
onContextMenuOpen: OnContextMenuOpen;
|
onContextMenuOpen: OnContextMenuOpen;
|
||||||
|
navigate: Navigate;
|
||||||
|
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -160,23 +164,6 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
|
||||||
if (menuItem.confirm) {
|
|
||||||
ConfirmDialog.open({
|
|
||||||
okButtonProps: {
|
|
||||||
primary: false,
|
|
||||||
accent: true,
|
|
||||||
},
|
|
||||||
ok: () => {
|
|
||||||
menuItem.onClick();
|
|
||||||
},
|
|
||||||
message: menuItem.confirm.message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
menuItem.onClick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get categories() {
|
get categories() {
|
||||||
return this.props.catalogCategoryRegistry.items;
|
return this.props.catalogCategoryRegistry.items;
|
||||||
}
|
}
|
||||||
@ -206,7 +193,7 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
|||||||
this.menuItems.clear();
|
this.menuItems.clear();
|
||||||
this.props.onContextMenuOpen(entity, {
|
this.props.onContextMenuOpen(entity, {
|
||||||
menuItems: this.menuItems,
|
menuItems: this.menuItems,
|
||||||
navigate: (url) => navigate(url),
|
navigate: this.props.navigate,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -219,11 +206,13 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
|||||||
View Details
|
View Details
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{
|
{
|
||||||
this.menuItems.map((menuItem, index) => (
|
this.menuItems
|
||||||
<MenuItem key={index} onClick={() => this.onMenuItemClick(menuItem)}>
|
.map(this.props.normalizeMenuItem)
|
||||||
{menuItem.title}
|
.map((menuItem, index) => (
|
||||||
</MenuItem>
|
<MenuItem key={index} onClick={menuItem.onClick}>
|
||||||
))
|
{menuItem.title}
|
||||||
|
</MenuItem>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
<HotbarToggleMenuItem
|
<HotbarToggleMenuItem
|
||||||
key="hotbar-toggle"
|
key="hotbar-toggle"
|
||||||
@ -358,8 +347,9 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Catalog = withInjectables<Dependencies>( NonInjectedCatalog, {
|
export const Catalog = withInjectables<Dependencies>(NonInjectedCatalog, {
|
||||||
getProps: (di) => ({
|
getProps: (di, props) => ({
|
||||||
|
...props,
|
||||||
catalogEntityStore: di.inject(catalogEntityStoreInjectable),
|
catalogEntityStore: di.inject(catalogEntityStoreInjectable),
|
||||||
catalogPreviousActiveTabStorage: di.inject(catalogPreviousActiveTabStorageInjectable),
|
catalogPreviousActiveTabStorage: di.inject(catalogPreviousActiveTabStorageInjectable),
|
||||||
getCategoryColumns: di.inject(getCategoryColumnsInjectable),
|
getCategoryColumns: di.inject(getCategoryColumnsInjectable),
|
||||||
@ -370,5 +360,7 @@ export const Catalog = withInjectables<Dependencies>( NonInjectedCatalog, {
|
|||||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
hotbarStore: di.inject(hotbarStoreInjectable),
|
||||||
catalogCategoryRegistry: di.inject(catalogCategoryRegistryInjectable),
|
catalogCategoryRegistry: di.inject(catalogCategoryRegistryInjectable),
|
||||||
onContextMenuOpen: di.inject(onContextMenuOpenInjectable),
|
onContextMenuOpen: di.inject(onContextMenuOpenInjectable),
|
||||||
|
navigate: di.inject(navigateInjectable),
|
||||||
|
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,152 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { ExtendableDisposer } from "../../../common/utils";
|
||||||
|
import { downloadFile, downloadJson } from "../../../common/utils";
|
||||||
|
import { Notifications } from "../notifications";
|
||||||
|
import React from "react";
|
||||||
|
import path from "path";
|
||||||
|
import { SemVer } from "semver";
|
||||||
|
import URLParse from "url-parse";
|
||||||
|
import type { InstallRequest } from "./attempt-install/install-request";
|
||||||
|
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||||
|
import type { Confirm } from "../confirm-dialog/confirm.injectable";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import attemptInstallInjectable from "./attempt-install/attempt-install.injectable";
|
||||||
|
import getBaseRegistryUrlInjectable from "./get-base-registry-url/get-base-registry-url.injectable";
|
||||||
|
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||||
|
import confirmInjectable from "../confirm-dialog/confirm.injectable";
|
||||||
|
import { reduce } from "lodash";
|
||||||
|
|
||||||
|
export interface ExtensionInfo {
|
||||||
|
name: string;
|
||||||
|
version?: string;
|
||||||
|
requireConfirmation?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AttemptInstallByInfo = (info: ExtensionInfo) => Promise<void>;
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
attemptInstall: (request: InstallRequest, d: ExtendableDisposer) => Promise<void>;
|
||||||
|
getBaseRegistryUrl: () => Promise<string>;
|
||||||
|
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||||
|
confirm: Confirm;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attemptInstallByInfo = ({
|
||||||
|
attemptInstall,
|
||||||
|
getBaseRegistryUrl,
|
||||||
|
extensionInstallationStateStore,
|
||||||
|
confirm,
|
||||||
|
}: Dependencies): AttemptInstallByInfo => (
|
||||||
|
async (info) => {
|
||||||
|
const { name, version, requireConfirmation = false } = info;
|
||||||
|
const disposer = extensionInstallationStateStore.startPreInstall();
|
||||||
|
const baseUrl = await getBaseRegistryUrl();
|
||||||
|
const registryUrl = new URLParse(baseUrl).set("pathname", name).toString();
|
||||||
|
let json: any;
|
||||||
|
let finalVersion = version;
|
||||||
|
|
||||||
|
try {
|
||||||
|
json = await downloadJson({ url: registryUrl }).promise;
|
||||||
|
|
||||||
|
if (!json || json.error || typeof json.versions !== "object" || !json.versions) {
|
||||||
|
const message = json?.error ? `: ${json.error}` : "";
|
||||||
|
|
||||||
|
Notifications.error(`Failed to get registry information for that extension${message}`);
|
||||||
|
|
||||||
|
return disposer();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof SyntaxError) {
|
||||||
|
// assume invalid JSON
|
||||||
|
console.warn("Set registry has invalid json", { url: baseUrl }, error);
|
||||||
|
Notifications.error("Failed to get valid registry information for that extension. Registry did not return valid JSON");
|
||||||
|
} else {
|
||||||
|
console.error("Failed to download registry information", error);
|
||||||
|
Notifications.error(`Failed to get valid registry information for that extension. ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return disposer();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version) {
|
||||||
|
if (!json.versions[version]) {
|
||||||
|
if (json["dist-tags"][version]) {
|
||||||
|
finalVersion = json["dist-tags"][version];
|
||||||
|
} else {
|
||||||
|
Notifications.error((
|
||||||
|
<p>
|
||||||
|
{"The "}
|
||||||
|
<em>{name}</em>
|
||||||
|
{" extension does not have a version or tag "}
|
||||||
|
<code>{version}</code>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
));
|
||||||
|
|
||||||
|
return disposer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const versions = Object.keys(json.versions)
|
||||||
|
.map(version => new SemVer(version, { loose: true, includePrerelease: true }))
|
||||||
|
// ignore pre-releases for auto picking the version
|
||||||
|
.filter(version => version.prerelease.length === 0);
|
||||||
|
|
||||||
|
const latestVersion = reduce(versions, (prev, curr) => prev.compareMain(curr) === -1 ? curr : prev);
|
||||||
|
|
||||||
|
if (!latestVersion) {
|
||||||
|
console.error("No versions supplied for that extension", { name });
|
||||||
|
Notifications.error(`No versions found for ${name}`);
|
||||||
|
|
||||||
|
return disposer();
|
||||||
|
}
|
||||||
|
|
||||||
|
finalVersion = latestVersion.format();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requireConfirmation) {
|
||||||
|
const proceed = await confirm({
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
Are you sure you want to install
|
||||||
|
{" "}
|
||||||
|
<b>
|
||||||
|
{name}
|
||||||
|
@
|
||||||
|
{finalVersion}
|
||||||
|
</b>
|
||||||
|
?
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
labelCancel: "Cancel",
|
||||||
|
labelOk: "Install",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!proceed) {
|
||||||
|
return disposer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const url = json.versions[finalVersion!].dist.tarball;
|
||||||
|
const fileName = path.basename(url);
|
||||||
|
const { promise: dataP } = downloadFile({ url, timeout: 10 * 60 * 1000 });
|
||||||
|
|
||||||
|
return attemptInstall({ fileName, dataP }, disposer);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const attemptInstallByInfoInjectable = getInjectable({
|
||||||
|
id: "attempt-install-by-info",
|
||||||
|
instantiate: (di) => attemptInstallByInfo({
|
||||||
|
attemptInstall: di.inject(attemptInstallInjectable),
|
||||||
|
getBaseRegistryUrl: di.inject(getBaseRegistryUrlInjectable),
|
||||||
|
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
||||||
|
confirm: di.inject(confirmInjectable),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default attemptInstallByInfoInjectable;
|
||||||
@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } 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";
|
|
||||||
import extensionInstallationStateStoreInjectable
|
|
||||||
from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
|
|
||||||
const attemptInstallByInfoInjectable = getInjectable({
|
|
||||||
id: "attempt-install-by-info",
|
|
||||||
|
|
||||||
instantiate: (di) =>
|
|
||||||
attemptInstallByInfo({
|
|
||||||
attemptInstall: di.inject(attemptInstallInjectable),
|
|
||||||
getBaseRegistryUrl: di.inject(getBaseRegistryUrlInjectable),
|
|
||||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default attemptInstallByInfoInjectable;
|
|
||||||
@ -1,124 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import type { ExtendableDisposer } from "../../../../common/utils";
|
|
||||||
import { downloadFile, downloadJson } from "../../../../common/utils";
|
|
||||||
import { Notifications } from "../../notifications";
|
|
||||||
import { ConfirmDialog } from "../../confirm-dialog";
|
|
||||||
import React from "react";
|
|
||||||
import path from "path";
|
|
||||||
import { SemVer } from "semver";
|
|
||||||
import URLParse from "url-parse";
|
|
||||||
import type { InstallRequest } from "../attempt-install/install-request";
|
|
||||||
import lodash from "lodash";
|
|
||||||
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
|
||||||
|
|
||||||
export interface ExtensionInfo {
|
|
||||||
name: string;
|
|
||||||
version?: string;
|
|
||||||
requireConfirmation?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
attemptInstall: (request: InstallRequest, d: ExtendableDisposer) => Promise<void>;
|
|
||||||
getBaseRegistryUrl: () => Promise<string>;
|
|
||||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const attemptInstallByInfo = ({ attemptInstall, getBaseRegistryUrl, extensionInstallationStateStore }: Dependencies) => async ({
|
|
||||||
name,
|
|
||||||
version,
|
|
||||||
requireConfirmation = false,
|
|
||||||
}: ExtensionInfo) => {
|
|
||||||
const disposer = extensionInstallationStateStore.startPreInstall();
|
|
||||||
const baseUrl = await getBaseRegistryUrl();
|
|
||||||
const registryUrl = new URLParse(baseUrl).set("pathname", name).toString();
|
|
||||||
let json: any;
|
|
||||||
|
|
||||||
try {
|
|
||||||
json = await downloadJson({ url: registryUrl }).promise;
|
|
||||||
|
|
||||||
if (!json || json.error || typeof json.versions !== "object" || !json.versions) {
|
|
||||||
const message = json?.error ? `: ${json.error}` : "";
|
|
||||||
|
|
||||||
Notifications.error(`Failed to get registry information for that extension${message}`);
|
|
||||||
|
|
||||||
return disposer();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof SyntaxError) {
|
|
||||||
// assume invalid JSON
|
|
||||||
console.warn("Set registry has invalid json", { url: baseUrl }, error);
|
|
||||||
Notifications.error("Failed to get valid registry information for that extension. Registry did not return valid JSON");
|
|
||||||
} else {
|
|
||||||
console.error("Failed to download registry information", error);
|
|
||||||
Notifications.error(`Failed to get valid registry information for that extension. ${error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return disposer();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (version) {
|
|
||||||
if (!json.versions[version]) {
|
|
||||||
if (json["dist-tags"][version]) {
|
|
||||||
version = json["dist-tags"][version];
|
|
||||||
} else {
|
|
||||||
Notifications.error(
|
|
||||||
<p>
|
|
||||||
{"The "}
|
|
||||||
<em>{name}</em>
|
|
||||||
{" extension does not have a version or tag "}
|
|
||||||
<code>{version}</code>
|
|
||||||
.
|
|
||||||
</p>,
|
|
||||||
);
|
|
||||||
|
|
||||||
return disposer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const versions = Object.keys(json.versions)
|
|
||||||
.map(
|
|
||||||
version =>
|
|
||||||
new SemVer(version, { loose: true, includePrerelease: true }),
|
|
||||||
)
|
|
||||||
// ignore pre-releases for auto picking the version
|
|
||||||
.filter(version => version.prerelease.length === 0);
|
|
||||||
const latestVersion = lodash.reduce(versions, (prev, curr) => prev.compareMain(curr) === -1 ? curr : prev);
|
|
||||||
|
|
||||||
if (!latestVersion) {
|
|
||||||
console.error("No versions supplied for that extension", { name });
|
|
||||||
Notifications.error(`No versions found for ${name}`);
|
|
||||||
|
|
||||||
return disposer();
|
|
||||||
}
|
|
||||||
|
|
||||||
version = latestVersion.format();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requireConfirmation) {
|
|
||||||
const proceed = await ConfirmDialog.confirm({
|
|
||||||
message: (
|
|
||||||
<p>
|
|
||||||
{"Are you sure you want to install "}
|
|
||||||
<b>{`${name}@${version}`}</b>
|
|
||||||
?
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
labelCancel: "Cancel",
|
|
||||||
labelOk: "Install",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!proceed) {
|
|
||||||
return disposer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
const url = json.versions[version!].dist.tarball;
|
|
||||||
const fileName = path.basename(url);
|
|
||||||
const { promise: dataP } = downloadFile({ url, timeout: 10 * 60 * 1000 });
|
|
||||||
|
|
||||||
return attemptInstall({ fileName, dataP }, disposer);
|
|
||||||
};
|
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import React from "react";
|
||||||
|
import type { InstalledExtension } from "../../../extensions/extension-discovery/extension-discovery";
|
||||||
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
|
import { extensionDisplayName } from "../../../extensions/lens-extension";
|
||||||
|
import type { Confirm } from "../confirm-dialog/confirm.injectable";
|
||||||
|
import confirmInjectable from "../confirm-dialog/confirm.injectable";
|
||||||
|
import uninstallExtensionInjectable from "./uninstall-extension/uninstall-extension.injectable";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
uninstallExtension: (id: LensExtensionId) => Promise<boolean>;
|
||||||
|
confirm: Confirm;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConfirmUninstallExtension = (ext: InstalledExtension) => Promise<void>;
|
||||||
|
|
||||||
|
const confirmUninstallExtension = ({
|
||||||
|
uninstallExtension,
|
||||||
|
confirm,
|
||||||
|
}: Dependencies): ConfirmUninstallExtension => (
|
||||||
|
async (extension) => {
|
||||||
|
const displayName = extensionDisplayName(
|
||||||
|
extension.manifest.name,
|
||||||
|
extension.manifest.version,
|
||||||
|
);
|
||||||
|
const confirmed = await confirm({
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
{"Are you sure you want to uninstall extension "}
|
||||||
|
<b>{displayName}</b>
|
||||||
|
?
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
labelOk: "Yes",
|
||||||
|
labelCancel: "No",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
|
await uninstallExtension(extension.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const confirmUninstallExtensionInjectable = getInjectable({
|
||||||
|
id: "confirm-uninstall-extension",
|
||||||
|
instantiate: (di) => confirmUninstallExtension({
|
||||||
|
uninstallExtension: di.inject(uninstallExtensionInjectable),
|
||||||
|
confirm: di.inject(confirmInjectable),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default confirmUninstallExtensionInjectable;
|
||||||
@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { confirmUninstallExtension } from "./confirm-uninstall-extension";
|
|
||||||
import uninstallExtensionInjectable from "../uninstall-extension/uninstall-extension.injectable";
|
|
||||||
|
|
||||||
const confirmUninstallExtensionInjectable = getInjectable({
|
|
||||||
id: "confirm-uninstall-extension",
|
|
||||||
|
|
||||||
instantiate: (di) =>
|
|
||||||
confirmUninstallExtension({
|
|
||||||
uninstallExtension: di.inject(uninstallExtensionInjectable),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default confirmUninstallExtensionInjectable;
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import React from "react";
|
|
||||||
import type { InstalledExtension } from "../../../../extensions/extension-discovery/extension-discovery";
|
|
||||||
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
|
||||||
import { extensionDisplayName } from "../../../../extensions/lens-extension";
|
|
||||||
import { ConfirmDialog } from "../../confirm-dialog";
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
uninstallExtension: (id: LensExtensionId) => Promise<boolean>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confirmUninstallExtension =
|
|
||||||
({ uninstallExtension }: Dependencies) =>
|
|
||||||
async (extension: InstalledExtension): Promise<void> => {
|
|
||||||
const displayName = extensionDisplayName(
|
|
||||||
extension.manifest.name,
|
|
||||||
extension.manifest.version,
|
|
||||||
);
|
|
||||||
const confirmed = await ConfirmDialog.confirm({
|
|
||||||
message: (
|
|
||||||
<p>
|
|
||||||
{"Are you sure you want to uninstall extension "}
|
|
||||||
<b>{displayName}</b>
|
|
||||||
?
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
labelOk: "Yes",
|
|
||||||
labelCancel: "No",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (confirmed) {
|
|
||||||
await uninstallExtension(extension.id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -26,7 +26,8 @@ import { withInjectables } from "@ogre-tools/injectable-react";
|
|||||||
import userExtensionsInjectable from "./user-extensions/user-extensions.injectable";
|
import userExtensionsInjectable from "./user-extensions/user-extensions.injectable";
|
||||||
import enableExtensionInjectable from "./enable-extension/enable-extension.injectable";
|
import enableExtensionInjectable from "./enable-extension/enable-extension.injectable";
|
||||||
import disableExtensionInjectable from "./disable-extension/disable-extension.injectable";
|
import disableExtensionInjectable from "./disable-extension/disable-extension.injectable";
|
||||||
import confirmUninstallExtensionInjectable from "./confirm-uninstall-extension/confirm-uninstall-extension.injectable";
|
import type { ConfirmUninstallExtension } from "./confirm-uninstall-extension.injectable";
|
||||||
|
import confirmUninstallExtensionInjectable from "./confirm-uninstall-extension.injectable";
|
||||||
import installFromInputInjectable from "./install-from-input/install-from-input.injectable";
|
import installFromInputInjectable from "./install-from-input/install-from-input.injectable";
|
||||||
import installFromSelectFileDialogInjectable from "./install-from-select-file-dialog.injectable";
|
import installFromSelectFileDialogInjectable from "./install-from-select-file-dialog.injectable";
|
||||||
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
@ -39,7 +40,7 @@ interface Dependencies {
|
|||||||
userExtensions: IComputedValue<InstalledExtension[]>;
|
userExtensions: IComputedValue<InstalledExtension[]>;
|
||||||
enableExtension: (id: LensExtensionId) => void;
|
enableExtension: (id: LensExtensionId) => void;
|
||||||
disableExtension: (id: LensExtensionId) => void;
|
disableExtension: (id: LensExtensionId) => void;
|
||||||
confirmUninstallExtension: (extension: InstalledExtension) => Promise<void>;
|
confirmUninstallExtension: ConfirmUninstallExtension;
|
||||||
installFromInput: (input: string) => Promise<void>;
|
installFromInput: (input: string) => Promise<void>;
|
||||||
installFromSelectFileDialog: () => Promise<void>;
|
installFromSelectFileDialog: () => Promise<void>;
|
||||||
installOnDrop: (files: File[]) => Promise<void>;
|
installOnDrop: (files: File[]) => Promise<void>;
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
||||||
import { installFromInput } 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.injectable";
|
||||||
import extensionInstallationStateStoreInjectable
|
import extensionInstallationStateStoreInjectable
|
||||||
from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import path from "path";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { readFileNotify } from "../read-file-notify/read-file-notify";
|
import { readFileNotify } from "../read-file-notify/read-file-notify";
|
||||||
import type { InstallRequest } from "../attempt-install/install-request";
|
import type { InstallRequest } from "../attempt-install/install-request";
|
||||||
import type { ExtensionInfo } from "../attempt-install-by-info/attempt-install-by-info";
|
import type { ExtensionInfo } from "../attempt-install-by-info.injectable";
|
||||||
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||||
import { AsyncInputValidationError } from "../../input/input_validators";
|
import { AsyncInputValidationError } from "../../input/input_validators";
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import React from "react";
|
|||||||
import type { ClusterRoleBinding } from "../../../../common/k8s-api/endpoints";
|
import type { ClusterRoleBinding } from "../../../../common/k8s-api/endpoints";
|
||||||
import { autoBind, ObservableHashSet, prevDefault } from "../../../utils";
|
import { autoBind, ObservableHashSet, prevDefault } from "../../../utils";
|
||||||
import { AddRemoveButtons } from "../../add-remove-buttons";
|
import { AddRemoveButtons } from "../../add-remove-buttons";
|
||||||
import { ConfirmDialog } from "../../confirm-dialog";
|
|
||||||
import { DrawerTitle } from "../../drawer";
|
import { DrawerTitle } from "../../drawer";
|
||||||
import type { KubeObjectDetailsProps } from "../../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../../kube-object-details";
|
||||||
import { KubeObjectMeta } from "../../kube-object-meta";
|
import { KubeObjectMeta } from "../../kube-object-meta";
|
||||||
@ -20,15 +19,22 @@ import { Table, TableCell, TableHead, TableRow } from "../../table";
|
|||||||
import { ClusterRoleBindingDialog } from "./dialog";
|
import { ClusterRoleBindingDialog } from "./dialog";
|
||||||
import { clusterRoleBindingStore } from "./legacy-store";
|
import { clusterRoleBindingStore } from "./legacy-store";
|
||||||
import { hashSubject } from "../hashers";
|
import { hashSubject } from "../hashers";
|
||||||
|
import type { OpenConfirmDialog } from "../../confirm-dialog/open.injectable";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import openConfirmDialogInjectable from "../../confirm-dialog/open.injectable";
|
||||||
|
|
||||||
export interface ClusterRoleBindingDetailsProps extends KubeObjectDetailsProps<ClusterRoleBinding> {
|
export interface ClusterRoleBindingDetailsProps extends KubeObjectDetailsProps<ClusterRoleBinding> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
openConfirmDialog: OpenConfirmDialog;
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindingDetailsProps> {
|
class NonInjectedClusterRoleBindingDetails extends React.Component<ClusterRoleBindingDetailsProps & Dependencies> {
|
||||||
selectedSubjects = new ObservableHashSet([], hashSubject);
|
selectedSubjects = new ObservableHashSet([], hashSubject);
|
||||||
|
|
||||||
constructor(props: ClusterRoleBindingDetailsProps) {
|
constructor(props: ClusterRoleBindingDetailsProps & Dependencies) {
|
||||||
super(props);
|
super(props);
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
}
|
}
|
||||||
@ -42,10 +48,10 @@ export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindin
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeSelectedSubjects() {
|
removeSelectedSubjects() {
|
||||||
const { object: clusterRoleBinding } = this.props;
|
const { object: clusterRoleBinding, openConfirmDialog } = this.props;
|
||||||
const { selectedSubjects } = this;
|
const { selectedSubjects } = this;
|
||||||
|
|
||||||
ConfirmDialog.open({
|
openConfirmDialog({
|
||||||
ok: () => clusterRoleBindingStore.removeSubjects(clusterRoleBinding, selectedSubjects),
|
ok: () => clusterRoleBindingStore.removeSubjects(clusterRoleBinding, selectedSubjects),
|
||||||
labelOk: `Remove`,
|
labelOk: `Remove`,
|
||||||
message: (
|
message: (
|
||||||
@ -127,3 +133,10 @@ export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindin
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ClusterRoleBindingDetails = withInjectables<Dependencies, ClusterRoleBindingDetailsProps>(NonInjectedClusterRoleBindingDetails, {
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
...props,
|
||||||
|
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import React from "react";
|
|||||||
import type { RoleBinding } from "../../../../common/k8s-api/endpoints";
|
import type { RoleBinding } from "../../../../common/k8s-api/endpoints";
|
||||||
import { prevDefault } from "../../../utils";
|
import { prevDefault } from "../../../utils";
|
||||||
import { AddRemoveButtons } from "../../add-remove-buttons";
|
import { AddRemoveButtons } from "../../add-remove-buttons";
|
||||||
import { ConfirmDialog } from "../../confirm-dialog";
|
|
||||||
import { DrawerTitle } from "../../drawer";
|
import { DrawerTitle } from "../../drawer";
|
||||||
import type { KubeObjectDetailsProps } from "../../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../../kube-object-details";
|
||||||
import { KubeObjectMeta } from "../../kube-object-meta";
|
import { KubeObjectMeta } from "../../kube-object-meta";
|
||||||
@ -20,13 +19,20 @@ import { RoleBindingDialog } from "./dialog";
|
|||||||
import { roleBindingStore } from "./legacy-store";
|
import { roleBindingStore } from "./legacy-store";
|
||||||
import { ObservableHashSet } from "../../../../common/utils/hash-set";
|
import { ObservableHashSet } from "../../../../common/utils/hash-set";
|
||||||
import { hashSubject } from "../hashers";
|
import { hashSubject } from "../hashers";
|
||||||
|
import type { OpenConfirmDialog } from "../../confirm-dialog/open.injectable";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import openConfirmDialogInjectable from "../../confirm-dialog/open.injectable";
|
||||||
|
|
||||||
export interface RoleBindingDetailsProps extends KubeObjectDetailsProps<RoleBinding> {
|
export interface RoleBindingDetailsProps extends KubeObjectDetailsProps<RoleBinding> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
openConfirmDialog: OpenConfirmDialog;
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps> {
|
class NonInjectedRoleBindingDetails extends React.Component<RoleBindingDetailsProps & Dependencies> {
|
||||||
selectedSubjects = new ObservableHashSet([], hashSubject);
|
private readonly selectedSubjects = new ObservableHashSet([], hashSubject);
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
@ -37,10 +43,10 @@ export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps>
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeSelectedSubjects = () => {
|
removeSelectedSubjects = () => {
|
||||||
const { object: roleBinding } = this.props;
|
const { object: roleBinding, openConfirmDialog } = this.props;
|
||||||
const { selectedSubjects } = this;
|
const { selectedSubjects } = this;
|
||||||
|
|
||||||
ConfirmDialog.open({
|
openConfirmDialog({
|
||||||
ok: () => roleBindingStore.removeSubjects(roleBinding, selectedSubjects.toJSON()),
|
ok: () => roleBindingStore.removeSubjects(roleBinding, selectedSubjects.toJSON()),
|
||||||
labelOk: `Remove`,
|
labelOk: `Remove`,
|
||||||
message: (
|
message: (
|
||||||
@ -122,3 +128,10 @@ export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const RoleBindingDetails = withInjectables<Dependencies, RoleBindingDetailsProps>(NonInjectedRoleBindingDetails, {
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
...props,
|
||||||
|
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|||||||
@ -9,80 +9,95 @@ import { cronJobApi } from "../../../common/k8s-api/endpoints";
|
|||||||
import { MenuItem } from "../menu";
|
import { MenuItem } from "../menu";
|
||||||
import { CronJobTriggerDialog } from "./cronjob-trigger-dialog";
|
import { CronJobTriggerDialog } from "./cronjob-trigger-dialog";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
|
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
|
||||||
|
|
||||||
export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) {
|
export interface CronJobMenuProps extends KubeObjectMenuProps<CronJob> {}
|
||||||
const { object, toolbar } = props;
|
|
||||||
|
|
||||||
return (
|
interface Dependencies {
|
||||||
<>
|
openConfirmDialog: OpenConfirmDialog;
|
||||||
<MenuItem onClick={() => CronJobTriggerDialog.open(object)}>
|
|
||||||
<Icon
|
|
||||||
material="play_circle_filled"
|
|
||||||
tooltip="Trigger"
|
|
||||||
interactive={toolbar}
|
|
||||||
/>
|
|
||||||
<span className="title">Trigger</span>
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
{object.isSuspend()
|
|
||||||
? (
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => ConfirmDialog.open({
|
|
||||||
ok: async () => {
|
|
||||||
try {
|
|
||||||
await cronJobApi.resume({ namespace: object.getNs(), name: object.getName() });
|
|
||||||
} catch (err) {
|
|
||||||
Notifications.checkedError(err, "Unknown error occured while resuming CronJob");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
labelOk: `Resume`,
|
|
||||||
message: (
|
|
||||||
<p>
|
|
||||||
{"Resume CronJob "}
|
|
||||||
<b>{object.getName()}</b>
|
|
||||||
?
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
material="play_circle_outline"
|
|
||||||
tooltip="Resume"
|
|
||||||
interactive={toolbar}
|
|
||||||
/>
|
|
||||||
<span className="title">Resume</span>
|
|
||||||
</MenuItem>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => ConfirmDialog.open({
|
|
||||||
ok: async () => {
|
|
||||||
try {
|
|
||||||
await cronJobApi.suspend({ namespace: object.getNs(), name: object.getName() });
|
|
||||||
} catch (err) {
|
|
||||||
Notifications.checkedError(err, "Unknown error occured while suspending CronJob");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
labelOk: `Suspend`,
|
|
||||||
message: (
|
|
||||||
<p>
|
|
||||||
{"Suspend CronJob "}
|
|
||||||
<b>{object.getName()}</b>
|
|
||||||
?
|
|
||||||
</p>),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
material="pause_circle_filled"
|
|
||||||
tooltip="Suspend"
|
|
||||||
interactive={toolbar}
|
|
||||||
/>
|
|
||||||
<span className="title">Suspend</span>
|
|
||||||
</MenuItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NonInjectedCronJobMenu = ({
|
||||||
|
object,
|
||||||
|
toolbar,
|
||||||
|
openConfirmDialog,
|
||||||
|
}: Dependencies & CronJobMenuProps) => (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={() => CronJobTriggerDialog.open(object)}>
|
||||||
|
<Icon
|
||||||
|
material="play_circle_filled"
|
||||||
|
tooltip="Trigger"
|
||||||
|
interactive={toolbar}
|
||||||
|
/>
|
||||||
|
<span className="title">Trigger</span>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
{object.isSuspend()
|
||||||
|
? (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => openConfirmDialog({
|
||||||
|
ok: async () => {
|
||||||
|
try {
|
||||||
|
await cronJobApi.resume({ namespace: object.getNs(), name: object.getName() });
|
||||||
|
} catch (err) {
|
||||||
|
Notifications.checkedError(err, "Unknown error occured while resuming CronJob");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelOk: `Resume`,
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
{"Resume CronJob "}
|
||||||
|
<b>{object.getName()}</b>
|
||||||
|
?
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
material="play_circle_outline"
|
||||||
|
tooltip="Resume"
|
||||||
|
interactive={toolbar}
|
||||||
|
/>
|
||||||
|
<span className="title">Resume</span>
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => openConfirmDialog({
|
||||||
|
ok: async () => {
|
||||||
|
try {
|
||||||
|
await cronJobApi.suspend({ namespace: object.getNs(), name: object.getName() });
|
||||||
|
} catch (err) {
|
||||||
|
Notifications.checkedError(err, "Unknown error occured while suspending CronJob");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelOk: `Suspend`,
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
{"Suspend CronJob "}
|
||||||
|
<b>{object.getName()}</b>
|
||||||
|
?
|
||||||
|
</p>),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
material="pause_circle_filled"
|
||||||
|
tooltip="Suspend"
|
||||||
|
interactive={toolbar}
|
||||||
|
/>
|
||||||
|
<span className="title">Suspend</span>
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const CronJobMenu = withInjectables<Dependencies, CronJobMenuProps>(NonInjectedCronJobMenu, {
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
...props,
|
||||||
|
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|||||||
@ -7,18 +7,20 @@ import type { KubeObjectMenuProps } from "../kube-object-menu";
|
|||||||
import type { Deployment, DeploymentApi } from "../../../common/k8s-api/endpoints";
|
import type { Deployment, DeploymentApi } from "../../../common/k8s-api/endpoints";
|
||||||
import { MenuItem } from "../menu";
|
import { MenuItem } from "../menu";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import type { OpenDeploymentScaleDialog } from "./scale/open.injectable";
|
import type { OpenDeploymentScaleDialog } from "./scale/open.injectable";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import deploymentApiInjectable from "../../../common/k8s-api/endpoints/deployment.api.injectable";
|
import deploymentApiInjectable from "../../../common/k8s-api/endpoints/deployment.api.injectable";
|
||||||
import openDeploymentScaleDialogInjectable from "./scale/open.injectable";
|
import openDeploymentScaleDialogInjectable from "./scale/open.injectable";
|
||||||
|
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
|
||||||
|
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
|
||||||
|
|
||||||
export interface DeploymentMenuProps extends KubeObjectMenuProps<Deployment> {}
|
export interface DeploymentMenuProps extends KubeObjectMenuProps<Deployment> {}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
openDeploymentScaleDialog: OpenDeploymentScaleDialog;
|
openDeploymentScaleDialog: OpenDeploymentScaleDialog;
|
||||||
deploymentApi: DeploymentApi;
|
deploymentApi: DeploymentApi;
|
||||||
|
openConfirmDialog: OpenConfirmDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedDeploymentMenu = ({
|
const NonInjectedDeploymentMenu = ({
|
||||||
@ -26,6 +28,7 @@ const NonInjectedDeploymentMenu = ({
|
|||||||
object,
|
object,
|
||||||
openDeploymentScaleDialog,
|
openDeploymentScaleDialog,
|
||||||
toolbar,
|
toolbar,
|
||||||
|
openConfirmDialog,
|
||||||
}: Dependencies & DeploymentMenuProps) => (
|
}: Dependencies & DeploymentMenuProps) => (
|
||||||
<>
|
<>
|
||||||
<MenuItem onClick={() => openDeploymentScaleDialog(object)}>
|
<MenuItem onClick={() => openDeploymentScaleDialog(object)}>
|
||||||
@ -37,7 +40,7 @@ const NonInjectedDeploymentMenu = ({
|
|||||||
<span className="title">Scale</span>
|
<span className="title">Scale</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => ConfirmDialog.open({
|
onClick={() => openConfirmDialog({
|
||||||
ok: async () =>
|
ok: async () =>
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@ -74,5 +77,6 @@ export const DeploymentMenu = withInjectables<Dependencies, DeploymentMenuProps>
|
|||||||
...props,
|
...props,
|
||||||
deploymentApi: di.inject(deploymentApiInjectable),
|
deploymentApi: di.inject(deploymentApiInjectable),
|
||||||
openDeploymentScaleDialog: di.inject(openDeploymentScaleDialogInjectable),
|
openDeploymentScaleDialog: di.inject(openDeploymentScaleDialogInjectable),
|
||||||
|
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import React from "react";
|
|
||||||
import type { KubeObjectMenuProps } from "../kube-object-menu";
|
|
||||||
import type { StatefulSet } from "../../../common/k8s-api/endpoints";
|
|
||||||
import { MenuItem } from "../menu";
|
|
||||||
import { Icon } from "../icon";
|
|
||||||
import type { OpenStatefulSetScaleDialog } from "./scale/open-dialog.injectable";
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
|
||||||
import openStatefulSetScaleDialogInjectable from "./scale/open-dialog.injectable";
|
|
||||||
|
|
||||||
export interface StatefulSetMenuProps extends KubeObjectMenuProps<StatefulSet> {}
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
openStatefulSetScaleDialog: OpenStatefulSetScaleDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NonInjectedStatefulSetMenu = ({
|
|
||||||
object,
|
|
||||||
toolbar,
|
|
||||||
openStatefulSetScaleDialog,
|
|
||||||
}: Dependencies & StatefulSetMenuProps) => (
|
|
||||||
<>
|
|
||||||
<MenuItem onClick={() => openStatefulSetScaleDialog(object)}>
|
|
||||||
<Icon
|
|
||||||
material="open_with"
|
|
||||||
tooltip="Scale"
|
|
||||||
interactive={toolbar}
|
|
||||||
/>
|
|
||||||
<span className="title">Scale</span>
|
|
||||||
</MenuItem>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const StatefulSetMenu = withInjectables<Dependencies, StatefulSetMenuProps>(NonInjectedStatefulSetMenu, {
|
|
||||||
getProps: (di, props) => ({
|
|
||||||
...props,
|
|
||||||
openStatefulSetScaleDialog: di.inject(openStatefulSetScaleDialogInjectable),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
import "./animate.scss";
|
import "./animate.scss";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observable, reaction, makeObservable } from "mobx";
|
import { observable, makeObservable } from "mobx";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { cssNames, noop } from "../../utils";
|
import { cssNames, noop } from "../../utils";
|
||||||
|
|
||||||
export type AnimateName = "opacity" | "slide-right" | "opacity-scale" | string;
|
export type AnimateName = "opacity" | "slide-right" | "opacity-scale" | string;
|
||||||
@ -47,15 +47,24 @@ class DefaultedAnimate extends React.Component<AnimateProps & typeof DefaultedAn
|
|||||||
return React.Children.only(this.props.children) as React.ReactElement<React.HTMLAttributes<any>>;
|
return React.Children.only(this.props.children) as React.ReactElement<React.HTMLAttributes<any>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private toggle(enter: boolean) {
|
||||||
|
if (enter) {
|
||||||
|
this.enter();
|
||||||
|
} else {
|
||||||
|
this.leave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
disposeOnUnmount(this, [
|
this.toggle(this.props.enter);
|
||||||
reaction(() => this.props.enter, enter => {
|
}
|
||||||
if (enter) this.enter();
|
|
||||||
else this.leave();
|
componentDidUpdate(prevProps: Readonly<AnimateProps>): void {
|
||||||
}, {
|
const { enter } = this.props;
|
||||||
fireImmediately: true,
|
|
||||||
}),
|
if (prevProps.enter !== enter) {
|
||||||
]);
|
this.toggle(enter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enter() {
|
enter() {
|
||||||
|
|||||||
@ -7,7 +7,8 @@ import "./confirm-dialog.scss";
|
|||||||
|
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observable, makeObservable } from "mobx";
|
import type { IObservableValue } from "mobx";
|
||||||
|
import { observable, makeObservable, computed } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { cssNames, noop, prevDefault } from "../../utils";
|
import { cssNames, noop, prevDefault } from "../../utils";
|
||||||
import type { ButtonProps } from "../button";
|
import type { ButtonProps } from "../button";
|
||||||
@ -16,6 +17,8 @@ import type { DialogProps } from "../dialog";
|
|||||||
import { Dialog } from "../dialog";
|
import { Dialog } from "../dialog";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import confirmDialogStateInjectable from "./state.injectable";
|
||||||
|
|
||||||
export interface ConfirmDialogProps extends Partial<DialogProps> {
|
export interface ConfirmDialogProps extends Partial<DialogProps> {
|
||||||
}
|
}
|
||||||
@ -34,6 +37,10 @@ export interface ConfirmDialogBooleanParams {
|
|||||||
cancelButtonProps?: Partial<ButtonProps>;
|
cancelButtonProps?: Partial<ButtonProps>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
state: IObservableValue<ConfirmDialogParams | undefined>;
|
||||||
|
}
|
||||||
|
|
||||||
const defaultParams = {
|
const defaultParams = {
|
||||||
ok: noop,
|
ok: noop,
|
||||||
cancel: noop,
|
cancel: noop,
|
||||||
@ -42,41 +49,18 @@ const defaultParams = {
|
|||||||
icon: <Icon big material="warning"/>,
|
icon: <Icon big material="warning"/>,
|
||||||
};
|
};
|
||||||
|
|
||||||
const dialogState = observable.box<ConfirmDialogParams | undefined>();
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ConfirmDialog extends React.Component<ConfirmDialogProps> {
|
class NonInjectedConfirmDialog extends React.Component<ConfirmDialogProps & Dependencies> {
|
||||||
@observable isSaving = false;
|
@observable isSaving = false;
|
||||||
|
|
||||||
constructor(props: ConfirmDialogProps & typeof defaultParams) {
|
constructor(props: ConfirmDialogProps & Dependencies) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static open(params: ConfirmDialogParams) {
|
@computed
|
||||||
dialogState.set(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
static confirm(params: ConfirmDialogBooleanParams): Promise<boolean> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
ConfirmDialog.open({
|
|
||||||
ok: () => resolve(true),
|
|
||||||
cancel: () => resolve(false),
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultParams = {
|
|
||||||
ok: noop,
|
|
||||||
cancel: noop,
|
|
||||||
labelOk: "Ok",
|
|
||||||
labelCancel: "Cancel",
|
|
||||||
icon: <Icon big material="warning"/>,
|
|
||||||
};
|
|
||||||
|
|
||||||
get params() {
|
get params() {
|
||||||
return Object.assign({}, ConfirmDialog.defaultParams, dialogState.get());
|
return Object.assign({}, defaultParams, this.props.state.get() ?? {} as ConfirmDialogParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = async () => {
|
ok = async () => {
|
||||||
@ -100,7 +84,7 @@ export class ConfirmDialog extends React.Component<ConfirmDialogProps> {
|
|||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
this.isSaving = false;
|
this.isSaving = false;
|
||||||
dialogState.set(undefined);
|
this.props.state.set(undefined);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -128,18 +112,18 @@ export class ConfirmDialog extends React.Component<ConfirmDialogProps> {
|
|||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
this.isSaving = false;
|
this.isSaving = false;
|
||||||
dialogState.set(undefined);
|
this.props.state.set(undefined);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, ...dialogProps } = this.props;
|
const { state, className, ...dialogProps } = this.props;
|
||||||
|
const isOpen = Boolean(state.get());
|
||||||
const {
|
const {
|
||||||
icon, labelOk, labelCancel, message,
|
icon, labelOk, labelCancel, message,
|
||||||
okButtonProps = {},
|
okButtonProps = {},
|
||||||
cancelButtonProps = {},
|
cancelButtonProps = {},
|
||||||
} = this.params;
|
} = this.params;
|
||||||
const isOpen = Boolean(dialogState.get());
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
@ -178,3 +162,10 @@ export class ConfirmDialog extends React.Component<ConfirmDialogProps> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ConfirmDialog = withInjectables<Dependencies, ConfirmDialogProps>(NonInjectedConfirmDialog, {
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
...props,
|
||||||
|
state: di.inject(confirmDialogStateInjectable),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|||||||
26
src/renderer/components/confirm-dialog/confirm.injectable.ts
Normal file
26
src/renderer/components/confirm-dialog/confirm.injectable.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { ConfirmDialogBooleanParams } from "./confirm-dialog";
|
||||||
|
import openConfirmDialogInjectable from "./open.injectable";
|
||||||
|
|
||||||
|
export type Confirm = (params: ConfirmDialogBooleanParams) => Promise<boolean>;
|
||||||
|
|
||||||
|
const confirmInjectable = getInjectable({
|
||||||
|
id: "confirm",
|
||||||
|
instantiate: (di): Confirm => {
|
||||||
|
const open = di.inject(openConfirmDialogInjectable);
|
||||||
|
|
||||||
|
return (params) => new Promise(resolve => {
|
||||||
|
open({
|
||||||
|
ok: () => resolve(true),
|
||||||
|
cancel: () => resolve(false),
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default confirmInjectable;
|
||||||
20
src/renderer/components/confirm-dialog/open.injectable.ts
Normal file
20
src/renderer/components/confirm-dialog/open.injectable.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { ConfirmDialogParams } from "./confirm-dialog";
|
||||||
|
import confirmDialogStateInjectable from "./state.injectable";
|
||||||
|
|
||||||
|
export type OpenConfirmDialog = (params: ConfirmDialogParams) => void;
|
||||||
|
|
||||||
|
const openConfirmDialogInjectable = getInjectable({
|
||||||
|
id: "open-confirm-dialog",
|
||||||
|
instantiate: (di): OpenConfirmDialog => {
|
||||||
|
const state = di.inject(confirmDialogStateInjectable);
|
||||||
|
|
||||||
|
return params => state.set(params);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default openConfirmDialogInjectable;
|
||||||
14
src/renderer/components/confirm-dialog/state.injectable.ts
Normal file
14
src/renderer/components/confirm-dialog/state.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { observable } from "mobx";
|
||||||
|
import type { ConfirmDialogParams } from ".";
|
||||||
|
|
||||||
|
const confirmDialogStateInjectable = getInjectable({
|
||||||
|
id: "confirm-dialog-state",
|
||||||
|
instantiate: () => observable.box<ConfirmDialogParams | undefined>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default confirmDialogStateInjectable;
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ConfirmDialogParams } from "./confirm-dialog";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import openConfirmDialogInjectable from "./open.injectable";
|
||||||
|
|
||||||
|
export type WithConfirmation = (params: ConfirmDialogParams) => () => void;
|
||||||
|
|
||||||
|
const withConfirmationInjectable = getInjectable({
|
||||||
|
id: "with-confirmation",
|
||||||
|
instantiate: (di): WithConfirmation => {
|
||||||
|
const open = di.inject(openConfirmDialogInjectable);
|
||||||
|
|
||||||
|
return (params) => () => open(params);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default withConfirmationInjectable;
|
||||||
@ -208,7 +208,7 @@ class NonInjectedDrawer extends React.Component<DrawerProps & Dependencies & typ
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{toolbar}
|
{toolbar}
|
||||||
<Icon material="close" onClick={this.close}/>
|
<Icon material="close" tooltip="Close" onClick={this.close}/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={cssNames("drawer-content flex column box grow", contentClass)}
|
className={cssNames("drawer-content flex column box grow", contentClass)}
|
||||||
|
|||||||
@ -9,13 +9,15 @@ import React, { useState } from "react";
|
|||||||
|
|
||||||
import type { CatalogEntityContextMenu } from "../../../common/catalog";
|
import type { CatalogEntityContextMenu } from "../../../common/catalog";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
|
||||||
import { Menu, MenuItem } from "../menu";
|
import { Menu, MenuItem } from "../menu";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { AvatarProps } from "../avatar";
|
import type { AvatarProps } from "../avatar";
|
||||||
import { Avatar } from "../avatar";
|
import { Avatar } from "../avatar";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Tooltip } from "../tooltip";
|
import { Tooltip } from "../tooltip";
|
||||||
|
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable";
|
||||||
|
|
||||||
export interface HotbarIconProps extends AvatarProps {
|
export interface HotbarIconProps extends AvatarProps {
|
||||||
uid: string;
|
uid: string;
|
||||||
@ -28,24 +30,17 @@ export interface HotbarIconProps extends AvatarProps {
|
|||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
interface Dependencies {
|
||||||
if (menuItem.confirm) {
|
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
|
||||||
ConfirmDialog.open({
|
|
||||||
okButtonProps: {
|
|
||||||
primary: false,
|
|
||||||
accent: true,
|
|
||||||
},
|
|
||||||
ok: () => {
|
|
||||||
menuItem.onClick();
|
|
||||||
},
|
|
||||||
message: menuItem.confirm.message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
menuItem.onClick();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HotbarIcon = observer(({ menuItems = [], size = 40, tooltip, ...props }: HotbarIconProps) => {
|
const NonInjectedHotbarIcon = observer(({
|
||||||
|
menuItems = [],
|
||||||
|
size = 40,
|
||||||
|
tooltip,
|
||||||
|
normalizeMenuItem,
|
||||||
|
...props
|
||||||
|
}: HotbarIconProps & Dependencies) => {
|
||||||
const { uid, title, src, material, active, className, source, disabled, onMenuOpen, onClick, children, ...rest } = props;
|
const { uid, title, src, material, active, className, source, disabled, onMenuOpen, onClick, children, ...rest } = props;
|
||||||
const id = `hotbarIcon-${uid}`;
|
const id = `hotbarIcon-${uid}`;
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
@ -88,13 +83,22 @@ export const HotbarIcon = observer(({ menuItems = [], size = 40, tooltip, ...pro
|
|||||||
close={() => toggleMenu()}
|
close={() => toggleMenu()}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
menuItems.map((menuItem) => (
|
menuItems
|
||||||
<MenuItem key={menuItem.title} onClick={() => onMenuItemClick(menuItem)}>
|
.map(normalizeMenuItem)
|
||||||
{menuItem.title}
|
.map((menuItem) => (
|
||||||
</MenuItem>
|
<MenuItem key={menuItem.title} onClick={menuItem.onClick}>
|
||||||
))
|
{menuItem.title}
|
||||||
|
</MenuItem>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const HotbarIcon = withInjectables<Dependencies, HotbarIconProps>(NonInjectedHotbarIcon, {
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
...props,
|
||||||
|
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|||||||
@ -7,19 +7,22 @@ import React from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Select } from "../select";
|
import { Select } from "../select";
|
||||||
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
||||||
import type { HotbarStore } from "../../../common/hotbars/store";
|
import type { HotbarStore } from "../../../common/hotbars/store";
|
||||||
|
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
|
||||||
|
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
closeCommandOverlay: () => void;
|
closeCommandOverlay: () => void;
|
||||||
|
openConfirmDialog: OpenConfirmDialog;
|
||||||
hotbarStore: HotbarStore;
|
hotbarStore: HotbarStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedHotbarRemoveCommand = observer(({
|
const NonInjectedHotbarRemoveCommand = observer(({
|
||||||
closeCommandOverlay,
|
closeCommandOverlay,
|
||||||
hotbarStore,
|
hotbarStore,
|
||||||
|
openConfirmDialog,
|
||||||
}: Dependencies) => (
|
}: Dependencies) => (
|
||||||
<Select
|
<Select
|
||||||
menuPortalTarget={null}
|
menuPortalTarget={null}
|
||||||
@ -29,8 +32,7 @@ const NonInjectedHotbarRemoveCommand = observer(({
|
|||||||
}
|
}
|
||||||
|
|
||||||
closeCommandOverlay();
|
closeCommandOverlay();
|
||||||
// TODO: make confirm dialog injectable
|
openConfirmDialog({
|
||||||
ConfirmDialog.open({
|
|
||||||
okButtonProps: {
|
okButtonProps: {
|
||||||
label: "Remove Hotbar",
|
label: "Remove Hotbar",
|
||||||
primary: false,
|
primary: false,
|
||||||
@ -68,8 +70,9 @@ const NonInjectedHotbarRemoveCommand = observer(({
|
|||||||
|
|
||||||
export const HotbarRemoveCommand = withInjectables<Dependencies>(NonInjectedHotbarRemoveCommand, {
|
export const HotbarRemoveCommand = withInjectables<Dependencies>(NonInjectedHotbarRemoveCommand, {
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
|
...props,
|
||||||
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
|
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
|
||||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
hotbarStore: di.inject(hotbarStoreInjectable),
|
||||||
...props,
|
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -14,22 +14,69 @@ import { withTooltip } from "../tooltip";
|
|||||||
import isNumber from "lodash/isNumber";
|
import isNumber from "lodash/isNumber";
|
||||||
import { decode } from "../../../common/utils/base64";
|
import { decode } from "../../../common/utils/base64";
|
||||||
|
|
||||||
export interface IconProps extends React.HTMLAttributes<any> {
|
export interface BaseIconProps {
|
||||||
material?: string; // material-icon, see available names at https://material.io/icons/
|
/**
|
||||||
svg?: string; // svg-filename without extension in current folder
|
* One of the names from https://material.io/icons/
|
||||||
link?: LocationDescriptor; // render icon as NavLink from react-router-dom
|
*/
|
||||||
href?: string; // render icon as hyperlink
|
material?: string;
|
||||||
size?: string | number; // icon-size
|
|
||||||
small?: boolean; // pre-defined icon-size
|
/**
|
||||||
smallest?: boolean; // pre-defined icon-size
|
* Either an SVG data URL or one of the following strings
|
||||||
big?: boolean; // pre-defined icon-size
|
*/
|
||||||
active?: boolean; // apply active-state styles
|
svg?: string;
|
||||||
interactive?: boolean; // indicates that icon is interactive and highlight it on focus/hover
|
|
||||||
focusable?: boolean; // allow focus to the icon + show .active styles (default: "true", when icon is interactive)
|
/**
|
||||||
|
* render icon as NavLink from react-router-dom
|
||||||
|
*/
|
||||||
|
link?: LocationDescriptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* render icon as hyperlink
|
||||||
|
*/
|
||||||
|
href?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The icon size (css units)
|
||||||
|
*/
|
||||||
|
size?: string | number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pre-defined icon-size
|
||||||
|
*/
|
||||||
|
small?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pre-defined icon-size
|
||||||
|
*/
|
||||||
|
smallest?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pre-defined icon-size
|
||||||
|
*/
|
||||||
|
big?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apply active-state styles
|
||||||
|
*/
|
||||||
|
active?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* indicates that icon is interactive and highlight it on focus/hover
|
||||||
|
*/
|
||||||
|
interactive?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow focus to the icon to show `.active` styles. Only applicable if {@link IconProps.interactive} is `true`.
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
focusable?: boolean;
|
||||||
sticker?: boolean;
|
sticker?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IconProps extends React.HTMLAttributes<any>, BaseIconProps {}
|
||||||
|
|
||||||
export function isSvg(content: string): boolean {
|
export function isSvg(content: string): boolean {
|
||||||
// data-url for raw svg-icon
|
// data-url for raw svg-icon
|
||||||
return String(content).includes("svg+xml");
|
return String(content).includes("svg+xml");
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import React from "react";
|
|||||||
import { computed, makeObservable } from "mobx";
|
import { computed, makeObservable } from "mobx";
|
||||||
import { Observer, observer } from "mobx-react";
|
import { Observer, observer } from "mobx-react";
|
||||||
import type { ConfirmDialogParams } from "../confirm-dialog";
|
import type { ConfirmDialogParams } from "../confirm-dialog";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
|
||||||
import type { TableCellProps, TableProps, TableRowProps, TableSortCallbacks } from "../table";
|
import type { TableCellProps, TableProps, TableRowProps, TableSortCallbacks } from "../table";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import type { IClassName } from "../../utils";
|
import type { IClassName } from "../../utils";
|
||||||
@ -30,6 +29,8 @@ import type { ItemListStore } from "./list-layout";
|
|||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import themeStoreInjectable from "../../themes/store.injectable";
|
import themeStoreInjectable from "../../themes/store.injectable";
|
||||||
import pageFiltersStoreInjectable from "./page-filters/store.injectable";
|
import pageFiltersStoreInjectable from "./page-filters/store.injectable";
|
||||||
|
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
|
||||||
|
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
|
||||||
|
|
||||||
export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStores extends boolean> {
|
export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStores extends boolean> {
|
||||||
getFilters: () => Filter[];
|
getFilters: () => Filter[];
|
||||||
@ -69,6 +70,7 @@ export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStor
|
|||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
themeStore: ThemeStore;
|
themeStore: ThemeStore;
|
||||||
pageFiltersStore: PageFiltersStore;
|
pageFiltersStore: PageFiltersStore;
|
||||||
|
openConfirmDialog: OpenConfirmDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -161,7 +163,7 @@ class NonInjectedItemListLayoutContent<
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeItemsDialog(selectedItems: Item[]) {
|
removeItemsDialog(selectedItems: Item[]) {
|
||||||
const { customizeRemoveDialog, store } = this.props;
|
const { customizeRemoveDialog, store, openConfirmDialog } = this.props;
|
||||||
const visibleMaxNamesCount = 5;
|
const visibleMaxNamesCount = 5;
|
||||||
const selectedNames = selectedItems.map(ns => ns.getName()).slice(0, visibleMaxNamesCount).join(", ");
|
const selectedNames = selectedItems.map(ns => ns.getName()).slice(0, visibleMaxNamesCount).join(", ");
|
||||||
const dialogCustomProps = customizeRemoveDialog ? customizeRemoveDialog(selectedItems) : {};
|
const dialogCustomProps = customizeRemoveDialog ? customizeRemoveDialog(selectedItems) : {};
|
||||||
@ -203,7 +205,7 @@ class NonInjectedItemListLayoutContent<
|
|||||||
? removeSelectedItems.bind(store)
|
? removeSelectedItems.bind(store)
|
||||||
: noop;
|
: noop;
|
||||||
|
|
||||||
ConfirmDialog.open({
|
openConfirmDialog({
|
||||||
ok: onConfirm,
|
ok: onConfirm,
|
||||||
labelOk: "Remove",
|
labelOk: "Remove",
|
||||||
message,
|
message,
|
||||||
@ -372,5 +374,6 @@ export const ItemListLayoutContent = withInjectables<Dependencies, ItemListLayou
|
|||||||
...props,
|
...props,
|
||||||
themeStore: di.inject(themeStoreInjectable),
|
themeStore: di.inject(themeStoreInjectable),
|
||||||
pageFiltersStore: di.inject(pageFiltersStoreInjectable),
|
pageFiltersStore: di.inject(pageFiltersStoreInjectable),
|
||||||
|
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
||||||
}),
|
}),
|
||||||
}) as <Item extends ItemObject, PreLoadStores extends boolean>(props: ItemListLayoutContentProps<Item, PreLoadStores>) => React.ReactElement;
|
}) as <Item extends ItemObject, PreLoadStores extends boolean>(props: ItemListLayoutContentProps<Item, PreLoadStores>) => React.ReactElement;
|
||||||
|
|||||||
@ -5,15 +5,14 @@ exports[`kube-object-menu given kube object renders 1`] = `
|
|||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<ul
|
<ul
|
||||||
class="Animate opacity Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
|
class="Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
|
||||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
Some menu item
|
Some menu item
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="MenuItem"
|
class="MenuItem"
|
||||||
data-testid="menu-action-remove"
|
data-testid="menu-action-delete"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
@ -45,15 +44,14 @@ exports[`kube-object-menu given kube object when removing kube object renders 1`
|
|||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<ul
|
<ul
|
||||||
class="Animate opacity Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
|
class="Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
|
||||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
Some menu item
|
Some menu item
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="MenuItem"
|
class="MenuItem"
|
||||||
data-testid="menu-action-remove"
|
data-testid="menu-action-delete"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
@ -78,9 +76,8 @@ exports[`kube-object-menu given kube object when removing kube object renders 1`
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="Animate opacity-scale Dialog flex center ConfirmDialog modal"
|
class="Dialog flex center ConfirmDialog modal"
|
||||||
data-testid="confirmation-dialog"
|
data-testid="confirmation-dialog"
|
||||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="box"
|
class="box"
|
||||||
@ -99,19 +96,19 @@ exports[`kube-object-menu given kube object when removing kube object renders 1`
|
|||||||
</span>
|
</span>
|
||||||
</i>
|
</i>
|
||||||
|
|
||||||
<div>
|
<p>
|
||||||
<p>
|
Remove
|
||||||
Remove some-kind
|
some-kind
|
||||||
<b>
|
|
||||||
some-namespace/some-name
|
<b>
|
||||||
</b>
|
some-namespace/some-name
|
||||||
from
|
</b>
|
||||||
<b>
|
from
|
||||||
Some name
|
<b>
|
||||||
</b>
|
Some name
|
||||||
?
|
</b>
|
||||||
</p>
|
?
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="confirm-buttons"
|
class="confirm-buttons"
|
||||||
@ -140,15 +137,14 @@ exports[`kube-object-menu given kube object with namespace when removing kube ob
|
|||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<ul
|
<ul
|
||||||
class="Animate opacity Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
|
class="Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
|
||||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
Some menu item
|
Some menu item
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="MenuItem"
|
class="MenuItem"
|
||||||
data-testid="menu-action-remove"
|
data-testid="menu-action-delete"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
@ -173,9 +169,8 @@ exports[`kube-object-menu given kube object with namespace when removing kube ob
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="Animate opacity-scale Dialog flex center ConfirmDialog modal"
|
class="Dialog flex center ConfirmDialog modal"
|
||||||
data-testid="confirmation-dialog"
|
data-testid="confirmation-dialog"
|
||||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="box"
|
class="box"
|
||||||
@ -194,19 +189,19 @@ exports[`kube-object-menu given kube object with namespace when removing kube ob
|
|||||||
</span>
|
</span>
|
||||||
</i>
|
</i>
|
||||||
|
|
||||||
<div>
|
<p>
|
||||||
<p>
|
Remove
|
||||||
Remove some-kind
|
some-kind
|
||||||
<b>
|
|
||||||
some-namespace/some-name
|
<b>
|
||||||
</b>
|
some-namespace/some-name
|
||||||
from
|
</b>
|
||||||
<b>
|
from
|
||||||
Some name
|
<b>
|
||||||
</b>
|
Some name
|
||||||
?
|
</b>
|
||||||
</p>
|
?
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="confirm-buttons"
|
class="confirm-buttons"
|
||||||
@ -235,15 +230,14 @@ exports[`kube-object-menu given kube object without namespace when removing kube
|
|||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<ul
|
<ul
|
||||||
class="Animate opacity Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
|
class="Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
|
||||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
Some menu item
|
Some menu item
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="MenuItem"
|
class="MenuItem"
|
||||||
data-testid="menu-action-remove"
|
data-testid="menu-action-delete"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
@ -268,9 +262,8 @@ exports[`kube-object-menu given kube object without namespace when removing kube
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="Animate opacity-scale Dialog flex center ConfirmDialog modal"
|
class="Dialog flex center ConfirmDialog modal"
|
||||||
data-testid="confirmation-dialog"
|
data-testid="confirmation-dialog"
|
||||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="box"
|
class="box"
|
||||||
@ -289,19 +282,19 @@ exports[`kube-object-menu given kube object without namespace when removing kube
|
|||||||
</span>
|
</span>
|
||||||
</i>
|
</i>
|
||||||
|
|
||||||
<div>
|
<p>
|
||||||
<p>
|
Remove
|
||||||
Remove some-kind
|
some-kind
|
||||||
<b>
|
|
||||||
some-name
|
<b>
|
||||||
</b>
|
some-name
|
||||||
from
|
</b>
|
||||||
<b>
|
from
|
||||||
Some name
|
<b>
|
||||||
</b>
|
Some name
|
||||||
?
|
</b>
|
||||||
</p>
|
?
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="confirm-buttons"
|
class="confirm-buttons"
|
||||||
@ -329,8 +322,7 @@ exports[`kube-object-menu given no kube object, renders 1`] = `
|
|||||||
<body>
|
<body>
|
||||||
<div>
|
<div>
|
||||||
<ul
|
<ul
|
||||||
class="Animate opacity Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
|
class="Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
|
||||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
import { conforms, includes, eq } from "lodash/fp";
|
import { conforms, includes, eq } from "lodash/fp";
|
||||||
import type { KubeObject } from "../../../../../common/k8s-api/kube-object";
|
import type { KubeObject } from "../../../../../common/k8s-api/kube-object";
|
||||||
import type { LensRendererExtension } from "../../../../../extensions/lens-renderer-extension";
|
import type { LensRendererExtension } from "../../../../../extensions/lens-renderer-extension";
|
||||||
import { staticKubeObjectMenuItems as staticMenuItems } from "./static-kube-object-menu-items";
|
import { staticKubeObjectMenuItems as staticMenuItems } from "../static-kube-object-menu-items";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
extensions: LensRendererExtension[];
|
extensions: LensRendererExtension[];
|
||||||
|
|||||||
@ -2,12 +2,10 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { ServiceAccountMenu } from "../../../+user-management/+service-accounts/service-account-menu";
|
import { ServiceAccountMenu } from "../../+user-management/+service-accounts/service-account-menu";
|
||||||
import { CronJobMenu } from "../../../+workloads-cronjobs/cron-job-menu";
|
import { CronJobMenu } from "../../+workloads-cronjobs/cron-job-menu";
|
||||||
import { DeploymentMenu } from "../../../+workloads-deployments/deployment-menu";
|
import { DeploymentMenu } from "../../+workloads-deployments/deployment-menu";
|
||||||
import { ReplicaSetMenu } from "../../../+workloads-replicasets/replica-set-menu";
|
import { ReplicaSetMenu } from "../../+workloads-replicasets/replica-set-menu";
|
||||||
import { StatefulSetMenu } from "../../../+workloads-statefulsets/stateful-set-menu";
|
|
||||||
import type { KubeObjectMenuRegistration } from "./kube-object-menu-registration";
|
|
||||||
|
|
||||||
export const staticKubeObjectMenuItems = [
|
export const staticKubeObjectMenuItems = [
|
||||||
{
|
{
|
||||||
@ -38,11 +36,4 @@ export const staticKubeObjectMenuItems = [
|
|||||||
MenuItem: ReplicaSetMenu,
|
MenuItem: ReplicaSetMenu,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
];
|
||||||
kind: "StatefulSet",
|
|
||||||
apiVersions: ["apps/v1"],
|
|
||||||
components: {
|
|
||||||
MenuItem: StatefulSetMenu,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as KubeObjectMenuRegistration[];
|
|
||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import type { KubeObject } from "../../../../../common/k8s-api/kube-object";
|
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
|
|
||||||
export interface KubeObjectMenuItemProps {
|
export interface KubeObjectMenuItemProps {
|
||||||
object: KubeObject;
|
object: KubeObject;
|
||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { screen } from "@testing-library/react";
|
import { screen, waitFor } from "@testing-library/react";
|
||||||
import "@testing-library/jest-dom/extend-expect";
|
import "@testing-library/jest-dom/extend-expect";
|
||||||
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
@ -20,7 +20,7 @@ import type { Cluster } from "../../../common/cluster/cluster";
|
|||||||
import type { ApiManager } from "../../../common/k8s-api/api-manager";
|
import type { ApiManager } from "../../../common/k8s-api/api-manager";
|
||||||
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
|
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
|
||||||
import { KubeObjectMenu } from "./index";
|
import { KubeObjectMenu } from "./index";
|
||||||
import type { KubeObjectMenuRegistration } from "./dependencies/kube-object-menu-items/kube-object-menu-registration";
|
import type { KubeObjectMenuRegistration } from "./kube-object-menu-registration";
|
||||||
import { computed } from "mobx";
|
import { computed } from "mobx";
|
||||||
import { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
import { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
||||||
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
|
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
|
||||||
@ -33,6 +33,8 @@ jest.mock("../tooltip/withTooltip", () => ({
|
|||||||
withTooltip: (target: any) => target,
|
withTooltip: (target: any) => target,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// TODO: make `animated={false}` not required to make tests deterministic
|
||||||
|
|
||||||
class SomeTestExtension extends LensRendererExtension {
|
class SomeTestExtension extends LensRendererExtension {
|
||||||
constructor(
|
constructor(
|
||||||
kubeObjectMenuItems: KubeObjectMenuRegistration[],
|
kubeObjectMenuItems: KubeObjectMenuRegistration[],
|
||||||
@ -143,7 +145,7 @@ describe("kube-object-menu", () => {
|
|||||||
|
|
||||||
({ baseElement } = render(
|
({ baseElement } = render(
|
||||||
<div>
|
<div>
|
||||||
<ConfirmDialog />
|
<ConfirmDialog animated={false} />
|
||||||
|
|
||||||
<KubeObjectMenu
|
<KubeObjectMenu
|
||||||
object={objectStub}
|
object={objectStub}
|
||||||
@ -163,23 +165,18 @@ describe("kube-object-menu", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("when removing kube object", () => {
|
describe("when removing kube object", () => {
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
const menuItem = screen.getByTestId("menu-action-remove");
|
userEvent.click(await screen.findByTestId("menu-action-delete"));
|
||||||
|
|
||||||
userEvent.click(menuItem);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", async () => {
|
||||||
|
await screen.findByTestId("confirmation-dialog");
|
||||||
expect(baseElement).toMatchSnapshot();
|
expect(baseElement).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("opens a confirmation dialog", () => {
|
|
||||||
screen.getByTestId("confirmation-dialog");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when remove is confirmed", () => {
|
describe("when remove is confirmed", () => {
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
const confirmRemovalButton = screen.getByTestId("confirm");
|
const confirmRemovalButton = await screen.findByTestId("confirm");
|
||||||
|
|
||||||
userEvent.click(confirmRemovalButton);
|
userEvent.click(confirmRemovalButton);
|
||||||
});
|
});
|
||||||
@ -188,14 +185,15 @@ describe("kube-object-menu", () => {
|
|||||||
expect(removeActionMock).toHaveBeenCalledWith();
|
expect(removeActionMock).toHaveBeenCalledWith();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not close the confirmation dialog yet", () => {
|
it("does not close the confirmation dialog yet", async () => {
|
||||||
screen.getByTestId("confirmation-dialog");
|
await screen.findByTestId("confirmation-dialog");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when removal resolves, closes the confirmation dialog", async () => {
|
it("when removal resolves, closes the confirmation dialog", async () => {
|
||||||
await removeActionMock.resolve();
|
await removeActionMock.resolve();
|
||||||
|
await waitFor(() => {
|
||||||
expect(screen.queryByTestId("confirmation-dialog")).toBeNull();
|
expect(screen.queryByTestId("confirmation-dialog")).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -219,7 +217,7 @@ describe("kube-object-menu", () => {
|
|||||||
|
|
||||||
({ baseElement } = render(
|
({ baseElement } = render(
|
||||||
<div>
|
<div>
|
||||||
<ConfirmDialog />
|
<ConfirmDialog animated={false} />
|
||||||
|
|
||||||
<KubeObjectMenu
|
<KubeObjectMenu
|
||||||
object={objectStub}
|
object={objectStub}
|
||||||
@ -230,8 +228,8 @@ describe("kube-object-menu", () => {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when removing kube object, renders confirmation dialog with namespace", () => {
|
it("when removing kube object, renders confirmation dialog with namespace", async () => {
|
||||||
const menuItem = screen.getByTestId("menu-action-remove");
|
const menuItem = await screen.findByTestId("menu-action-delete");
|
||||||
|
|
||||||
userEvent.click(menuItem);
|
userEvent.click(menuItem);
|
||||||
|
|
||||||
@ -257,7 +255,7 @@ describe("kube-object-menu", () => {
|
|||||||
|
|
||||||
({ baseElement } = render(
|
({ baseElement } = render(
|
||||||
<div>
|
<div>
|
||||||
<ConfirmDialog />
|
<ConfirmDialog animated={false} />
|
||||||
|
|
||||||
<KubeObjectMenu
|
<KubeObjectMenu
|
||||||
object={objectStub}
|
object={objectStub}
|
||||||
@ -268,8 +266,8 @@ describe("kube-object-menu", () => {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when removing kube object, renders confirmation dialog without namespace", () => {
|
it("when removing kube object, renders confirmation dialog without namespace", async () => {
|
||||||
const menuItem = screen.getByTestId("menu-action-remove");
|
const menuItem = await screen.findByTestId("menu-action-delete");
|
||||||
|
|
||||||
userEvent.click(menuItem);
|
userEvent.click(menuItem);
|
||||||
|
|
||||||
|
|||||||
@ -4,10 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { autoBind, cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
import type { MenuActionsProps } from "../menu";
|
import type { MenuActionsProps } from "../menu";
|
||||||
import { MenuActions } from "../menu";
|
import { MenuItem, MenuActions } from "../menu";
|
||||||
import identity from "lodash/identity";
|
import identity from "lodash/identity";
|
||||||
import type { ApiManager } from "../../../common/k8s-api/api-manager";
|
import type { ApiManager } from "../../../common/k8s-api/api-manager";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
@ -17,6 +17,16 @@ import kubeObjectMenuItemsInjectable from "./dependencies/kube-object-menu-items
|
|||||||
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
|
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
|
||||||
import type { HideDetails } from "../kube-detail-params/hide-details.injectable";
|
import type { HideDetails } from "../kube-detail-params/hide-details.injectable";
|
||||||
import hideDetailsInjectable from "../kube-detail-params/hide-details.injectable";
|
import hideDetailsInjectable from "../kube-detail-params/hide-details.injectable";
|
||||||
|
import type { OnKubeObjectContextMenuOpen } from "./on-context-menu-open.injectable";
|
||||||
|
import onKubeObjectContextMenuOpenInjectable from "./on-context-menu-open.injectable";
|
||||||
|
import type { KubeObjectContextMenuItem } from "../../kube-object/handler";
|
||||||
|
import { observable, runInAction } from "mobx";
|
||||||
|
import type { WithConfirmation } from "../confirm-dialog/with-confirm.injectable";
|
||||||
|
import type { Navigate } from "../../navigation/navigate.injectable";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
import navigateInjectable from "../../navigation/navigate.injectable";
|
||||||
|
import withConfirmationInjectable from "../confirm-dialog/with-confirm.injectable";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
|
||||||
export interface KubeObjectMenuProps<TKubeObject extends KubeObject> extends MenuActionsProps {
|
export interface KubeObjectMenuProps<TKubeObject extends KubeObject> extends MenuActionsProps {
|
||||||
object: TKubeObject;
|
object: TKubeObject;
|
||||||
@ -30,51 +40,17 @@ interface Dependencies {
|
|||||||
clusterName: string | undefined;
|
clusterName: string | undefined;
|
||||||
hideDetails: HideDetails;
|
hideDetails: HideDetails;
|
||||||
createEditResourceTab: (kubeObject: KubeObject) => void;
|
createEditResourceTab: (kubeObject: KubeObject) => void;
|
||||||
|
onContextMenuOpen: OnKubeObjectContextMenuOpen;
|
||||||
|
withConfirmation: WithConfirmation;
|
||||||
|
navigate: Navigate;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NonInjectedKubeObjectMenu<TKubeObject extends KubeObject, Props extends KubeObjectMenuProps<TKubeObject> & Dependencies> extends React.Component<Props> {
|
@observer
|
||||||
constructor(props: Props) {
|
class NonInjectedKubeObjectMenu<Kube extends KubeObject> extends React.Component<KubeObjectMenuProps<Kube> & Dependencies> {
|
||||||
super(props);
|
private menuItems = observable.array<KubeObjectContextMenuItem>();
|
||||||
autoBind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
get store() {
|
|
||||||
const { object } = this.props;
|
|
||||||
|
|
||||||
if (!object?.selfLink) return null;
|
|
||||||
|
|
||||||
return this.props.apiManager.getStore(object.selfLink);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isEditable() {
|
|
||||||
return this.props.editable ?? Boolean(this.store?.patch);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRemovable() {
|
|
||||||
return this.props.removable ?? Boolean(this.store?.remove);
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(object: KubeObject) {
|
|
||||||
this.props.hideDetails();
|
|
||||||
this.props.createEditResourceTab(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(object: KubeObject) {
|
|
||||||
const { hideDetails, removeAction = this.store?.remove } = this.props;
|
|
||||||
|
|
||||||
hideDetails();
|
|
||||||
await removeAction?.(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderRemoveMessage() {
|
|
||||||
const { object } = this.props;
|
|
||||||
|
|
||||||
if (!object) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private renderRemoveMessage(object: KubeObject) {
|
||||||
const breadcrumbParts = [object.getNs(), object.getName()];
|
const breadcrumbParts = [object.getNs(), object.getName()];
|
||||||
|
|
||||||
const breadcrumb = breadcrumbParts.filter(identity).join("/");
|
const breadcrumb = breadcrumbParts.filter(identity).join("/");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -88,10 +64,8 @@ class NonInjectedKubeObjectMenu<TKubeObject extends KubeObject, Props extends Ku
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenuItems(object: KubeObject, toolbar: boolean): React.ReactChild[] {
|
private renderMenuItems() {
|
||||||
if (!object) {
|
const { object, toolbar } = this.props;
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.props.kubeObjectMenuItems.map((MenuItem, index) => (
|
return this.props.kubeObjectMenuItems.map((MenuItem, index) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@ -102,25 +76,123 @@ class NonInjectedKubeObjectMenu<TKubeObject extends KubeObject, Props extends Ku
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private emitOnContextMenuOpen(object: KubeObject) {
|
||||||
|
const {
|
||||||
|
apiManager,
|
||||||
|
editable,
|
||||||
|
removable,
|
||||||
|
hideDetails,
|
||||||
|
createEditResourceTab,
|
||||||
|
withConfirmation,
|
||||||
|
removeAction,
|
||||||
|
onContextMenuOpen,
|
||||||
|
navigate,
|
||||||
|
updateAction,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const store = apiManager.getStore(object.selfLink);
|
||||||
|
const isEditable = editable ?? (Boolean(store?.patch) || Boolean(updateAction));
|
||||||
|
const isRemovable = removable ?? (Boolean(store?.remove) || Boolean(removeAction));
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.menuItems.clear();
|
||||||
|
|
||||||
|
if (isRemovable) {
|
||||||
|
this.menuItems.push({
|
||||||
|
title: "Delete",
|
||||||
|
icon: "delete",
|
||||||
|
onClick: withConfirmation({
|
||||||
|
message: this.renderRemoveMessage(object),
|
||||||
|
labelOk: "Remove",
|
||||||
|
ok: async () => {
|
||||||
|
hideDetails();
|
||||||
|
|
||||||
|
if (removeAction) {
|
||||||
|
await removeAction();
|
||||||
|
} else if (store?.remove) {
|
||||||
|
await store.remove(object);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEditable) {
|
||||||
|
this.menuItems.push({
|
||||||
|
title: "Edit",
|
||||||
|
icon: "edit",
|
||||||
|
onClick: async () => {
|
||||||
|
hideDetails();
|
||||||
|
|
||||||
|
if (updateAction) {
|
||||||
|
await updateAction();
|
||||||
|
} else {
|
||||||
|
createEditResourceTab(object);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onContextMenuOpen(object, {
|
||||||
|
menuItems: this.menuItems,
|
||||||
|
navigate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderContextMenuItems = (object: KubeObject) => (
|
||||||
|
[...this.menuItems]
|
||||||
|
.reverse() // This is done because the order that we "grow" is right->left
|
||||||
|
.map(({ icon, ...rest }) => ({
|
||||||
|
...rest,
|
||||||
|
icon: typeof icon === "string"
|
||||||
|
? { material: icon }
|
||||||
|
: icon,
|
||||||
|
}))
|
||||||
|
.map((item, index) => (
|
||||||
|
<MenuItem
|
||||||
|
key={`context-menu-item-${index}`}
|
||||||
|
onClick={() => item.onClick(object)}
|
||||||
|
data-testid={`menu-action-${item.title.toLowerCase().replace(/\s+/, "-")}`}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
{...item.icon}
|
||||||
|
interactive={this.props.toolbar}
|
||||||
|
tooltip={item.title}
|
||||||
|
/>
|
||||||
|
<span className="title">
|
||||||
|
{item.title}
|
||||||
|
</span>
|
||||||
|
</MenuItem>
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { remove, update, renderRemoveMessage, isEditable, isRemovable } = this;
|
const {
|
||||||
const { className, editable, removable, object, ...menuProps } = this.props;
|
className,
|
||||||
|
editable,
|
||||||
|
removable,
|
||||||
|
object,
|
||||||
|
removeAction, // This is here so we don't pass it down to `<MenuAction>`
|
||||||
|
removeConfirmationMessage, // This is here so we don't pass it down to `<MenuAction>`
|
||||||
|
updateAction, // This is here so we don't pass it down to `<MenuAction>`
|
||||||
|
...menuProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuActions
|
<MenuActions
|
||||||
className={cssNames("KubeObjectMenu", className)}
|
className={cssNames("KubeObjectMenu", className)}
|
||||||
updateAction={(object && isEditable) ? (() => update(object)) : undefined}
|
onOpen={object ? () => this.emitOnContextMenuOpen(object) : undefined}
|
||||||
removeAction={(object && isRemovable) ? (() => remove(object)) : undefined}
|
|
||||||
removeConfirmationMessage={renderRemoveMessage}
|
|
||||||
{...menuProps}
|
{...menuProps}
|
||||||
>
|
>
|
||||||
{this.getMenuItems(object, this.props.toolbar ?? false)}
|
{this.renderMenuItems()}
|
||||||
|
{object && this.renderContextMenuItems(object)}
|
||||||
</MenuActions>
|
</MenuActions>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const InjectedKubeObjectMenu = withInjectables<Dependencies, KubeObjectMenuProps<KubeObject>>(NonInjectedKubeObjectMenu, {
|
export const KubeObjectMenu = withInjectables<Dependencies, KubeObjectMenuProps<KubeObject>>(NonInjectedKubeObjectMenu, {
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
...props,
|
...props,
|
||||||
clusterName: di.inject(clusterNameInjectable),
|
clusterName: di.inject(clusterNameInjectable),
|
||||||
@ -130,9 +202,8 @@ const InjectedKubeObjectMenu = withInjectables<Dependencies, KubeObjectMenuProps
|
|||||||
kubeObjectMenuItems: di.inject(kubeObjectMenuItemsInjectable, {
|
kubeObjectMenuItems: di.inject(kubeObjectMenuItemsInjectable, {
|
||||||
kubeObject: props.object,
|
kubeObject: props.object,
|
||||||
}),
|
}),
|
||||||
|
onContextMenuOpen: di.inject(onKubeObjectContextMenuOpenInjectable),
|
||||||
|
navigate: di.inject(navigateInjectable),
|
||||||
|
withConfirmation: di.inject(withConfirmationInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
}) as <T extends KubeObject>(props: KubeObjectMenuProps<T>) => React.ReactElement;
|
||||||
|
|
||||||
export function KubeObjectMenu<T extends KubeObject>(props: KubeObjectMenuProps<T>) {
|
|
||||||
return <InjectedKubeObjectMenu {...props} />;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
|
import type { KubeObjectOnContextMenuOpenContext } from "../../kube-object/handler";
|
||||||
|
import kubeObjectHandlersInjectable from "../../kube-object/handlers.injectable";
|
||||||
|
|
||||||
|
export type OnKubeObjectContextMenuOpen = (obj: KubeObject, ctx: KubeObjectOnContextMenuOpenContext) => void;
|
||||||
|
|
||||||
|
const onKubeObjectContextMenuOpenInjectable = getInjectable({
|
||||||
|
id: "on-kube-object-context-menu-open",
|
||||||
|
instantiate: (di): OnKubeObjectContextMenuOpen => {
|
||||||
|
const handlers = di.inject(kubeObjectHandlersInjectable);
|
||||||
|
|
||||||
|
return (obj, ctx) => {
|
||||||
|
const specificHandlers = handlers.get().get(obj.apiVersion)?.get(obj.kind) ?? [];
|
||||||
|
|
||||||
|
for (const { onContextMenuOpen } of specificHandlers) {
|
||||||
|
onContextMenuOpen?.(ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default onKubeObjectContextMenuOpenInjectable;
|
||||||
@ -7,13 +7,11 @@ import styles from "./sidebar-cluster.module.scss";
|
|||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { broadcastMessage } from "../../../common/ipc";
|
import { broadcastMessage } from "../../../common/ipc";
|
||||||
import type { CatalogEntity, CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
|
import type { CatalogEntity, CatalogEntityContextMenu } from "../../api/catalog-entity";
|
||||||
import { IpcRendererNavigationEvents } from "../../navigation/events";
|
import { IpcRendererNavigationEvents } from "../../navigation/events";
|
||||||
import { Avatar } from "../avatar";
|
import { Avatar } from "../avatar";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { navigate } from "../../navigation";
|
|
||||||
import { Menu, MenuItem } from "../menu";
|
import { Menu, MenuItem } from "../menu";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
|
||||||
import { Tooltip } from "../tooltip";
|
import { Tooltip } from "../tooltip";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
||||||
@ -21,67 +19,45 @@ import type { HotbarStore } from "../../../common/hotbars/store";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { OnContextMenuOpen } from "../../../common/catalog/on-context-menu-open.injectable";
|
import type { OnContextMenuOpen } from "../../../common/catalog/on-context-menu-open.injectable";
|
||||||
import onContextMenuOpenInjectable from "../../../common/catalog/on-context-menu-open.injectable";
|
import onContextMenuOpenInjectable from "../../../common/catalog/on-context-menu-open.injectable";
|
||||||
|
import type { Navigate } from "../../navigation/navigate.injectable";
|
||||||
|
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
|
||||||
|
import navigateInjectable from "../../navigation/navigate.injectable";
|
||||||
|
import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable";
|
||||||
|
|
||||||
const contextMenu: CatalogEntityContextMenuContext = observable({
|
export interface SidebarClusterProps {
|
||||||
menuItems: [],
|
clusterEntity: CatalogEntity;
|
||||||
navigate: (url: string, forceMainFrame = true) => {
|
|
||||||
if (forceMainFrame) {
|
|
||||||
broadcastMessage(IpcRendererNavigationEvents.NAVIGATE_IN_APP, url);
|
|
||||||
} else {
|
|
||||||
navigate(url);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
|
||||||
if (menuItem.confirm) {
|
|
||||||
ConfirmDialog.open({
|
|
||||||
okButtonProps: {
|
|
||||||
primary: false,
|
|
||||||
accent: true,
|
|
||||||
},
|
|
||||||
ok: () => {
|
|
||||||
menuItem.onClick();
|
|
||||||
},
|
|
||||||
message: menuItem.confirm.message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
menuItem.onClick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderLoadingSidebarCluster() {
|
|
||||||
return (
|
|
||||||
<div className={styles.SidebarCluster}>
|
|
||||||
<Avatar
|
|
||||||
title="??"
|
|
||||||
background="var(--halfGray)"
|
|
||||||
size={40}
|
|
||||||
className={styles.loadingAvatar}
|
|
||||||
/>
|
|
||||||
<div className={styles.loadingClusterName} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
|
navigate: Navigate;
|
||||||
|
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
|
||||||
hotbarStore: HotbarStore;
|
hotbarStore: HotbarStore;
|
||||||
onContextMenuOpen: OnContextMenuOpen;
|
onContextMenuOpen: OnContextMenuOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SidebarClusterProps {
|
|
||||||
clusterEntity: CatalogEntity | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NonInjectedSidebarCluster = observer(({
|
const NonInjectedSidebarCluster = observer(({
|
||||||
clusterEntity,
|
clusterEntity,
|
||||||
hotbarStore,
|
hotbarStore,
|
||||||
onContextMenuOpen,
|
onContextMenuOpen,
|
||||||
|
navigate,
|
||||||
|
normalizeMenuItem,
|
||||||
}: Dependencies & SidebarClusterProps) => {
|
}: Dependencies & SidebarClusterProps) => {
|
||||||
const [opened, setOpened] = useState(false);
|
const [menuItems] = useState(observable.array<CatalogEntityContextMenu>());
|
||||||
|
const [opened, setOpened] = useState(false );
|
||||||
|
|
||||||
if (!clusterEntity) {
|
if (!clusterEntity) {
|
||||||
return renderLoadingSidebarCluster();
|
// render a Loading version of the SidebarCluster
|
||||||
|
return (
|
||||||
|
<div className={styles.SidebarCluster}>
|
||||||
|
<Avatar
|
||||||
|
title="??"
|
||||||
|
background="var(--halfGray)"
|
||||||
|
size={40}
|
||||||
|
className={styles.loadingAvatar}
|
||||||
|
/>
|
||||||
|
<div className={styles.loadingClusterName} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMenuOpen = () => {
|
const onMenuOpen = () => {
|
||||||
@ -93,8 +69,17 @@ const NonInjectedSidebarCluster = observer(({
|
|||||||
? () => hotbarStore.removeFromHotbar(clusterEntity.getId())
|
? () => hotbarStore.removeFromHotbar(clusterEntity.getId())
|
||||||
: () => hotbarStore.addToHotbar(clusterEntity);
|
: () => hotbarStore.addToHotbar(clusterEntity);
|
||||||
|
|
||||||
contextMenu.menuItems = [{ title, onClick }];
|
menuItems.replace([{ title, onClick }]);
|
||||||
onContextMenuOpen(clusterEntity, contextMenu);
|
onContextMenuOpen(clusterEntity, {
|
||||||
|
menuItems,
|
||||||
|
navigate: (url, forceMainFrame = true) => {
|
||||||
|
if (forceMainFrame) {
|
||||||
|
broadcastMessage(IpcRendererNavigationEvents.NAVIGATE_IN_APP, url);
|
||||||
|
} else {
|
||||||
|
navigate(url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
toggle();
|
toggle();
|
||||||
};
|
};
|
||||||
@ -146,11 +131,13 @@ const NonInjectedSidebarCluster = observer(({
|
|||||||
className={styles.menu}
|
className={styles.menu}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
contextMenu.menuItems.map((menuItem) => (
|
menuItems
|
||||||
<MenuItem key={menuItem.title} onClick={() => onMenuItemClick(menuItem)}>
|
.map(normalizeMenuItem)
|
||||||
{menuItem.title}
|
.map((menuItem) => (
|
||||||
</MenuItem>
|
<MenuItem key={menuItem.title} onClick={menuItem.onClick}>
|
||||||
))
|
{menuItem.title}
|
||||||
|
</MenuItem>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
@ -162,5 +149,7 @@ export const SidebarCluster = withInjectables<Dependencies, SidebarClusterProps>
|
|||||||
...props,
|
...props,
|
||||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
hotbarStore: di.inject(hotbarStoreInjectable),
|
||||||
onContextMenuOpen: di.inject(onContextMenuOpenInjectable),
|
onContextMenuOpen: di.inject(onContextMenuOpenInjectable),
|
||||||
|
navigate: di.inject(navigateInjectable),
|
||||||
|
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,10 +6,9 @@
|
|||||||
import "./menu-actions.scss";
|
import "./menu-actions.scss";
|
||||||
|
|
||||||
import React, { isValidElement } from "react";
|
import React, { isValidElement } from "react";
|
||||||
import { observable, makeObservable } from "mobx";
|
import { observable, makeObservable, reaction } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { autoBind, cssNames } from "../../utils";
|
import { autoBind, cssNames } from "../../utils";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
|
||||||
import type { IconProps } from "../icon";
|
import type { IconProps } from "../icon";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import type { MenuProps } from "./menu";
|
import type { MenuProps } from "./menu";
|
||||||
@ -17,24 +16,38 @@ import { Menu, MenuItem } from "./menu";
|
|||||||
import uniqueId from "lodash/uniqueId";
|
import uniqueId from "lodash/uniqueId";
|
||||||
import isString from "lodash/isString";
|
import isString from "lodash/isString";
|
||||||
import type { TooltipDecoratorProps } from "../tooltip";
|
import type { TooltipDecoratorProps } from "../tooltip";
|
||||||
|
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
|
||||||
|
|
||||||
export interface MenuActionsProps extends Partial<MenuProps> {
|
export interface MenuActionsProps extends Partial<MenuProps> {
|
||||||
className?: string;
|
className?: string;
|
||||||
toolbar?: boolean; // display menu as toolbar with icons
|
toolbar?: boolean; // display menu as toolbar with icons
|
||||||
autoCloseOnSelect?: boolean;
|
autoCloseOnSelect?: boolean;
|
||||||
triggerIcon?: string | (IconProps & TooltipDecoratorProps) | React.ReactNode;
|
triggerIcon?: string | (IconProps & TooltipDecoratorProps) | React.ReactNode;
|
||||||
|
/**
|
||||||
|
* @deprecated Provide your own remove `<MenuItem>` as part of the `children` passed to this component
|
||||||
|
*/
|
||||||
removeConfirmationMessage?: React.ReactNode | (() => React.ReactNode);
|
removeConfirmationMessage?: React.ReactNode | (() => React.ReactNode);
|
||||||
|
/**
|
||||||
|
* @deprecated Provide your own update `<MenuItem>` as part of the `children` passed to this component
|
||||||
|
*/
|
||||||
updateAction?: () => void | Promise<void>;
|
updateAction?: () => void | Promise<void>;
|
||||||
|
/**
|
||||||
|
* @deprecated Provide your own remove `<MenuItem>` as part of the `children` passed to this component
|
||||||
|
*/
|
||||||
removeAction?: () => void | Promise<void>;
|
removeAction?: () => void | Promise<void>;
|
||||||
onOpen?(): void;
|
onOpen?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
openConfirmDialog: OpenConfirmDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class MenuActions extends React.Component<MenuActionsProps> {
|
class NonInjectedMenuActions extends React.Component<MenuActionsProps & Dependencies> {
|
||||||
static defaultProps: MenuActionsProps = {
|
static defaultProps = {
|
||||||
get removeConfirmationMessage() {
|
removeConfirmationMessage: "Remove item?",
|
||||||
return `Remove item?`;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public id = uniqueId("menu_actions_");
|
public id = uniqueId("menu_actions_");
|
||||||
@ -46,22 +59,34 @@ export class MenuActions extends React.Component<MenuActionsProps> {
|
|||||||
this.isOpen = !this.isOpen;
|
this.isOpen = !this.isOpen;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: MenuActionsProps) {
|
constructor(props: MenuActionsProps & Dependencies) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
disposeOnUnmount(this, [
|
||||||
|
reaction(() => this.isOpen, (isOpen) => {
|
||||||
|
if (isOpen) {
|
||||||
|
this.props.onOpen?.();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
fireImmediately: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
remove() {
|
remove() {
|
||||||
const { removeAction } = this.props;
|
const { removeAction, openConfirmDialog } = this.props;
|
||||||
let { removeConfirmationMessage } = this.props;
|
let { removeConfirmationMessage } = this.props;
|
||||||
|
|
||||||
if (typeof removeConfirmationMessage === "function") {
|
if (typeof removeConfirmationMessage === "function") {
|
||||||
removeConfirmationMessage = removeConfirmationMessage();
|
removeConfirmationMessage = removeConfirmationMessage();
|
||||||
}
|
}
|
||||||
ConfirmDialog.open({
|
openConfirmDialog({
|
||||||
ok: removeAction,
|
ok: removeAction,
|
||||||
labelOk: `Remove`,
|
labelOk: "Remove",
|
||||||
message: <div>{removeConfirmationMessage}</div>,
|
message: <div>{removeConfirmationMessage}</div>,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -84,10 +109,6 @@ export class MenuActions extends React.Component<MenuActionsProps> {
|
|||||||
...(typeof triggerIcon === "object" ? triggerIcon : {}),
|
...(typeof triggerIcon === "object" ? triggerIcon : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.props.onOpen) {
|
|
||||||
iconProps.onClick = this.props.onOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (iconProps.tooltip && this.isOpen) {
|
if (iconProps.tooltip && this.isOpen) {
|
||||||
delete iconProps.tooltip; // don't show tooltip for icon when menu is open
|
delete iconProps.tooltip; // don't show tooltip for icon when menu is open
|
||||||
}
|
}
|
||||||
@ -102,10 +123,6 @@ export class MenuActions extends React.Component<MenuActionsProps> {
|
|||||||
className, toolbar, autoCloseOnSelect, children, updateAction, removeAction, triggerIcon, removeConfirmationMessage,
|
className, toolbar, autoCloseOnSelect, children, updateAction, removeAction, triggerIcon, removeConfirmationMessage,
|
||||||
...menuProps
|
...menuProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const menuClassName = cssNames("MenuActions flex", className, {
|
|
||||||
toolbar,
|
|
||||||
gaps: toolbar, // add spacing for .flex
|
|
||||||
});
|
|
||||||
const autoClose = !toolbar;
|
const autoClose = !toolbar;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -117,10 +134,14 @@ export class MenuActions extends React.Component<MenuActionsProps> {
|
|||||||
isOpen={this.isOpen}
|
isOpen={this.isOpen}
|
||||||
open={this.toggle}
|
open={this.toggle}
|
||||||
close={this.toggle}
|
close={this.toggle}
|
||||||
className={menuClassName}
|
className={cssNames("MenuActions flex", className, {
|
||||||
|
toolbar,
|
||||||
|
gaps: toolbar, // add spacing for .flex
|
||||||
|
})}
|
||||||
|
animated={!toolbar}
|
||||||
usePortal={autoClose}
|
usePortal={autoClose}
|
||||||
closeOnScroll={autoClose}
|
closeOnScroll={autoClose}
|
||||||
closeOnClickItem={autoCloseOnSelect ?? autoClose }
|
closeOnClickItem={autoCloseOnSelect ?? autoClose}
|
||||||
closeOnClickOutside={autoClose}
|
closeOnClickOutside={autoClose}
|
||||||
{...menuProps}
|
{...menuProps}
|
||||||
>
|
>
|
||||||
@ -150,3 +171,10 @@ export class MenuActions extends React.Component<MenuActionsProps> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MenuActions = withInjectables<Dependencies, MenuActionsProps>(NonInjectedMenuActions, {
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
...props,
|
||||||
|
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|||||||
@ -42,6 +42,7 @@ export interface MenuProps {
|
|||||||
closeOnScroll?: boolean; // applicable when usePortal={true}
|
closeOnScroll?: boolean; // applicable when usePortal={true}
|
||||||
position?: MenuPosition; // applicable when usePortal={false}
|
position?: MenuPosition; // applicable when usePortal={false}
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
animated?: boolean;
|
||||||
toggleEvent?: "click" | "contextmenu";
|
toggleEvent?: "click" | "contextmenu";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,6 +59,7 @@ const defaultPropsMenu: Partial<MenuProps> = {
|
|||||||
closeOnClickOutside: true,
|
closeOnClickOutside: true,
|
||||||
closeOnScroll: false,
|
closeOnScroll: false,
|
||||||
toggleEvent: "click",
|
toggleEvent: "click",
|
||||||
|
animated: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Menu extends React.Component<MenuProps, State> {
|
export class Menu extends React.Component<MenuProps, State> {
|
||||||
@ -311,7 +313,7 @@ export class Menu extends React.Component<MenuProps, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { position, id } = this.props;
|
const { position, id, animated } = this.props;
|
||||||
let { className, usePortal } = this.props;
|
let { className, usePortal } = this.props;
|
||||||
|
|
||||||
className = cssNames("Menu", className, this.state.position || position, {
|
className = cssNames("Menu", className, this.state.position || position, {
|
||||||
@ -333,28 +335,40 @@ export class Menu extends React.Component<MenuProps, State> {
|
|||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
|
|
||||||
const menu = (
|
let menu = (
|
||||||
<MenuContext.Provider value={this}>
|
<ul
|
||||||
|
id={id}
|
||||||
|
ref={this.bindRef}
|
||||||
|
className={className}
|
||||||
|
style={{
|
||||||
|
left: this.state?.menuStyle?.left,
|
||||||
|
top: this.state?.menuStyle?.top,
|
||||||
|
}}
|
||||||
|
onKeyDown={this.onKeyDown}
|
||||||
|
>
|
||||||
|
{menuItems}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (animated) {
|
||||||
|
menu = (
|
||||||
<Animate enter={this.isOpen}>
|
<Animate enter={this.isOpen}>
|
||||||
<ul
|
{menu}
|
||||||
id={id}
|
|
||||||
ref={this.bindRef}
|
|
||||||
className={className}
|
|
||||||
style={{
|
|
||||||
left: this.state?.menuStyle?.left,
|
|
||||||
top: this.state?.menuStyle?.top,
|
|
||||||
}}
|
|
||||||
onKeyDown={this.onKeyDown}
|
|
||||||
>
|
|
||||||
{menuItems}
|
|
||||||
</ul>
|
|
||||||
</Animate>
|
</Animate>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu = (
|
||||||
|
<MenuContext.Provider value={this}>
|
||||||
|
{menu}
|
||||||
</MenuContext.Provider>
|
</MenuContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (usePortal === true) usePortal = document.body;
|
if (usePortal === true) usePortal = document.body;
|
||||||
|
|
||||||
return usePortal instanceof HTMLElement ? createPortal(menu, usePortal) : menu;
|
return usePortal instanceof HTMLElement
|
||||||
|
? createPortal(menu, usePortal)
|
||||||
|
: menu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
49
src/renderer/kube-object/handler.ts
Normal file
49
src/renderer/kube-object/handler.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
import type { RequireAtLeastOne } from "type-fest";
|
||||||
|
import type { KubeObject } from "../../common/k8s-api/kube-object";
|
||||||
|
import type { BaseIconProps } from "../components/icon";
|
||||||
|
|
||||||
|
export interface KubeObjectContextMenuItem {
|
||||||
|
/**
|
||||||
|
* If the type is `string` then it is shorthand for {@link BaseIconProps.material}
|
||||||
|
*
|
||||||
|
* This is required because this item can be either rendered as a context menu or as a toolbar in
|
||||||
|
* the kube object details page.
|
||||||
|
*/
|
||||||
|
icon: string | BaseIconProps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The title text for the menu item or the hover text for the icon.
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The action when clicked
|
||||||
|
*/
|
||||||
|
onClick: (obj: KubeObject) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KubeObjectOnContextMenuOpenContext {
|
||||||
|
menuItems: KubeObjectContextMenuItem[];
|
||||||
|
navigate: (location: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type KubeObjectOnContextMenuOpen = (ctx: KubeObjectOnContextMenuOpenContext) => void;
|
||||||
|
|
||||||
|
export interface KubeObjectHandlers {
|
||||||
|
onContextMenuOpen: KubeObjectOnContextMenuOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type KubeObjectHandlerRegistration = {
|
||||||
|
apiVersions: string[];
|
||||||
|
kind: string;
|
||||||
|
} & RequireAtLeastOne<KubeObjectHandlers>;
|
||||||
|
|
||||||
|
export const staticKubeObjectHandlerInjectionToken = getInjectionToken<KubeObjectHandlerRegistration>({
|
||||||
|
id: "static-kube-object-handler-token",
|
||||||
|
});
|
||||||
42
src/renderer/kube-object/handlers.injectable.ts
Normal file
42
src/renderer/kube-object/handlers.injectable.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import rendererExtensionsInjectable from "../../extensions/renderer-extensions.injectable";
|
||||||
|
import { getOrInsert, getOrInsertMap, readonly } from "../utils";
|
||||||
|
import type { KubeObjectHandlerRegistration, KubeObjectHandlers } from "./handler";
|
||||||
|
import { staticKubeObjectHandlerInjectionToken } from "./handler";
|
||||||
|
|
||||||
|
const kubeObjectHandlersInjectable = getInjectable({
|
||||||
|
id: "kube-object-handlers",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const extensions = di.inject(rendererExtensionsInjectable);
|
||||||
|
const staticKubeObjectContextMenuHandlers = di.injectMany(staticKubeObjectHandlerInjectionToken);
|
||||||
|
|
||||||
|
return computed(() => {
|
||||||
|
const res = new Map<string, Map<string, Partial<KubeObjectHandlers>[]>>();
|
||||||
|
const addAllHandlers = (registrations: KubeObjectHandlerRegistration[]) => {
|
||||||
|
for (const { apiVersions, kind, ...handlers } of registrations) {
|
||||||
|
for (const apiVersion of apiVersions) {
|
||||||
|
const byApiVersion = getOrInsertMap(res, apiVersion);
|
||||||
|
const byKind = getOrInsert(byApiVersion, kind, []);
|
||||||
|
|
||||||
|
byKind.push(handlers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extensions.get()
|
||||||
|
.map(ext => ext.kubeObjectHandlers)
|
||||||
|
.forEach(addAllHandlers);
|
||||||
|
|
||||||
|
addAllHandlers(staticKubeObjectContextMenuHandlers);
|
||||||
|
|
||||||
|
return readonly(res);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default kubeObjectHandlersInjectable;
|
||||||
30
src/renderer/kube-object/handlers/stateful-set.injectable.ts
Normal file
30
src/renderer/kube-object/handlers/stateful-set.injectable.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { StatefulSet } from "../../../common/k8s-api/endpoints";
|
||||||
|
import openStatefulSetScaleDialogInjectable from "../../components/+workloads-statefulsets/scale/open-dialog.injectable";
|
||||||
|
import { staticKubeObjectHandlerInjectionToken } from "../handler";
|
||||||
|
|
||||||
|
const statefulSetKubeObjectHandlerInjectable = getInjectable({
|
||||||
|
id: "stateful-set-kube-object-handler",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const openStatefulSetScaleDialog = di.inject(openStatefulSetScaleDialogInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "StatefulSet",
|
||||||
|
apiVersions: ["apps/v1"],
|
||||||
|
onContextMenuOpen: (ctx) => {
|
||||||
|
ctx.menuItems.push({
|
||||||
|
icon: "open_with",
|
||||||
|
title: "Scale",
|
||||||
|
onClick: (obj) => openStatefulSetScaleDialog(obj as StatefulSet),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
injectionToken: staticKubeObjectHandlerInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default statefulSetKubeObjectHandlerInjectable;
|
||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import attemptInstallByInfoInjectable from "../../components/+extensions/attempt-install-by-info/attempt-install-by-info.injectable";
|
import attemptInstallByInfoInjectable from "../../components/+extensions/attempt-install-by-info.injectable";
|
||||||
import { bindProtocolAddRouteHandlers } from "./bind-protocol-add-route-handlers";
|
import { bindProtocolAddRouteHandlers } from "./bind-protocol-add-route-handlers";
|
||||||
import lensProtocolRouterRendererInjectable from "../lens-protocol-router-renderer/lens-protocol-router-renderer.injectable";
|
import lensProtocolRouterRendererInjectable from "../lens-protocol-router-renderer/lens-protocol-router-renderer.injectable";
|
||||||
import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||||
|
|||||||
@ -13,16 +13,15 @@ import {
|
|||||||
LensProtocolRouter,
|
LensProtocolRouter,
|
||||||
} from "../../../common/protocol-handler";
|
} from "../../../common/protocol-handler";
|
||||||
import { Notifications } from "../../components/notifications";
|
import { Notifications } from "../../components/notifications";
|
||||||
import type { ExtensionInfo } from "../../components/+extensions/attempt-install-by-info/attempt-install-by-info";
|
|
||||||
import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||||
import type { NavigateToEntitySettings } from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable";
|
import type { NavigateToEntitySettings } from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable";
|
||||||
import type { NavigateToClusterView } from "../../../common/front-end-routing/routes/cluster-view/navigate-to-cluster-view.injectable";
|
import type { NavigateToClusterView } from "../../../common/front-end-routing/routes/cluster-view/navigate-to-cluster-view.injectable";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
|
import type { AttemptInstallByInfo } from "../../components/+extensions/attempt-install-by-info.injectable";
|
||||||
|
|
||||||
// TODO: make it so that the handlers are type safe and we don't need to do the asserts
|
// TODO: make it so that the handlers are type safe and we don't need to do the asserts
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
attemptInstallByInfo: (extensionInfo: ExtensionInfo) => Promise<void>;
|
attemptInstallByInfo: AttemptInstallByInfo;
|
||||||
lensProtocolRouterRenderer: LensProtocolRouterRenderer;
|
lensProtocolRouterRenderer: LensProtocolRouterRenderer;
|
||||||
navigateToCatalog: NavigateToCatalog;
|
navigateToCatalog: NavigateToCatalog;
|
||||||
navigateToAddCluster: () => void;
|
navigateToAddCluster: () => void;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user