mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Make all of Command* injectable, with some others too
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
abe90ad92a
commit
f8ae1149fb
@ -26,7 +26,7 @@ import logger from "../../main/logger";
|
||||
import { AppPaths } from "../app-paths";
|
||||
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
||||
import { ClusterStore } from "../cluster-store";
|
||||
import { HotbarStore } from "../hotbar-store";
|
||||
import { HotbarStore } from "../hotbar-store.injectable";
|
||||
|
||||
jest.mock("../../main/catalog/catalog-entity-registry", () => ({
|
||||
catalogEntityRegistry: {
|
||||
@ -251,7 +251,7 @@ describe("HotbarStore", () => {
|
||||
const hotbarStore = HotbarStore.getInstance();
|
||||
|
||||
hotbarStore.add({ name: "hottest", id: "hottest" });
|
||||
hotbarStore.activeHotbarId = "hottest";
|
||||
hotbarStore.setActiveHotbar("hottest");
|
||||
|
||||
const { error } = logger;
|
||||
const mocked = jest.fn();
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { CatalogCategory, CatalogEntity, CatalogEntityAddMenuContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
||||
import { CatalogCategory, CatalogEntity, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
||||
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
|
||||
import { productName } from "../vars";
|
||||
import { WeblinkStore } from "../weblink-store";
|
||||
@ -86,21 +86,6 @@ export class WebLinkCategory extends CatalogCategory {
|
||||
kind: "WebLink",
|
||||
},
|
||||
};
|
||||
public static onAdd?: () => void;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.on("catalogAddMenu", (ctx: CatalogEntityAddMenuContext) => {
|
||||
ctx.menuItems.push({
|
||||
icon: "public",
|
||||
title: "Add web link",
|
||||
onClick: () => {
|
||||
WebLinkCategory.onAdd();
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
catalogCategoryRegistry.add(new WebLinkCategory());
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { action, comparer, observable, makeObservable } from "mobx";
|
||||
import { action, comparer, observable, makeObservable, computed } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
import migrations from "../migrations/hotbar-store";
|
||||
import { toJS } from "./utils";
|
||||
@ -27,7 +27,8 @@ import { CatalogEntity } from "./catalog";
|
||||
import { catalogEntity } from "../main/catalog-sources/general";
|
||||
import logger from "../main/logger";
|
||||
import { broadcastMessage, HotbarTooManyItems } from "./ipc";
|
||||
import { defaultHotbarCells, getEmptyHotbar, Hotbar, HotbarCreateOptions } from "./hotbar-types";
|
||||
import { defaultHotbarCells, getEmptyHotbar, Hotbar, CreateHotbarData, CreateHotbarOptions } from "./hotbar-types";
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
|
||||
export interface HotbarStoreModel {
|
||||
hotbars: Hotbar[];
|
||||
@ -52,22 +53,40 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||
this.load();
|
||||
}
|
||||
|
||||
get activeHotbarId() {
|
||||
@computed get activeHotbarId() {
|
||||
return this._activeHotbarId;
|
||||
}
|
||||
|
||||
set activeHotbarId(id: string) {
|
||||
if (this.getById(id)) {
|
||||
this._activeHotbarId = id;
|
||||
/**
|
||||
* If `hotbar` points to a known hotbar, make it active. Otherwise, ignore
|
||||
* @param hotbar The hotbar instance, or the index, or its ID
|
||||
*/
|
||||
setActiveHotbar(hotbar: Hotbar | number | string) {
|
||||
if (typeof hotbar === "number") {
|
||||
if (hotbar >= 0 && hotbar < this.hotbars.length) {
|
||||
this._activeHotbarId = this.hotbars[hotbar].id;
|
||||
}
|
||||
} else if (typeof hotbar === "string") {
|
||||
if (this.getById(hotbar)) {
|
||||
this._activeHotbarId = hotbar;
|
||||
}
|
||||
} else {
|
||||
if (this.hotbars.indexOf(hotbar) >= 0) {
|
||||
this._activeHotbarId = hotbar.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hotbarIndex(id: string) {
|
||||
private hotbarIndexById(id: string) {
|
||||
return this.hotbars.findIndex((hotbar) => hotbar.id === id);
|
||||
}
|
||||
|
||||
get activeHotbarIndex() {
|
||||
return this.hotbarIndex(this.activeHotbarId);
|
||||
private hotbarIndex(hotbar: Hotbar) {
|
||||
return this.hotbars.indexOf(hotbar);
|
||||
}
|
||||
|
||||
@computed get activeHotbarIndex() {
|
||||
return this.hotbarIndexById(this.activeHotbarId);
|
||||
}
|
||||
|
||||
@action
|
||||
@ -87,13 +106,11 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||
this.hotbars.forEach(ensureExactHotbarItemLength);
|
||||
|
||||
if (data.activeHotbarId) {
|
||||
if (this.getById(data.activeHotbarId)) {
|
||||
this.activeHotbarId = data.activeHotbarId;
|
||||
}
|
||||
this.setActiveHotbar(data.activeHotbarId);
|
||||
}
|
||||
|
||||
if (!this.activeHotbarId) {
|
||||
this.activeHotbarId = this.hotbars[0].id;
|
||||
this.setActiveHotbar(0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,8 +135,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||
return this.hotbars.find((hotbar) => hotbar.id === id);
|
||||
}
|
||||
|
||||
@action
|
||||
add(data: HotbarCreateOptions, { setActive = false } = {}) {
|
||||
add = action((data: CreateHotbarData, { setActive = false }: CreateHotbarOptions = {}) => {
|
||||
const hotbar = getEmptyHotbar(data.name, data.id);
|
||||
|
||||
this.hotbars.push(hotbar);
|
||||
@ -127,29 +143,29 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||
if (setActive) {
|
||||
this._activeHotbarId = hotbar.id;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@action
|
||||
setHotbarName(id: string, name: string) {
|
||||
setHotbarName = action((id: string, name: string) => {
|
||||
const index = this.hotbars.findIndex((hotbar) => hotbar.id === id);
|
||||
|
||||
if(index < 0) {
|
||||
console.warn(`[HOTBAR-STORE]: cannot setHotbarName: unknown id`, { id });
|
||||
|
||||
return;
|
||||
if (index < 0) {
|
||||
return void console.warn(`[HOTBAR-STORE]: cannot setHotbarName: unknown id`, { id });
|
||||
}
|
||||
|
||||
this.hotbars[index].name = name;
|
||||
}
|
||||
});
|
||||
|
||||
remove = action((hotbar: Hotbar) => {
|
||||
if (this.hotbars.length <= 1) {
|
||||
throw new Error("Cannot remove the last hotbar");
|
||||
}
|
||||
|
||||
@action
|
||||
remove(hotbar: Hotbar) {
|
||||
this.hotbars = this.hotbars.filter((h) => h !== hotbar);
|
||||
|
||||
if (this.activeHotbarId === hotbar.id) {
|
||||
this.activeHotbarId = this.hotbars[0].id;
|
||||
this.setActiveHotbar(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@action
|
||||
addToHotbar(item: CatalogEntity, cellIndex?: number) {
|
||||
@ -263,7 +279,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||
index = hotbarStore.hotbars.length - 1;
|
||||
}
|
||||
|
||||
hotbarStore.activeHotbarId = hotbarStore.hotbars[index].id;
|
||||
hotbarStore.setActiveHotbar(index);
|
||||
}
|
||||
|
||||
switchToNext() {
|
||||
@ -274,7 +290,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
hotbarStore.activeHotbarId = hotbarStore.hotbars[index].id;
|
||||
hotbarStore.setActiveHotbar(index);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -284,6 +300,20 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||
isAddedToActive(entity: CatalogEntity) {
|
||||
return !!this.getActive().items.find(item => item?.entity.uid === entity.metadata.uid);
|
||||
}
|
||||
|
||||
getDisplayLabel(hotbar: Hotbar): string {
|
||||
return `${this.getDisplayIndex(hotbar)}: ${hotbar.name}`;
|
||||
}
|
||||
|
||||
getDisplayIndex(hotbar: Hotbar): string {
|
||||
const index = this.hotbarIndex(hotbar);
|
||||
|
||||
if (index < 0) {
|
||||
return "??";
|
||||
}
|
||||
|
||||
return `${index + 1}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -292,12 +322,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||
* @param hotbar The hotbar to modify
|
||||
*/
|
||||
function ensureExactHotbarItemLength(hotbar: Hotbar) {
|
||||
if (hotbar.items.length === defaultHotbarCells) {
|
||||
// if we already have `defaultHotbarCells` then we are good to stop
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, keep adding empty entries until full
|
||||
// if there are not enough items
|
||||
while (hotbar.items.length < defaultHotbarCells) {
|
||||
hotbar.items.push(null);
|
||||
}
|
||||
@ -314,3 +339,10 @@ function ensureExactHotbarItemLength(hotbar: Hotbar) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hotbarManagerInjectable = getInjectable({
|
||||
instantiate: () => HotbarStore.getInstance(),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default hotbarManagerInjectable;
|
||||
@ -33,14 +33,18 @@ export interface HotbarItem {
|
||||
}
|
||||
}
|
||||
|
||||
export type Hotbar = Required<HotbarCreateOptions>;
|
||||
export type Hotbar = Required<CreateHotbarData>;
|
||||
|
||||
export interface HotbarCreateOptions {
|
||||
export interface CreateHotbarData {
|
||||
id?: string;
|
||||
name: string;
|
||||
items?: Tuple<HotbarItem | null, typeof defaultHotbarCells>;
|
||||
}
|
||||
|
||||
export interface CreateHotbarOptions {
|
||||
setActive?: boolean;
|
||||
}
|
||||
|
||||
export const defaultHotbarCells = 12; // Number is chosen to easy hit any item with keyboard
|
||||
|
||||
export function getEmptyHotbar(name: string, id: string = uuid.v4()): Hotbar {
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
export const dialogShowOpenDialogHandler = "dialog:show-open-dialog";
|
||||
export const catalogEntityRunListener = "catalog-entity:run";
|
||||
|
||||
export * from "./ipc";
|
||||
export * from "./invalid-kubeconfig";
|
||||
|
||||
@ -201,18 +201,3 @@ export function every<T>(src: Iterable<T>, fn: (val: T) => any): boolean {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a new iterator that drains the first and then the second
|
||||
* @param first The first iterable to iterate through
|
||||
* @param second The second iterable to iterate through
|
||||
*/
|
||||
export function* chain<T>(first: Iterable<T>, second: Iterable<T>): IterableIterator<T> {
|
||||
for (const t of first) {
|
||||
yield t;
|
||||
}
|
||||
|
||||
for (const t of second) {
|
||||
yield t;
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,7 +41,6 @@ export const getDiForUnitTesting = () => {
|
||||
aliases: [injectable, ...(injectable.aliases || [])],
|
||||
};
|
||||
})
|
||||
|
||||
.forEach(injectable => di.register(injectable));
|
||||
|
||||
di.preventSideEffects();
|
||||
|
||||
@ -54,7 +54,7 @@ export class EntitySettingRegistry extends BaseRegistry<EntitySettingRegistratio
|
||||
};
|
||||
}
|
||||
|
||||
getItemsForKind(kind: string, apiVersion: string, source?: string) {
|
||||
getItemsForKind = (kind: string, apiVersion: string, source?: string) => {
|
||||
let items = this.getItems().filter((item) => {
|
||||
return item.kind === kind && item.apiVersions.includes(apiVersion);
|
||||
});
|
||||
@ -66,5 +66,5 @@ export class EntitySettingRegistry extends BaseRegistry<EntitySettingRegistratio
|
||||
}
|
||||
|
||||
return items.sort((a, b) => (b.priority ?? 50) - (a.priority ?? 50));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ import configurePackages from "../common/configure-packages";
|
||||
import { PrometheusProviderRegistry } from "./prometheus";
|
||||
import * as initializers from "./initializers";
|
||||
import { ClusterStore } from "../common/cluster-store";
|
||||
import { HotbarStore } from "../common/hotbar-store";
|
||||
import { HotbarStore } from "../common/hotbar-store.injectable";
|
||||
import { UserStore } from "../common/user-store";
|
||||
import { WeblinkStore } from "../common/weblink-store";
|
||||
import { ExtensionsStore } from "../extensions/extensions-store";
|
||||
|
||||
@ -220,6 +220,8 @@ export function getAppMenu(
|
||||
/**
|
||||
* Don't broadcast unless it was triggered by menu iteration so that
|
||||
* there aren't double events in renderer
|
||||
*
|
||||
* NOTE: this `?` is required because of a bug in playwright. https://github.com/microsoft/playwright/issues/10554
|
||||
*/
|
||||
if (!event?.triggeredByAccelerator) {
|
||||
broadcastMessage("command-palette:open");
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
*/
|
||||
|
||||
import { computed, observable, makeObservable, action } from "mobx";
|
||||
import { ipcRendererOn } from "../../common/ipc";
|
||||
import { catalogEntityRunListener, ipcRendererOn } from "../../common/ipc";
|
||||
import { CatalogCategory, CatalogEntity, CatalogEntityData, catalogCategoryRegistry, CatalogCategoryRegistry, CatalogEntityKindData } from "../../common/catalog";
|
||||
import "../../common/catalog-entities";
|
||||
import type { Cluster } from "../../main/cluster";
|
||||
@ -88,7 +88,7 @@ export class CatalogEntityRegistry {
|
||||
ipcRenderer.send(CatalogIpcEvents.INIT);
|
||||
|
||||
if (isMainFrame) {
|
||||
ipcRendererOn("catalog-entity:run", (event, id: string) => {
|
||||
ipcRendererOn(catalogEntityRunListener, (event, id: string) => {
|
||||
const entity = this.getById(id);
|
||||
|
||||
if (entity) {
|
||||
|
||||
@ -40,7 +40,7 @@ import { DefaultProps } from "./mui-base-theme";
|
||||
import configurePackages from "../common/configure-packages";
|
||||
import * as initializers from "./initializers";
|
||||
import logger from "../common/logger";
|
||||
import { HotbarStore } from "../common/hotbar-store";
|
||||
import { HotbarStore } from "../common/hotbar-store.injectable";
|
||||
import { WeblinkStore } from "../common/weblink-store";
|
||||
import { ExtensionsStore } from "../extensions/extensions-store";
|
||||
import { FilesystemProvisionerStore } from "../main/extension-filesystem";
|
||||
@ -59,6 +59,7 @@ import bindProtocolAddRouteHandlersInjectable
|
||||
import type { LensProtocolRouterRenderer } from "./protocol-handler";
|
||||
import lensProtocolRouterRendererInjectable
|
||||
from "./protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable";
|
||||
import commandOverlayInjectable from "./components/command-palette/command-overlay.injectable";
|
||||
|
||||
if (process.isMainFrame) {
|
||||
SentryInit();
|
||||
@ -121,7 +122,9 @@ export async function bootstrap(comp: () => Promise<AppComponent>, di: Dependenc
|
||||
initializers.initCatalogCategoryRegistryEntries();
|
||||
|
||||
logger.info(`${logPrefix} initializing Catalog`);
|
||||
initializers.initCatalog();
|
||||
initializers.initCatalog({
|
||||
openCommandDialog: di.inject(commandOverlayInjectable).open,
|
||||
});
|
||||
|
||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ import { CatalogEntityStore } from "./catalog-entity.store";
|
||||
import { navigate } from "../../navigation";
|
||||
import { MenuItem, MenuActions } from "../menu";
|
||||
import type { CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { HotbarStore } from "../../../common/hotbar-store.injectable";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { catalogCategoryRegistry, CatalogEntity } from "../../../common/catalog";
|
||||
import { CatalogAddButton } from "./catalog-add-button";
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
*/
|
||||
import React, { ReactNode, useState } from "react";
|
||||
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { HotbarStore } from "../../../common/hotbar-store.injectable";
|
||||
import { MenuItem } from "../menu";
|
||||
|
||||
import type { CatalogEntity } from "../../api/catalog-entity";
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import { crdStore } from "../../../../renderer/components/+custom-resources/crd.store";
|
||||
import { crdStore } from "./crd.store";
|
||||
|
||||
const customResourceDefinitionsInjectable = getInjectable({
|
||||
instantiate: () => computed(() => [...crdStore.items]),
|
||||
@ -34,7 +34,7 @@ import { mockWindow } from "../../../../../__mocks__/windowMock";
|
||||
import { AppPaths } from "../../../../common/app-paths";
|
||||
import extensionLoaderInjectable
|
||||
from "../../../../extensions/extension-loader/extension-loader.injectable";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import { DiRender, renderFor } from "../../test-utils/renderFor";
|
||||
|
||||
mockWindow();
|
||||
|
||||
@ -24,7 +24,7 @@ import { screen } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import { defaultWidth, Welcome } from "../welcome";
|
||||
import { computed } from "mobx";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import type { DiRender } from "../../test-utils/renderFor";
|
||||
import { renderFor } from "../../test-utils/renderFor";
|
||||
import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
|
||||
|
||||
@ -19,41 +19,49 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { computed } from "mobx";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import { computed, IComputedValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import { broadcastMessage } from "../../../common/ipc";
|
||||
import { broadcastMessage, catalogEntityRunListener } from "../../../common/ipc";
|
||||
import type { CatalogEntity } from "../../api/catalog-entity";
|
||||
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||
import { CommandOverlay } from "../command-palette";
|
||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
||||
import { Select } from "../select";
|
||||
|
||||
@observer
|
||||
export class ActivateEntityCommand extends React.Component {
|
||||
@computed get options() {
|
||||
return catalogEntityRegistry.items.map(entity => ({
|
||||
label: `${entity.kind}: ${entity.getName()}`,
|
||||
value: entity,
|
||||
}));
|
||||
}
|
||||
|
||||
onSelect(entity: CatalogEntity): void {
|
||||
broadcastMessage("catalog-entity:run", entity.getId());
|
||||
CommandOverlay.close();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Select
|
||||
menuPortalTarget={null}
|
||||
onChange={(v) => this.onSelect(v.value)}
|
||||
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||
menuIsOpen={true}
|
||||
options={this.options}
|
||||
autoFocus={true}
|
||||
escapeClearsValue={false}
|
||||
placeholder="Activate entity ..."
|
||||
/>
|
||||
);
|
||||
}
|
||||
interface Dependencies {
|
||||
closeCommandOverlay: () => void;
|
||||
entities: IComputedValue<CatalogEntity[]>;
|
||||
}
|
||||
|
||||
const NonInjectedActivateEntityCommand = observer(({ closeCommandOverlay, entities }: Dependencies) => {
|
||||
const options = entities.get().map(entity => ({
|
||||
label: `${entity.kind}: ${entity.getName()}`,
|
||||
value: entity,
|
||||
}));
|
||||
|
||||
const onSelect = (entity: CatalogEntity): void => {
|
||||
broadcastMessage(catalogEntityRunListener, entity.getId());
|
||||
closeCommandOverlay();
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
menuPortalTarget={null}
|
||||
onChange={(v) => onSelect(v.value)}
|
||||
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||
menuIsOpen={true}
|
||||
options={options}
|
||||
autoFocus={true}
|
||||
escapeClearsValue={false}
|
||||
placeholder="Activate entity ..."
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const ActivateEntityCommand = withInjectables<Dependencies>(NonInjectedActivateEntityCommand, {
|
||||
getProps: di => ({
|
||||
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
|
||||
entities: computed(() => [...catalogEntityRegistry.items]),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -21,21 +21,26 @@
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { CommandOverlay } from "../command-palette";
|
||||
import { Input } from "../input";
|
||||
import { isUrl } from "../input/input_validators";
|
||||
import { WeblinkStore } from "../../../common/weblink-store";
|
||||
import { computed, makeObservable, observable } from "mobx";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
closeCommandOverlay: () => void;
|
||||
}
|
||||
|
||||
|
||||
@observer
|
||||
export class WeblinkAddCommand extends React.Component {
|
||||
class NonInjectedWeblinkAddCommand extends React.Component<Dependencies> {
|
||||
@observable url = "";
|
||||
@observable nameHidden = true;
|
||||
@observable dirty = false;
|
||||
|
||||
constructor(props: {}) {
|
||||
constructor(props: Dependencies) {
|
||||
super(props);
|
||||
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@ -55,8 +60,7 @@ export class WeblinkAddCommand extends React.Component {
|
||||
name: name || this.url,
|
||||
url: this.url,
|
||||
});
|
||||
|
||||
CommandOverlay.close();
|
||||
this.props.closeCommandOverlay();
|
||||
}
|
||||
|
||||
@computed get showValidation() {
|
||||
@ -100,3 +104,10 @@ export class WeblinkAddCommand extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const WeblinkAddCommand = withInjectables<Dependencies>(NonInjectedWeblinkAddCommand, {
|
||||
getProps: (di, props) => ({
|
||||
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -22,19 +22,31 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Icon } from "../icon";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { CommandOverlay } from "../command-palette";
|
||||
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
|
||||
import { HotbarSwitchCommand } from "../hotbar/hotbar-switch-command";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
||||
|
||||
export const ActiveHotbarName = observer(() => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center"
|
||||
data-testid="current-hotbar-name"
|
||||
onClick={() => CommandOverlay.open(<HotbarSwitchCommand />)}
|
||||
>
|
||||
<Icon material="bookmarks" className="mr-2" size={14}/>
|
||||
{HotbarStore.getInstance().getActive()?.name}
|
||||
</div>
|
||||
);
|
||||
interface Dependencies {
|
||||
openCommandOverlay: (component: React.ReactElement) => void;
|
||||
activeHotbarName: () => string | undefined;
|
||||
}
|
||||
|
||||
const NonInjectedActiveHotbarName = observer(({ openCommandOverlay, activeHotbarName }: Dependencies) => (
|
||||
<div
|
||||
className="flex items-center"
|
||||
data-testid="current-hotbar-name"
|
||||
onClick={() => openCommandOverlay(<HotbarSwitchCommand />)}
|
||||
>
|
||||
<Icon material="bookmarks" className="mr-2" size={14} />
|
||||
{activeHotbarName()}
|
||||
</div>
|
||||
));
|
||||
|
||||
export const ActiveHotbarName = withInjectables<Dependencies>(NonInjectedActiveHotbarName, {
|
||||
getProps: (di, props) => ({
|
||||
activeHotbarName: () => di.inject(hotbarManagerInjectable).getActive()?.name,
|
||||
openCommandOverlay: di.inject(commandOverlayInjectable).open,
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -21,21 +21,19 @@
|
||||
|
||||
import React from "react";
|
||||
import mockFs from "mock-fs";
|
||||
import { render, fireEvent } from "@testing-library/react";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import { BottomBar } from "./bottom-bar";
|
||||
import { StatusBarRegistry } from "../../../extensions/registries";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
|
||||
import { AppPaths } from "../../../common/app-paths";
|
||||
import { CommandOverlay } from "../command-palette";
|
||||
import { HotbarSwitchCommand } from "../hotbar/hotbar-switch-command";
|
||||
import { ActiveHotbarName } from "./active-hotbar-name";
|
||||
|
||||
jest.mock("../command-palette", () => ({
|
||||
CommandOverlay: {
|
||||
open: jest.fn(),
|
||||
},
|
||||
}));
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import { DiRender, renderFor } from "../test-utils/renderFor";
|
||||
import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
|
||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
||||
import { getEmptyHotbar } from "../../../common/hotbar-types";
|
||||
|
||||
AppPaths.init();
|
||||
|
||||
@ -55,7 +53,12 @@ jest.mock("electron", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const foobarHotbar = getEmptyHotbar("foobar");
|
||||
|
||||
describe("<BottomBar />", () => {
|
||||
let di: ConfigurableDependencyInjectionContainer;
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockOpts = {
|
||||
"tmp": {
|
||||
@ -63,14 +66,19 @@ describe("<BottomBar />", () => {
|
||||
},
|
||||
};
|
||||
|
||||
di = getDiForUnitTesting();
|
||||
render = renderFor(di);
|
||||
|
||||
mockFs(mockOpts);
|
||||
StatusBarRegistry.createInstance();
|
||||
HotbarStore.createInstance();
|
||||
|
||||
di.override(hotbarManagerInjectable, () => ({
|
||||
getActive: () => foobarHotbar,
|
||||
} as any));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
StatusBarRegistry.resetInstance();
|
||||
HotbarStore.resetInstance();
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
@ -80,24 +88,20 @@ describe("<BottomBar />", () => {
|
||||
expect(container).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it("renders w/o errors when .getItems() returns unexpected (not type compliant) data", async () => {
|
||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => undefined);
|
||||
expect(() => render(<BottomBar />)).not.toThrow();
|
||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => "hello");
|
||||
expect(() => render(<BottomBar />)).not.toThrow();
|
||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => 6);
|
||||
expect(() => render(<BottomBar />)).not.toThrow();
|
||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => null);
|
||||
expect(() => render(<BottomBar />)).not.toThrow();
|
||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => []);
|
||||
expect(() => render(<BottomBar />)).not.toThrow();
|
||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [{}]);
|
||||
expect(() => render(<BottomBar />)).not.toThrow();
|
||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => { return {};});
|
||||
it.each([
|
||||
undefined,
|
||||
"hello",
|
||||
6,
|
||||
null,
|
||||
[],
|
||||
[{}],
|
||||
{},
|
||||
])("renders w/o errors when .getItems() returns not type compliant (%p)", val => {
|
||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => val);
|
||||
expect(() => render(<BottomBar />)).not.toThrow();
|
||||
});
|
||||
|
||||
it("renders items [{item: React.ReactNode}] (4.0.0-rc.1)", async () => {
|
||||
it("renders items [{item: React.ReactNode}] (4.0.0-rc.1)", () => {
|
||||
const testId = "testId";
|
||||
const text = "heee";
|
||||
|
||||
@ -106,10 +110,10 @@ describe("<BottomBar />", () => {
|
||||
]);
|
||||
const { getByTestId } = render(<BottomBar />);
|
||||
|
||||
expect(await getByTestId(testId)).toHaveTextContent(text);
|
||||
expect(getByTestId(testId)).toHaveTextContent(text);
|
||||
});
|
||||
|
||||
it("renders items [{item: () => React.ReactNode}] (4.0.0-rc.1+)", async () => {
|
||||
it("renders items [{item: () => React.ReactNode}] (4.0.0-rc.1+)", () => {
|
||||
const testId = "testId";
|
||||
const text = "heee";
|
||||
|
||||
@ -118,33 +122,25 @@ describe("<BottomBar />", () => {
|
||||
]);
|
||||
const { getByTestId } = render(<BottomBar />);
|
||||
|
||||
expect(await getByTestId(testId)).toHaveTextContent(text);
|
||||
expect(getByTestId(testId)).toHaveTextContent(text);
|
||||
});
|
||||
|
||||
it("show default hotbar name", () => {
|
||||
it("shows active hotbar name", () => {
|
||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
|
||||
{ item: () => <ActiveHotbarName/> },
|
||||
]);
|
||||
const { getByTestId } = render(<BottomBar />);
|
||||
|
||||
expect(getByTestId("current-hotbar-name")).toHaveTextContent("default");
|
||||
});
|
||||
|
||||
it("show active hotbar name", () => {
|
||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
|
||||
{ item: () => <ActiveHotbarName/> },
|
||||
]);
|
||||
const { getByTestId } = render(<BottomBar />);
|
||||
|
||||
HotbarStore.getInstance().add({
|
||||
id: "new",
|
||||
name: "new",
|
||||
}, { setActive: true });
|
||||
|
||||
expect(getByTestId("current-hotbar-name")).toHaveTextContent("new");
|
||||
expect(getByTestId("current-hotbar-name")).toHaveTextContent("foobar");
|
||||
});
|
||||
|
||||
it("opens command palette on click", () => {
|
||||
const mockOpen = jest.fn();
|
||||
|
||||
di.override(commandOverlayInjectable, () => ({
|
||||
open: mockOpen,
|
||||
}) as any);
|
||||
|
||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
|
||||
{ item: () => <ActiveHotbarName/> },
|
||||
]);
|
||||
@ -153,7 +149,8 @@ describe("<BottomBar />", () => {
|
||||
|
||||
fireEvent.click(activeHotbar);
|
||||
|
||||
expect(CommandOverlay.open).toHaveBeenCalledWith(<HotbarSwitchCommand />);
|
||||
|
||||
expect(mockOpen).toHaveBeenCalledWith(<HotbarSwitchCommand />);
|
||||
});
|
||||
|
||||
it("sort positioned items properly", () => {
|
||||
|
||||
@ -26,41 +26,43 @@ import React from "react";
|
||||
import { Dialog } from "../dialog";
|
||||
import { CommandDialog } from "./command-dialog";
|
||||
import type { ClusterId } from "../../../common/cluster-types";
|
||||
import { CommandOverlay } from "./command-overlay";
|
||||
import commandOverlayInjectable, { CommandOverlay } from "./command-overlay.injectable";
|
||||
import { isMac } from "../../../common/vars";
|
||||
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||
import { broadcastMessage, ipcRendererOn } from "../../../common/ipc";
|
||||
import { getMatchedClusterId } from "../../navigation";
|
||||
import type { Disposer } from "../../utils";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import windowAddEventListenerInjectable from "../../window/event-listener.injectable";
|
||||
|
||||
export interface CommandContainerProps {
|
||||
clusterId?: ClusterId;
|
||||
}
|
||||
|
||||
function addWindowEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): Disposer {
|
||||
window.addEventListener(type, listener, options);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(type, listener);
|
||||
};
|
||||
interface Dependencies {
|
||||
addWindowEventListener: <K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions) => Disposer;
|
||||
commandOverlay: CommandOverlay,
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CommandContainer extends React.Component<CommandContainerProps> {
|
||||
class NonInjectedCommandContainer extends React.Component<CommandContainerProps & Dependencies> {
|
||||
private escHandler(event: KeyboardEvent) {
|
||||
const { commandOverlay } = this.props;
|
||||
|
||||
if (event.key === "Escape") {
|
||||
event.stopPropagation();
|
||||
CommandOverlay.close();
|
||||
commandOverlay.close();
|
||||
}
|
||||
}
|
||||
|
||||
handleCommandPalette = () => {
|
||||
const { commandOverlay } = this.props;
|
||||
const clusterIsActive = getMatchedClusterId() !== undefined;
|
||||
|
||||
if (clusterIsActive) {
|
||||
broadcastMessage(`command-palette:${catalogEntityRegistry.activeEntity.getId()}:open`);
|
||||
} else {
|
||||
CommandOverlay.open(<CommandDialog />);
|
||||
commandOverlay.open(<CommandDialog />);
|
||||
}
|
||||
};
|
||||
|
||||
@ -75,11 +77,13 @@ export class CommandContainer extends React.Component<CommandContainerProps> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const action = this.props.clusterId
|
||||
? () => CommandOverlay.open(<CommandDialog />)
|
||||
const { clusterId, addWindowEventListener, commandOverlay } = this.props;
|
||||
|
||||
const action = clusterId
|
||||
? () => commandOverlay.open(<CommandDialog />)
|
||||
: this.handleCommandPalette;
|
||||
const ipcChannel = this.props.clusterId
|
||||
? `command-palette:${this.props.clusterId}:open`
|
||||
const ipcChannel = clusterId
|
||||
? `command-palette:${clusterId}:open`
|
||||
: "command-palette:open";
|
||||
|
||||
disposeOnUnmount(this, [
|
||||
@ -90,17 +94,27 @@ export class CommandContainer extends React.Component<CommandContainerProps> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { commandOverlay } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={CommandOverlay.isOpen}
|
||||
isOpen={commandOverlay.isOpen}
|
||||
animated={true}
|
||||
onClose={CommandOverlay.close}
|
||||
onClose={commandOverlay.close}
|
||||
modal={false}
|
||||
>
|
||||
<div id="command-container">
|
||||
{CommandOverlay.component}
|
||||
{commandOverlay.component}
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const CommandContainer = withInjectables<Dependencies, CommandContainerProps>(NonInjectedCommandContainer, {
|
||||
getProps: (di, props) => ({
|
||||
addWindowEventListener: di.inject(windowAddEventListenerInjectable),
|
||||
commandOverlay: di.inject(commandOverlayInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -24,7 +24,7 @@ import { Select } from "../select";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import React, { useState } from "react";
|
||||
import { CommandOverlay } from "./command-overlay";
|
||||
import commandOverlayInjectable from "./command-overlay.injectable";
|
||||
import type { CatalogEntity } from "../../../common/catalog";
|
||||
import { navigate } from "../../navigation";
|
||||
import { broadcastMessage } from "../../../common/ipc";
|
||||
@ -39,9 +39,10 @@ import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||
interface Dependencies {
|
||||
commands: IComputedValue<Map<string, RegisteredCommand>>;
|
||||
activeEntity?: CatalogEntity;
|
||||
closeCommandOverlay: () => void;
|
||||
}
|
||||
|
||||
const NonInjectedCommandDialog = observer(({ commands, activeEntity }: Dependencies) => {
|
||||
const NonInjectedCommandDialog = observer(({ commands, activeEntity, closeCommandOverlay }: Dependencies) => {
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const executeAction = (commandId: string) => {
|
||||
@ -52,7 +53,7 @@ const NonInjectedCommandDialog = observer(({ commands, activeEntity }: Dependenc
|
||||
}
|
||||
|
||||
try {
|
||||
CommandOverlay.close();
|
||||
closeCommandOverlay();
|
||||
command.action({
|
||||
entity: activeEntity,
|
||||
navigate: (url, opts = {}) => {
|
||||
@ -121,5 +122,6 @@ export const CommandDialog = withInjectables<Dependencies>(NonInjectedCommandDia
|
||||
commands: di.inject(registeredCommandsInjectable),
|
||||
// TODO: replace with injection
|
||||
activeEntity: catalogEntityRegistry.activeEntity,
|
||||
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -19,29 +19,37 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { observable } from "mobx";
|
||||
import React from "react";
|
||||
|
||||
export class CommandOverlay {
|
||||
static #component = observable.box<React.ReactElement | null>(null, { deep: false });
|
||||
#component = observable.box<React.ReactElement | null>(null, { deep: false });
|
||||
|
||||
static get isOpen(): boolean {
|
||||
return Boolean(CommandOverlay.#component.get());
|
||||
get isOpen(): boolean {
|
||||
return Boolean(this.#component.get());
|
||||
}
|
||||
|
||||
static open(component: React.ReactElement) {
|
||||
open = (component: React.ReactElement) => {
|
||||
if (!React.isValidElement(component)) {
|
||||
throw new TypeError("CommandOverlay.open must be passed a valid ReactElement");
|
||||
}
|
||||
|
||||
CommandOverlay.#component.set(component);
|
||||
}
|
||||
this.#component.set(component);
|
||||
};
|
||||
|
||||
static close() {
|
||||
CommandOverlay.#component.set(null);
|
||||
}
|
||||
close = () => {
|
||||
this.#component.set(null);
|
||||
};
|
||||
|
||||
static get component(): React.ReactElement | null {
|
||||
return CommandOverlay.#component.get();
|
||||
get component(): React.ReactElement | null {
|
||||
return this.#component.get();
|
||||
}
|
||||
}
|
||||
|
||||
const commandOverlayInjectable = getInjectable({
|
||||
instantiate: () => new CommandOverlay(),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default commandOverlayInjectable;
|
||||
@ -21,4 +21,4 @@
|
||||
|
||||
export * from "./command-container";
|
||||
export * from "./command-dialog";
|
||||
export * from "./command-overlay";
|
||||
export * from "./command-overlay.injectable";
|
||||
|
||||
@ -0,0 +1,231 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import * as routes from "../../../../common/routes";
|
||||
import { EntitySettingRegistry, RegisteredEntitySetting } from "../../../../extensions/registries";
|
||||
import { createTerminalTab } from "../../dock/terminal.store";
|
||||
import { HotbarAddCommand } from "../../hotbar/hotbar-add-command";
|
||||
import { HotbarRemoveCommand } from "../../hotbar/hotbar-remove-command";
|
||||
import { HotbarSwitchCommand } from "../../hotbar/hotbar-switch-command";
|
||||
import { HotbarRenameCommand } from "../../hotbar/hotbar-rename-command";
|
||||
import { ActivateEntityCommand } from "../../activate-entity-command";
|
||||
import type { CommandContext, CommandRegistration } from "./commands";
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import commandOverlayInjectable from "../command-overlay.injectable";
|
||||
|
||||
export function isKubernetesClusterActive(context: CommandContext): boolean {
|
||||
return context.entity?.kind === "KubernetesCluster";
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
openCommandDialog: (component: React.ReactElement) => void;
|
||||
getEntitySettingItems: (kind: string, apiVersion: string, source?: string) => RegisteredEntitySetting[];
|
||||
}
|
||||
|
||||
function getInternalCommands({ openCommandDialog, getEntitySettingItems }: Dependencies): CommandRegistration[] {
|
||||
return [
|
||||
{
|
||||
id: "app.showPreferences",
|
||||
title: "Preferences: Open",
|
||||
action: ({ navigate }) => navigate(routes.preferencesURL(), {
|
||||
forceRootFrame: true,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewHelmCharts",
|
||||
title: "Cluster: View Helm Charts",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.helmChartsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewHelmReleases",
|
||||
title: "Cluster: View Helm Releases",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.releaseURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewConfigMaps",
|
||||
title: "Cluster: View ConfigMaps",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.configMapsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewSecrets",
|
||||
title: "Cluster: View Secrets",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.secretsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewResourceQuotas",
|
||||
title: "Cluster: View ResourceQuotas",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.resourceQuotaURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewLimitRanges",
|
||||
title: "Cluster: View LimitRanges",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.limitRangeURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewHorizontalPodAutoscalers",
|
||||
title: "Cluster: View HorizontalPodAutoscalers (HPA)",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.hpaURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewPodDisruptionBudget",
|
||||
title: "Cluster: View PodDisruptionBudgets",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.pdbURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewServices",
|
||||
title: "Cluster: View Services",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.servicesURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewEndpoints",
|
||||
title: "Cluster: View Endpoints",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.endpointURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewIngresses",
|
||||
title: "Cluster: View Ingresses",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.ingressURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewNetworkPolicies",
|
||||
title: "Cluster: View NetworkPolicies",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.networkPoliciesURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewNodes",
|
||||
title: "Cluster: View Nodes",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.nodesURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewPods",
|
||||
title: "Cluster: View Pods",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.podsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewDeployments",
|
||||
title: "Cluster: View Deployments",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.deploymentsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewDaemonSets",
|
||||
title: "Cluster: View DaemonSets",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.daemonSetsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewStatefulSets",
|
||||
title: "Cluster: View StatefulSets",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.statefulSetsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewJobs",
|
||||
title: "Cluster: View Jobs",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.jobsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewCronJobs",
|
||||
title: "Cluster: View CronJobs",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.cronJobsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewCustomResourceDefinitions",
|
||||
title: "Cluster: View Custom Resource Definitions",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.crdURL()),
|
||||
},
|
||||
{
|
||||
id: "entity.viewSettings",
|
||||
title: ({ entity }) => `${entity.kind}/${entity.getName()}: View Settings`,
|
||||
action: ({ entity, navigate }) => navigate(`/entity/${entity.getId()}/settings`, {
|
||||
forceRootFrame: true,
|
||||
}),
|
||||
isActive: ({ entity }) => {
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getEntitySettingItems(entity.kind, entity.apiVersion, entity.metadata.source).length > 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "cluster.openTerminal",
|
||||
title: "Cluster: Open terminal",
|
||||
action: () => createTerminalTab(),
|
||||
isActive: isKubernetesClusterActive,
|
||||
},
|
||||
{
|
||||
id: "hotbar.switchHotbar",
|
||||
title: "Hotbar: Switch ...",
|
||||
action: () => openCommandDialog(<HotbarSwitchCommand />),
|
||||
},
|
||||
{
|
||||
id: "hotbar.addHotbar",
|
||||
title: "Hotbar: Add Hotbar ...",
|
||||
action: () => openCommandDialog(<HotbarAddCommand />),
|
||||
},
|
||||
{
|
||||
id: "hotbar.removeHotbar",
|
||||
title: "Hotbar: Remove Hotbar ...",
|
||||
action: () => openCommandDialog(<HotbarRemoveCommand />),
|
||||
},
|
||||
{
|
||||
id: "hotbar.renameHotbar",
|
||||
title: "Hotbar: Rename Hotbar ...",
|
||||
action: () => openCommandDialog(<HotbarRenameCommand />),
|
||||
},
|
||||
{
|
||||
id: "catalog.searchEntities",
|
||||
title: "Catalog: Activate Entity ...",
|
||||
action: () => openCommandDialog(<ActivateEntityCommand />),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const internalCommandsInjectable = getInjectable({
|
||||
instantiate: (di) => getInternalCommands({
|
||||
openCommandDialog: di.inject(commandOverlayInjectable).open,
|
||||
getEntitySettingItems: EntitySettingRegistry
|
||||
.getInstance()
|
||||
.getItemsForKind,
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default internalCommandsInjectable;
|
||||
@ -1,215 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import * as routes from "../../../../common/routes";
|
||||
import { EntitySettingRegistry } from "../../../../extensions/registries";
|
||||
import { CommandOverlay } from "../../../components/command-palette";
|
||||
import { createTerminalTab } from "../../../components/dock/terminal.store";
|
||||
import { HotbarAddCommand } from "../../../components/hotbar/hotbar-add-command";
|
||||
import { HotbarRemoveCommand } from "../../../components/hotbar/hotbar-remove-command";
|
||||
import { HotbarSwitchCommand } from "../../../components/hotbar/hotbar-switch-command";
|
||||
import { HotbarRenameCommand } from "../../../components/hotbar/hotbar-rename-command";
|
||||
import { ActivateEntityCommand } from "../../../components/activate-entity-command";
|
||||
import type { CommandContext, CommandRegistration } from "./commands";
|
||||
|
||||
export function isKubernetesClusterActive(context: CommandContext): boolean {
|
||||
return context.entity?.kind === "KubernetesCluster";
|
||||
}
|
||||
|
||||
export const internalCommands: CommandRegistration[] = [
|
||||
{
|
||||
id: "app.showPreferences",
|
||||
title: "Preferences: Open",
|
||||
action: ({ navigate }) => navigate(routes.preferencesURL(), {
|
||||
forceRootFrame: true,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewHelmCharts",
|
||||
title: "Cluster: View Helm Charts",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.helmChartsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewHelmReleases",
|
||||
title: "Cluster: View Helm Releases",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.releaseURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewConfigMaps",
|
||||
title: "Cluster: View ConfigMaps",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.configMapsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewSecrets",
|
||||
title: "Cluster: View Secrets",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.secretsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewResourceQuotas",
|
||||
title: "Cluster: View ResourceQuotas",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.resourceQuotaURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewLimitRanges",
|
||||
title: "Cluster: View LimitRanges",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.limitRangeURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewHorizontalPodAutoscalers",
|
||||
title: "Cluster: View HorizontalPodAutoscalers (HPA)",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.hpaURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewPodDisruptionBudget",
|
||||
title: "Cluster: View PodDisruptionBudgets",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.pdbURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewServices",
|
||||
title: "Cluster: View Services",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.servicesURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewEndpoints",
|
||||
title: "Cluster: View Endpoints",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.endpointURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewIngresses",
|
||||
title: "Cluster: View Ingresses",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.ingressURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewNetworkPolicies",
|
||||
title: "Cluster: View NetworkPolicies",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.networkPoliciesURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewNodes",
|
||||
title: "Cluster: View Nodes",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.nodesURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewPods",
|
||||
title: "Cluster: View Pods",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.podsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewDeployments",
|
||||
title: "Cluster: View Deployments",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.deploymentsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewDaemonSets",
|
||||
title: "Cluster: View DaemonSets",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.daemonSetsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewStatefulSets",
|
||||
title: "Cluster: View StatefulSets",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.statefulSetsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewJobs",
|
||||
title: "Cluster: View Jobs",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.jobsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewCronJobs",
|
||||
title: "Cluster: View CronJobs",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.cronJobsURL()),
|
||||
},
|
||||
{
|
||||
id: "cluster.viewCustomResourceDefinitions",
|
||||
title: "Cluster: View Custom Resource Definitions",
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(routes.crdURL()),
|
||||
},
|
||||
{
|
||||
id: "entity.viewSettings",
|
||||
title: ({ entity }) => `${entity.kind}/${entity.getName()}: View Settings`,
|
||||
action: ({ entity, navigate }) => navigate(`/entity/${entity.getId()}/settings`, {
|
||||
forceRootFrame: true,
|
||||
}),
|
||||
isActive: ({ entity }) => {
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: replace with injection
|
||||
const entries = EntitySettingRegistry.getInstance()
|
||||
.getItemsForKind(entity.kind, entity.apiVersion, entity.metadata.source);
|
||||
|
||||
return entries.length > 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "cluster.openTerminal",
|
||||
title: "Cluster: Open terminal",
|
||||
action: () => createTerminalTab(),
|
||||
isActive: isKubernetesClusterActive,
|
||||
},
|
||||
{
|
||||
id: "hotbar.switchHotbar",
|
||||
title: "Hotbar: Switch ...",
|
||||
action: () => CommandOverlay.open(<HotbarSwitchCommand />),
|
||||
},
|
||||
{
|
||||
id: "hotbar.addHotbar",
|
||||
title: "Hotbar: Add Hotbar ...",
|
||||
action: () => CommandOverlay.open(<HotbarAddCommand />),
|
||||
},
|
||||
{
|
||||
id: "hotbar.removeHotbar",
|
||||
title: "Hotbar: Remove Hotbar ...",
|
||||
action: () => CommandOverlay.open(<HotbarRemoveCommand />),
|
||||
},
|
||||
{
|
||||
id: "hotbar.renameHotbar",
|
||||
title: "Hotbar: Rename Hotbar ...",
|
||||
action: () => CommandOverlay.open(<HotbarRenameCommand />),
|
||||
},
|
||||
{
|
||||
id: "catalog.searchEntities",
|
||||
title: "Catalog: Activate Entity ...",
|
||||
action: () => CommandOverlay.open(<ActivateEntityCommand />),
|
||||
},
|
||||
];
|
||||
@ -22,32 +22,30 @@
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { computed, IComputedValue } from "mobx";
|
||||
import type { CustomResourceDefinition } from "../../../../common/k8s-api/endpoints";
|
||||
import customResourceDefinitionsInjectable from "../../../../common/k8s-api/endpoints/custom-resources/custom-resources.injectable";
|
||||
import customResourceDefinitionsInjectable from "../../+custom-resources/custom-resources.injectable";
|
||||
import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
|
||||
import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable";
|
||||
import { iter } from "../../../utils";
|
||||
import type { RegisteredCommand } from "./commands";
|
||||
import { internalCommands, isKubernetesClusterActive } from "./internal-commands";
|
||||
import type { CommandRegistration, RegisteredCommand } from "./commands";
|
||||
import internalCommandsInjectable, { isKubernetesClusterActive } from "./internal-commands.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
extensions: IComputedValue<LensRendererExtension[]>;
|
||||
customResourceDefinitions: IComputedValue<CustomResourceDefinition[]>;
|
||||
internalCommands: CommandRegistration[];
|
||||
}
|
||||
|
||||
const instantiateRegisteredCommands = ({ extensions, customResourceDefinitions }: Dependencies) => computed(() => {
|
||||
const instantiateRegisteredCommands = ({ extensions, customResourceDefinitions, internalCommands }: Dependencies) => computed(() => {
|
||||
const result = new Map<string, RegisteredCommand>();
|
||||
const commands = iter.chain(
|
||||
internalCommands,
|
||||
iter.chain(
|
||||
iter.flatMap(extensions.get(), extension => extension.commands),
|
||||
iter.map(customResourceDefinitions.get(), command => ({
|
||||
id: `cluster.view.${command.getResourceKind()}`,
|
||||
title: `Cluster: View ${command.getResourceKind()}`,
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(command.getResourceUrl()),
|
||||
})),
|
||||
),
|
||||
);
|
||||
const commands = [
|
||||
...internalCommands,
|
||||
...extensions.get().flatMap(e => e.commands),
|
||||
...customResourceDefinitions.get().map((command): CommandRegistration => ({
|
||||
id: `cluster.view.${command.getResourceKind()}`,
|
||||
title: `Cluster: View ${command.getResourceKind()}`,
|
||||
isActive: isKubernetesClusterActive,
|
||||
action: ({ navigate }) => navigate(command.getResourceUrl()),
|
||||
})),
|
||||
];
|
||||
|
||||
for (const { scope, isActive = () => true, ...command } of commands) {
|
||||
if (!result.has(command.id)) {
|
||||
@ -62,6 +60,7 @@ const registeredCommandsInjectable = getInjectable({
|
||||
instantiate: (di) => instantiateRegisteredCommands({
|
||||
extensions: di.inject(rendererExtensionsInjectable),
|
||||
customResourceDefinitions: di.inject(customResourceDefinitionsInjectable),
|
||||
internalCommands: di.inject(internalCommandsInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
|
||||
@ -31,7 +31,7 @@ import { saveKubeconfig } from "./save-config";
|
||||
import { requestMain } from "../../../common/ipc";
|
||||
import { clusterClearDeletingHandler, clusterDeleteHandler, clusterSetDeletingHandler } from "../../../common/cluster-ipc";
|
||||
import { Notifications } from "../notifications";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { HotbarStore } from "../../../common/hotbar-store.injectable";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { Dialog } from "../dialog";
|
||||
import { Icon } from "../icon";
|
||||
|
||||
@ -21,13 +21,16 @@
|
||||
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import { HotbarRemoveCommand } from "../hotbar-remove-command";
|
||||
import { render, fireEvent } from "@testing-library/react";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { ThemeStore } from "../../../theme.store";
|
||||
import { UserStore } from "../../../../common/user-store";
|
||||
import { Notifications } from "../../notifications";
|
||||
import mockFs from "mock-fs";
|
||||
import { AppPaths } from "../../../../common/app-paths";
|
||||
import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import { type DiRender, renderFor } from "../../test-utils/renderFor";
|
||||
import hotbarManagerInjectable, { HotbarStore } from "../../../../common/hotbar-store.injectable";
|
||||
import { UserStore } from "../../../../common/user-store";
|
||||
import { ThemeStore } from "../../../theme.store";
|
||||
import { ConfirmDialog } from "../../confirm-dialog";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
@ -55,45 +58,58 @@ const mockHotbars: { [id: string]: any } = {
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock("../../../../common/hotbar-store", () => ({
|
||||
HotbarStore: {
|
||||
getInstance: () => ({
|
||||
hotbars: [mockHotbars["1"]],
|
||||
getById: (id: string) => mockHotbars[id],
|
||||
remove: () => {},
|
||||
hotbarIndex: () => 0,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
describe("<HotbarRemoveCommand />", () => {
|
||||
let di: ConfigurableDependencyInjectionContainer;
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
"tmp": {},
|
||||
});
|
||||
di = getDiForUnitTesting();
|
||||
render = renderFor(di);
|
||||
|
||||
UserStore.createInstance();
|
||||
ThemeStore.createInstance();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
UserStore.resetInstance();
|
||||
ThemeStore.resetInstance();
|
||||
mockFs.restore();
|
||||
UserStore.resetInstance();
|
||||
});
|
||||
|
||||
it("renders w/o errors", () => {
|
||||
di.override(hotbarManagerInjectable, () => ({
|
||||
hotbars: [mockHotbars["1"]],
|
||||
getById: (id: string) => mockHotbars[id],
|
||||
remove: () => { },
|
||||
hotbarIndex: () => 0,
|
||||
getDisplayLabel: () => "1: Default",
|
||||
}) as any as HotbarStore);
|
||||
|
||||
const { container } = render(<HotbarRemoveCommand/>);
|
||||
|
||||
expect(container).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it("displays error notification if user tries to remove last hotbar", () => {
|
||||
const spy = jest.spyOn(Notifications, "error");
|
||||
const { getByText } = render(<HotbarRemoveCommand/>);
|
||||
it("calls remove if you click on the entry", () => {
|
||||
const removeMock = jest.fn();
|
||||
|
||||
di.override(hotbarManagerInjectable, () => ({
|
||||
hotbars: [mockHotbars["1"]],
|
||||
getById: (id: string) => mockHotbars[id],
|
||||
remove: removeMock,
|
||||
hotbarIndex: () => 0,
|
||||
getDisplayLabel: () => "1: Default",
|
||||
}) as any as HotbarStore);
|
||||
|
||||
const { getByText } = render(
|
||||
<>
|
||||
<HotbarRemoveCommand />
|
||||
<ConfirmDialog />
|
||||
</>,
|
||||
);
|
||||
|
||||
fireEvent.click(getByText("1: Default"));
|
||||
fireEvent.click(getByText("Remove Hotbar"));
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
spy.mockRestore();
|
||||
expect(removeMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@ -21,9 +21,11 @@
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { CommandOverlay } from "../command-palette";
|
||||
import hotbarManagerInjectable, { HotbarStore } from "../../../common/hotbar-store.injectable";
|
||||
import { Input, InputValidator } from "../input";
|
||||
import type { CreateHotbarData, CreateHotbarOptions } from "../../../common/hotbar-types";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
||||
|
||||
export const uniqueHotbarName: InputValidator = {
|
||||
condition: ({ required }) => required,
|
||||
@ -31,34 +33,44 @@ export const uniqueHotbarName: InputValidator = {
|
||||
validate: value => !HotbarStore.getInstance().getByName(value),
|
||||
};
|
||||
|
||||
@observer
|
||||
export class HotbarAddCommand extends React.Component {
|
||||
onSubmit = (name: string) => {
|
||||
interface Dependencies {
|
||||
closeCommandOverlay: () => void;
|
||||
addHotbar: (data: CreateHotbarData, { setActive }?: CreateHotbarOptions) => void;
|
||||
}
|
||||
|
||||
const NonInjectedHotbarAddCommand = observer(({ closeCommandOverlay, addHotbar }: Dependencies) => {
|
||||
const onSubmit = (name: string) => {
|
||||
if (!name.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
HotbarStore.getInstance().add({ name }, { setActive: true });
|
||||
CommandOverlay.close();
|
||||
addHotbar({ name }, { setActive: true });
|
||||
closeCommandOverlay();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Hotbar name"
|
||||
autoFocus={true}
|
||||
theme="round-black"
|
||||
data-test-id="command-palette-hotbar-add-name"
|
||||
validators={uniqueHotbarName}
|
||||
onSubmit={this.onSubmit}
|
||||
dirty={true}
|
||||
showValidationLine={true}
|
||||
/>
|
||||
<small className="hint">
|
||||
Please provide a new hotbar name (Press "Enter" to confirm or "Escape" to cancel)
|
||||
</small>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Hotbar name"
|
||||
autoFocus={true}
|
||||
theme="round-black"
|
||||
data-test-id="command-palette-hotbar-add-name"
|
||||
validators={uniqueHotbarName}
|
||||
onSubmit={onSubmit}
|
||||
dirty={true}
|
||||
showValidationLine={true}
|
||||
/>
|
||||
<small className="hint">
|
||||
Please provide a new hotbar name (Press "Enter" to confirm or "Escape" to cancel)
|
||||
</small>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export const HotbarAddCommand = withInjectables<Dependencies>(NonInjectedHotbarAddCommand, {
|
||||
getProps: (di, props) => ({
|
||||
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
|
||||
addHotbar: di.inject(hotbarManagerInjectable).add,
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -26,7 +26,7 @@ import { observer } from "mobx-react";
|
||||
import { HotbarEntityIcon } from "./hotbar-entity-icon";
|
||||
import { cssNames, IClassName } from "../../utils";
|
||||
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { HotbarStore } from "../../../common/hotbar-store.injectable";
|
||||
import type { CatalogEntity } from "../../api/catalog-entity";
|
||||
import { DragDropContext, Draggable, Droppable, DropResult } from "react-beautiful-dnd";
|
||||
import { HotbarSelector } from "./hotbar-selector";
|
||||
|
||||
@ -22,51 +22,44 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Select } from "../select";
|
||||
import { computed, makeObservable } from "mobx";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { hotbarDisplayLabel } from "./hotbar-display-label";
|
||||
import { CommandOverlay } from "../command-palette";
|
||||
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { Notifications } from "../notifications";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
||||
import type { Hotbar } from "../../../common/hotbar-types";
|
||||
|
||||
@observer
|
||||
export class HotbarRemoveCommand extends React.Component {
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
interface Dependencies {
|
||||
closeCommandOverlay: () => void;
|
||||
hotbarManager: {
|
||||
hotbars: Hotbar[];
|
||||
getById: (id: string) => Hotbar | undefined;
|
||||
remove: (hotbar: Hotbar) => void;
|
||||
getDisplayLabel: (hotbar: Hotbar) => string;
|
||||
};
|
||||
}
|
||||
|
||||
@computed get options() {
|
||||
return HotbarStore.getInstance().hotbars.map((hotbar) => {
|
||||
return { value: hotbar.id, label: hotbarDisplayLabel(hotbar.id) };
|
||||
});
|
||||
}
|
||||
const NonInjectedHotbarRemoveCommand = observer(({ closeCommandOverlay, hotbarManager }: Dependencies) => {
|
||||
const options = hotbarManager.hotbars.map(hotbar => ({
|
||||
value: hotbar.id,
|
||||
label: hotbarManager.getDisplayLabel(hotbar),
|
||||
}));
|
||||
|
||||
onChange(id: string): void {
|
||||
const hotbarStore = HotbarStore.getInstance();
|
||||
const hotbar = hotbarStore.getById(id);
|
||||
|
||||
CommandOverlay.close();
|
||||
const onChange = (id: string): void => {
|
||||
const hotbar = hotbarManager.getById(id);
|
||||
|
||||
if (!hotbar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hotbarStore.hotbars.length === 1) {
|
||||
Notifications.error("Can't remove the last hotbar");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
closeCommandOverlay();
|
||||
// TODO: make confirm dialog injectable
|
||||
ConfirmDialog.open({
|
||||
okButtonProps: {
|
||||
label: `Remove Hotbar`,
|
||||
label: "Remove Hotbar",
|
||||
primary: false,
|
||||
accent: true,
|
||||
},
|
||||
ok: () => {
|
||||
hotbarStore.remove(hotbar);
|
||||
},
|
||||
ok: () => hotbarManager.remove(hotbar),
|
||||
message: (
|
||||
<div className="confirm flex column gaps">
|
||||
<p>
|
||||
@ -75,19 +68,26 @@ export class HotbarRemoveCommand extends React.Component {
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Select
|
||||
menuPortalTarget={null}
|
||||
onChange={(v) => this.onChange(v.value)}
|
||||
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||
menuIsOpen={true}
|
||||
options={this.options}
|
||||
autoFocus={true}
|
||||
escapeClearsValue={false}
|
||||
placeholder="Remove hotbar" />
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Select
|
||||
menuPortalTarget={null}
|
||||
onChange={(v) => onChange(v.value)}
|
||||
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||
menuIsOpen={true}
|
||||
options={options}
|
||||
autoFocus={true}
|
||||
escapeClearsValue={false}
|
||||
placeholder="Remove hotbar"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const HotbarRemoveCommand = withInjectables<Dependencies>(NonInjectedHotbarRemoveCommand, {
|
||||
getProps: (di, props) => ({
|
||||
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
|
||||
hotbarManager: di.inject(hotbarManagerInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -19,81 +19,60 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Select } from "../select";
|
||||
import { action, computed, makeObservable, observable } from "mobx";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { hotbarDisplayLabel } from "./hotbar-display-label";
|
||||
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
|
||||
import { Input } from "../input";
|
||||
import { uniqueHotbarName } from "./hotbar-add-command";
|
||||
import { CommandOverlay } from "../command-palette";
|
||||
import type { Hotbar } from "../../../common/hotbar-types";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
||||
|
||||
@observer
|
||||
export class HotbarRenameCommand extends React.Component {
|
||||
@observable hotbarId = "";
|
||||
@observable hotbarName = "";
|
||||
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@computed get options() {
|
||||
return HotbarStore.getInstance().hotbars.map((hotbar) => {
|
||||
return { value: hotbar.id, label: hotbarDisplayLabel(hotbar.id) };
|
||||
});
|
||||
}
|
||||
|
||||
@action onSelect = (id: string) => {
|
||||
this.hotbarId = id;
|
||||
this.hotbarName = HotbarStore.getInstance().getById(this.hotbarId).name;
|
||||
interface Dependencies {
|
||||
closeCommandOverlay: () => void;
|
||||
hotbarManager: {
|
||||
hotbars: Hotbar[];
|
||||
getById: (id: string) => Hotbar | undefined;
|
||||
setHotbarName: (id: string, name: string) => void;
|
||||
getDisplayLabel: (hotbar: Hotbar) => string;
|
||||
};
|
||||
}
|
||||
|
||||
onSubmit = (name: string) => {
|
||||
const NonInjectedHotbarRenameCommand = observer(({ closeCommandOverlay, hotbarManager }: Dependencies) => {
|
||||
const [hotbarId, setHotbarId] = useState("");
|
||||
const [hotbarName, setHotbarName] = useState("");
|
||||
|
||||
const options = hotbarManager.hotbars.map(hotbar => ({
|
||||
value: hotbar.id,
|
||||
label: hotbarManager.getDisplayLabel(hotbar),
|
||||
}));
|
||||
|
||||
const onSelect = (id: string) => {
|
||||
setHotbarId(id);
|
||||
setHotbarName(hotbarManager.getById(id).name);
|
||||
};
|
||||
const onSubmit = (name: string) => {
|
||||
if (!name.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hotbarStore = HotbarStore.getInstance();
|
||||
const hotbar = HotbarStore.getInstance().getById(this.hotbarId);
|
||||
|
||||
if (!hotbar) {
|
||||
return;
|
||||
}
|
||||
|
||||
hotbarStore.setHotbarName(this.hotbarId, name);
|
||||
CommandOverlay.close();
|
||||
hotbarManager.setHotbarName(hotbarId, name);
|
||||
closeCommandOverlay();
|
||||
};
|
||||
|
||||
renderHotbarList() {
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
menuPortalTarget={null}
|
||||
onChange={(v) => this.onSelect(v.value)}
|
||||
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||
menuIsOpen={true}
|
||||
options={this.options}
|
||||
autoFocus={true}
|
||||
escapeClearsValue={false}
|
||||
placeholder="Rename hotbar"/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderNameInput() {
|
||||
if (hotbarId) {
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
trim={true}
|
||||
value={this.hotbarName}
|
||||
onChange={v => this.hotbarName = v}
|
||||
value={hotbarName}
|
||||
onChange={setHotbarName}
|
||||
placeholder="New hotbar name"
|
||||
autoFocus={true}
|
||||
theme="round-black"
|
||||
validators={uniqueHotbarName}
|
||||
onSubmit={this.onSubmit}
|
||||
onSubmit={onSubmit}
|
||||
showValidationLine={true}
|
||||
/>
|
||||
<small className="hint">
|
||||
@ -103,12 +82,24 @@ export class HotbarRenameCommand extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Select
|
||||
menuPortalTarget={null}
|
||||
onChange={(v) => onSelect(v.value)}
|
||||
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||
menuIsOpen={true}
|
||||
options={options}
|
||||
autoFocus={true}
|
||||
escapeClearsValue={false}
|
||||
placeholder="Rename hotbar"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{!this.hotbarId ? this.renderHotbarList() : this.renderNameInput()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
export const HotbarRenameCommand = withInjectables<Dependencies>(NonInjectedHotbarRenameCommand, {
|
||||
getProps: (di, props) => ({
|
||||
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
|
||||
hotbarManager: di.inject(hotbarManagerInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -23,38 +23,52 @@ import "./hotbar-selector.scss";
|
||||
import React from "react";
|
||||
import { Icon } from "../icon";
|
||||
import { Badge } from "../badge";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { CommandOverlay } from "../command-palette";
|
||||
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
|
||||
import { HotbarSwitchCommand } from "./hotbar-switch-command";
|
||||
import { hotbarDisplayIndex } from "./hotbar-display-label";
|
||||
import { TooltipPosition } from "../tooltip";
|
||||
import { observer } from "mobx-react";
|
||||
import type { Hotbar } from "../../../common/hotbar-types";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
||||
|
||||
interface Props {
|
||||
export interface HotbarSelectorProps {
|
||||
hotbar: Hotbar;
|
||||
}
|
||||
|
||||
export const HotbarSelector = observer(({ hotbar }: Props) => {
|
||||
const store = HotbarStore.getInstance();
|
||||
interface Dependencies {
|
||||
hotbarManager: {
|
||||
switchToPrevious: () => void;
|
||||
switchToNext: () => void;
|
||||
getActive: () => Hotbar;
|
||||
getDisplayIndex: (hotbar: Hotbar) => string;
|
||||
};
|
||||
openCommandOverlay: (component: React.ReactElement) => void;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="HotbarSelector flex align-center">
|
||||
<Icon material="play_arrow" className="previous box" onClick={() => store.switchToPrevious()} />
|
||||
<div className="box grow flex align-center">
|
||||
<Badge
|
||||
id="hotbarIndex"
|
||||
small
|
||||
label={hotbarDisplayIndex(store.activeHotbarId)}
|
||||
onClick={() => CommandOverlay.open(<HotbarSwitchCommand />)}
|
||||
tooltip={{
|
||||
preferredPositions: [TooltipPosition.TOP, TooltipPosition.TOP_LEFT],
|
||||
children: hotbar.name,
|
||||
}}
|
||||
className="SelectorIndex"
|
||||
/>
|
||||
</div>
|
||||
<Icon material="play_arrow" className="next box" onClick={() => store.switchToNext()} />
|
||||
const NonInjectedHotbarSelector = observer(({ hotbar, hotbarManager, openCommandOverlay }: HotbarSelectorProps & Dependencies) => (
|
||||
<div className="HotbarSelector flex align-center">
|
||||
<Icon material="play_arrow" className="previous box" onClick={() => hotbarManager.switchToPrevious()} />
|
||||
<div className="box grow flex align-center">
|
||||
<Badge
|
||||
id="hotbarIndex"
|
||||
small
|
||||
label={hotbarManager.getDisplayIndex(hotbarManager.getActive())}
|
||||
onClick={() => openCommandOverlay(<HotbarSwitchCommand />)}
|
||||
tooltip={{
|
||||
preferredPositions: [TooltipPosition.TOP, TooltipPosition.TOP_LEFT],
|
||||
children: hotbar.name,
|
||||
}}
|
||||
className="SelectorIndex"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
<Icon material="play_arrow" className="next box" onClick={() => hotbarManager.switchToNext()} />
|
||||
</div>
|
||||
));
|
||||
|
||||
export const HotbarSelector = withInjectables<Dependencies, HotbarSelectorProps>(NonInjectedHotbarSelector, {
|
||||
getProps: (di, props) => ({
|
||||
hotbarManager: di.inject(hotbarManagerInjectable),
|
||||
openCommandOverlay: di.inject(commandOverlayInjectable).open,
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -22,73 +22,82 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Select } from "../select";
|
||||
import { computed, makeObservable } from "mobx";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { CommandOverlay } from "../command-palette";
|
||||
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
|
||||
import type { CommandOverlay } from "../command-palette";
|
||||
import { HotbarAddCommand } from "./hotbar-add-command";
|
||||
import { HotbarRemoveCommand } from "./hotbar-remove-command";
|
||||
import { hotbarDisplayLabel } from "./hotbar-display-label";
|
||||
import { HotbarRenameCommand } from "./hotbar-rename-command";
|
||||
import type { Hotbar } from "../../../common/hotbar-types";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
||||
|
||||
@observer
|
||||
export class HotbarSwitchCommand extends React.Component {
|
||||
private static addActionId = "__add__";
|
||||
private static removeActionId = "__remove__";
|
||||
private static renameActionId = "__rename__";
|
||||
const addActionId = "__add__";
|
||||
const removeActionId = "__remove__";
|
||||
const renameActionId = "__rename__";
|
||||
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@computed get options() {
|
||||
const hotbarStore = HotbarStore.getInstance();
|
||||
const options = hotbarStore.hotbars.map((hotbar) => {
|
||||
return { value: hotbar.id, label: hotbarDisplayLabel(hotbar.id) };
|
||||
});
|
||||
|
||||
options.push({ value: HotbarSwitchCommand.addActionId, label: "Add hotbar ..." });
|
||||
|
||||
if (hotbarStore.hotbars.length > 1) {
|
||||
options.push({ value: HotbarSwitchCommand.removeActionId, label: "Remove hotbar ..." });
|
||||
}
|
||||
|
||||
options.push({ value: HotbarSwitchCommand.renameActionId, label: "Rename hotbar ..." });
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
onChange(idOrAction: string): void {
|
||||
switch (idOrAction) {
|
||||
case HotbarSwitchCommand.addActionId:
|
||||
CommandOverlay.open(<HotbarAddCommand />);
|
||||
|
||||
return;
|
||||
case HotbarSwitchCommand.removeActionId:
|
||||
CommandOverlay.open(<HotbarRemoveCommand />);
|
||||
|
||||
return;
|
||||
case HotbarSwitchCommand.renameActionId:
|
||||
CommandOverlay.open(<HotbarRenameCommand />);
|
||||
|
||||
return;
|
||||
default:
|
||||
HotbarStore.getInstance().activeHotbarId = idOrAction;
|
||||
CommandOverlay.close();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Select
|
||||
menuPortalTarget={null}
|
||||
onChange={(v) => this.onChange(v.value)}
|
||||
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||
menuIsOpen={true}
|
||||
options={this.options}
|
||||
autoFocus={true}
|
||||
escapeClearsValue={false}
|
||||
placeholder="Switch to hotbar" />
|
||||
);
|
||||
}
|
||||
interface HotbarManager {
|
||||
hotbars: Hotbar[];
|
||||
setActiveHotbar: (id: string) => void;
|
||||
getDisplayLabel: (hotbar: Hotbar) => string;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
hotbarManager: HotbarManager
|
||||
commandOverlay: CommandOverlay;
|
||||
}
|
||||
|
||||
function getHotbarSwitchOptions(hotbarManager: HotbarManager) {
|
||||
const options = hotbarManager.hotbars.map(hotbar => ({
|
||||
value: hotbar.id,
|
||||
label: hotbarManager.getDisplayLabel(hotbar),
|
||||
}));
|
||||
|
||||
options.push({ value: addActionId, label: "Add hotbar ..." });
|
||||
|
||||
if (hotbarManager.hotbars.length > 1) {
|
||||
options.push({ value: removeActionId, label: "Remove hotbar ..." });
|
||||
}
|
||||
|
||||
options.push({ value: renameActionId, label: "Rename hotbar ..." });
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
const NonInjectedHotbarSwitchCommand = observer(({ hotbarManager, commandOverlay }: Dependencies) => {
|
||||
const options = getHotbarSwitchOptions(hotbarManager);
|
||||
|
||||
const onChange = (idOrAction: string): void => {
|
||||
switch (idOrAction) {
|
||||
case addActionId:
|
||||
return commandOverlay.open(<HotbarAddCommand />);
|
||||
case removeActionId:
|
||||
return commandOverlay.open(<HotbarRemoveCommand />);
|
||||
case renameActionId:
|
||||
return commandOverlay.open(<HotbarRenameCommand />);
|
||||
default:
|
||||
hotbarManager.setActiveHotbar(idOrAction);
|
||||
commandOverlay.close();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
menuPortalTarget={null}
|
||||
onChange={(v) => onChange(v.value)}
|
||||
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||
menuIsOpen={true}
|
||||
options={options}
|
||||
autoFocus={true}
|
||||
escapeClearsValue={false}
|
||||
placeholder="Switch to hotbar"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const HotbarSwitchCommand = withInjectables<Dependencies>(NonInjectedHotbarSwitchCommand, {
|
||||
getProps: (di, props) => ({
|
||||
hotbarManager: di.inject(hotbarManagerInjectable),
|
||||
commandOverlay: di.inject(commandOverlayInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -29,7 +29,7 @@ import type { KubeObjectMenuRegistration } from "../../../extensions/registries"
|
||||
import { KubeObjectMenuRegistry } from "../../../extensions/registries";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
|
||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
|
||||
import clusterInjectable from "./dependencies/cluster.injectable";
|
||||
import hideDetailsInjectable from "./dependencies/hide-details.injectable";
|
||||
@ -60,8 +60,7 @@ describe("kube-object-menu", () => {
|
||||
}) as Cluster);
|
||||
|
||||
di.override(apiManagerInjectable, () => ({
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||
getStore: api => undefined,
|
||||
getStore: api => void api,
|
||||
}) as ApiManager);
|
||||
|
||||
di.override(hideDetailsInjectable, () => () => {});
|
||||
|
||||
@ -25,7 +25,7 @@ import { render, fireEvent } from "@testing-library/react";
|
||||
import { SidebarCluster } from "../sidebar-cluster";
|
||||
import { KubernetesCluster } from "../../../../common/catalog-entities";
|
||||
|
||||
jest.mock("../../../../common/hotbar-store", () => ({
|
||||
jest.mock("../../../../common/hotbar-store.injectable", () => ({
|
||||
HotbarStore: {
|
||||
getInstance: () => ({
|
||||
isAddedToActive: jest.fn(),
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
import styles from "./sidebar-cluster.module.scss";
|
||||
import { observable } from "mobx";
|
||||
import React, { useState } from "react";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { HotbarStore } from "../../../common/hotbar-store.injectable";
|
||||
import { broadcastMessage } from "../../../common/ipc";
|
||||
import type { CatalogEntity, CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
|
||||
import { IpcRendererNavigationEvents } from "../../navigation/events";
|
||||
|
||||
@ -26,7 +26,7 @@ import { TopBar } from "./top-bar";
|
||||
import { IpcMainWindowEvents } from "../../../../main/window-manager";
|
||||
import { broadcastMessage } from "../../../../common/ipc";
|
||||
import * as vars from "../../../../common/vars";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import { DiRender, renderFor } from "../../test-utils/renderFor";
|
||||
|
||||
const mockConfig = vars as { isWindows: boolean; isLinux: boolean };
|
||||
|
||||
@ -23,7 +23,7 @@ import React from "react";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import { TopBar } from "./top-bar";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
|
||||
import { DiRender, renderFor } from "../../test-utils/renderFor";
|
||||
import topBarItemsInjectable from "./top-bar-items/top-bar-items.injectable";
|
||||
|
||||
@ -21,27 +21,20 @@
|
||||
|
||||
import glob from "glob";
|
||||
import { memoize } from "lodash/fp";
|
||||
|
||||
import {
|
||||
createContainer,
|
||||
ConfigurableDependencyInjectionContainer,
|
||||
} from "@ogre-tools/injectable";
|
||||
import { createContainer } from "@ogre-tools/injectable";
|
||||
|
||||
export const getDiForUnitTesting = () => {
|
||||
const di: ConfigurableDependencyInjectionContainer = createContainer();
|
||||
const di = createContainer();
|
||||
|
||||
getInjectableFilePaths()
|
||||
.map(key => {
|
||||
const injectable = require(key).default;
|
||||
for (const filePath of getInjectableFilePaths()) {
|
||||
const injectableInstance = require(filePath).default;
|
||||
|
||||
return {
|
||||
id: key,
|
||||
...injectable,
|
||||
aliases: [injectable, ...(injectable.aliases || [])],
|
||||
};
|
||||
})
|
||||
|
||||
.forEach(injectable => di.register(injectable));
|
||||
di.register({
|
||||
id: filePath,
|
||||
...injectableInstance,
|
||||
aliases: [injectableInstance, ...(injectableInstance.aliases || [])],
|
||||
});
|
||||
}
|
||||
|
||||
di.preventSideEffects();
|
||||
|
||||
@ -50,5 +43,6 @@ export const getDiForUnitTesting = () => {
|
||||
|
||||
const getInjectableFilePaths = memoize(() => [
|
||||
...glob.sync("./**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
||||
...glob.sync("../../extensions/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
||||
...glob.sync("../common/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
||||
...glob.sync("../extensions/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
||||
]);
|
||||
@ -22,32 +22,12 @@
|
||||
import React from "react";
|
||||
import fs from "fs";
|
||||
import "../../common/catalog-entities/kubernetes-cluster";
|
||||
import { WebLinkCategory } from "../../common/catalog-entities";
|
||||
import { ClusterStore } from "../../common/cluster-store";
|
||||
import { catalogCategoryRegistry } from "../api/catalog-category-registry";
|
||||
import { WeblinkAddCommand } from "../components/catalog-entities/weblink-add-command";
|
||||
import { CommandOverlay } from "../components/command-palette";
|
||||
import { loadConfigFromString } from "../../common/kube-helpers";
|
||||
import { DeleteClusterDialog } from "../components/delete-cluster-dialog";
|
||||
|
||||
function initWebLinks() {
|
||||
WebLinkCategory.onAdd = () => CommandOverlay.open(<WeblinkAddCommand />);
|
||||
}
|
||||
|
||||
function initKubernetesClusters() {
|
||||
catalogCategoryRegistry
|
||||
.getForGroupKind("entity.k8slens.dev", "KubernetesCluster")
|
||||
.on("contextMenuOpen", (entity, context) => {
|
||||
if (entity.metadata?.source == "local") {
|
||||
context.menuItems.push({
|
||||
title: "Delete",
|
||||
icon: "delete",
|
||||
onClick: () => onClusterDelete(entity.metadata.uid),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function onClusterDelete(clusterId: string) {
|
||||
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||
|
||||
@ -64,7 +44,29 @@ async function onClusterDelete(clusterId: string) {
|
||||
DeleteClusterDialog.open({ cluster, config });
|
||||
}
|
||||
|
||||
export function initCatalog() {
|
||||
initWebLinks();
|
||||
initKubernetesClusters();
|
||||
interface Dependencies {
|
||||
openCommandDialog: (component: React.ReactElement) => void;
|
||||
}
|
||||
|
||||
export function initCatalog({ openCommandDialog }: Dependencies) {
|
||||
catalogCategoryRegistry
|
||||
.getForGroupKind("entity.k8slens.dev", "WebLink")
|
||||
.on("catalogAddMenu", ctx => {
|
||||
ctx.menuItems.push({
|
||||
title: "Add web link",
|
||||
icon: "public",
|
||||
onClick: () => openCommandDialog(<WeblinkAddCommand />),
|
||||
});
|
||||
});
|
||||
catalogCategoryRegistry
|
||||
.getForGroupKind("entity.k8slens.dev", "KubernetesCluster")
|
||||
.on("contextMenuOpen", (entity, context) => {
|
||||
if (entity.metadata?.source == "local") {
|
||||
context.menuItems.push({
|
||||
title: "Delete",
|
||||
icon: "delete",
|
||||
onClick: () => onClusterDelete(entity.metadata.uid),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -19,18 +19,18 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import type { Disposer } from "../utils";
|
||||
|
||||
function hotbarIndex(id: string) {
|
||||
return HotbarStore.getInstance().hotbarIndex(id) + 1;
|
||||
function addWindowEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): Disposer {
|
||||
window.addEventListener(type, listener, options);
|
||||
|
||||
return () => void window.removeEventListener(type, listener);
|
||||
}
|
||||
|
||||
export function hotbarDisplayLabel(id: string) : string {
|
||||
const hotbar = HotbarStore.getInstance().getById(id);
|
||||
const windowAddEventListenerInjectable = getInjectable({
|
||||
instantiate: () => addWindowEventListener,
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
return `${hotbarIndex(id)}: ${hotbar.name}`;
|
||||
}
|
||||
|
||||
export function hotbarDisplayIndex(id: string) : string {
|
||||
return hotbarIndex(id).toString();
|
||||
}
|
||||
export default windowAddEventListenerInjectable;
|
||||
Loading…
Reference in New Issue
Block a user