/** * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ import React from "react"; import { cssNames } from "../../utils"; import type { KubeObject } from "../../../common/k8s-api/kube-object"; import type { MenuActionsProps } from "../menu"; import { MenuItem, MenuActions } from "../menu"; import identity from "lodash/identity"; import type { ApiManager } from "../../../common/k8s-api/api-manager"; import { withInjectables } from "@ogre-tools/injectable-react"; import clusterNameInjectable from "./dependencies/cluster-name.injectable"; import createEditResourceTabInjectable from "../dock/edit-resource/edit-resource-tab.injectable"; import kubeObjectMenuItemsInjectable from "./kube-object-menu-items.injectable"; import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable"; import type { HideDetails } 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 type { IComputedValue } from "mobx"; 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 extends MenuActionsProps { object: TKubeObject; editable?: boolean; removable?: boolean; } interface Dependencies { apiManager: ApiManager; kubeObjectMenuItems: IComputedValue; clusterName: string | undefined; hideDetails: HideDetails; createEditResourceTab: (kubeObject: KubeObject) => void; onContextMenuOpen: OnKubeObjectContextMenuOpen; withConfirmation: WithConfirmation; navigate: Navigate; } @observer class NonInjectedKubeObjectMenu extends React.Component & Dependencies> { private menuItems = observable.array(); componentDidUpdate(prevProps: Readonly & Dependencies>): void { if (prevProps.object !== this.props.object && this.props.object) { this.emitOnContextMenuOpen(this.props.object); } } private renderRemoveMessage(object: KubeObject) { const breadcrumbParts = [object.getNs(), object.getName()]; const breadcrumb = breadcrumbParts.filter(identity).join("/"); return (

{`Remove ${object.kind} `} {breadcrumb} {" from "} {this.props.clusterName} ?

); } private renderMenuItems() { const { object, toolbar } = this.props; return this.props.kubeObjectMenuItems.get().map((MenuItem, index) => ( )); } 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) => ( item.onClick(object)} data-testid={`menu-action-${item.title.toLowerCase().replace(/\s+/, "-")}`} > {item.title} )) ); render() { const { className, editable, removable, object, removeAction, // This is here so we don't pass it down to `` removeConfirmationMessage, // This is here so we don't pass it down to `` updateAction, // This is here so we don't pass it down to `` ...menuProps } = this.props; return ( this.emitOnContextMenuOpen(object) : undefined} {...menuProps} > {this.renderMenuItems()} {object && this.renderContextMenuItems(object)} ); } } export const KubeObjectMenu = withInjectables>(NonInjectedKubeObjectMenu, { getProps: (di, props) => ({ ...props, clusterName: di.inject(clusterNameInjectable), apiManager: di.inject(apiManagerInjectable), createEditResourceTab: di.inject(createEditResourceTabInjectable), hideDetails: di.inject(hideDetailsInjectable), kubeObjectMenuItems: di.inject(kubeObjectMenuItemsInjectable, { kubeObject: props.object, }), onContextMenuOpen: di.inject(onKubeObjectContextMenuOpenInjectable), navigate: di.inject(navigateInjectable), withConfirmation: di.inject(withConfirmationInjectable), }), }) as (props: KubeObjectMenuProps) => React.ReactElement;