mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Remove global shared state usages of hot bar store
Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
a1a088c655
commit
ad7369988a
@ -7,11 +7,13 @@ import { anyObject } from "jest-mock-extended";
|
|||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
||||||
import { HotbarStore } from "../hotbar-store";
|
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable";
|
import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable";
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
|
import hotbarStoreInjectable from "../hotbar-store.injectable";
|
||||||
|
import { HotbarStore } from "../hotbar-store";
|
||||||
|
import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
||||||
|
|
||||||
jest.mock("../../main/catalog/catalog-entity-registry", () => ({
|
jest.mock("../../main/catalog/catalog-entity-registry", () => ({
|
||||||
catalogEntityRegistry: {
|
catalogEntityRegistry: {
|
||||||
@ -123,12 +125,21 @@ const awsCluster = getMockCatalogEntity({
|
|||||||
|
|
||||||
describe("HotbarStore", () => {
|
describe("HotbarStore", () => {
|
||||||
let di: DiContainer;
|
let di: DiContainer;
|
||||||
|
let hotbarStore: HotbarStore;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(appVersionInjectable);
|
di.permitSideEffects(appVersionInjectable);
|
||||||
|
|
||||||
|
di.override(hotbarStoreInjectable, () => {
|
||||||
|
HotbarStore.resetInstance();
|
||||||
|
|
||||||
|
return HotbarStore.createInstance({
|
||||||
|
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -140,18 +151,18 @@ describe("HotbarStore", () => {
|
|||||||
mockFs();
|
mockFs();
|
||||||
|
|
||||||
await di.runSetups();
|
await di.runSetups();
|
||||||
|
|
||||||
|
hotbarStore = di.inject(hotbarStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("load", () => {
|
describe("load", () => {
|
||||||
it("loads one hotbar by default", () => {
|
it("loads one hotbar by default", () => {
|
||||||
expect(HotbarStore.getInstance().hotbars.length).toEqual(1);
|
expect(hotbarStore.hotbars.length).toEqual(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("add", () => {
|
describe("add", () => {
|
||||||
it("adds a hotbar", () => {
|
it("adds a hotbar", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.add({ name: "hottest" });
|
hotbarStore.add({ name: "hottest" });
|
||||||
expect(hotbarStore.hotbars.length).toEqual(2);
|
expect(hotbarStore.hotbars.length).toEqual(2);
|
||||||
});
|
});
|
||||||
@ -159,20 +170,14 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
describe("hotbar items", () => {
|
describe("hotbar items", () => {
|
||||||
it("initially creates 12 empty cells", () => {
|
it("initially creates 12 empty cells", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items.length).toEqual(12);
|
expect(hotbarStore.getActive().items.length).toEqual(12);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("initially adds catalog entity as first item", () => {
|
it("initially adds catalog entity as first item", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[0].entity.name).toEqual("Catalog");
|
expect(hotbarStore.getActive().items[0].entity.name).toEqual("Catalog");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds items", () => {
|
it("adds items", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||||
|
|
||||||
@ -180,8 +185,6 @@ describe("HotbarStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("removes items", () => {
|
it("removes items", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.removeFromHotbar("test");
|
hotbarStore.removeFromHotbar("test");
|
||||||
hotbarStore.removeFromHotbar("catalog-entity");
|
hotbarStore.removeFromHotbar("catalog-entity");
|
||||||
@ -191,8 +194,6 @@ describe("HotbarStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does nothing if removing with invalid uid", () => {
|
it("does nothing if removing with invalid uid", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.removeFromHotbar("invalid uid");
|
hotbarStore.removeFromHotbar("invalid uid");
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||||
@ -201,8 +202,6 @@ describe("HotbarStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("moves item to empty cell", () => {
|
it("moves item to empty cell", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
hotbarStore.addToHotbar(minikubeCluster);
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
hotbarStore.addToHotbar(awsCluster);
|
||||||
@ -216,8 +215,6 @@ describe("HotbarStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("moves items down", () => {
|
it("moves items down", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
hotbarStore.addToHotbar(minikubeCluster);
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
hotbarStore.addToHotbar(awsCluster);
|
||||||
@ -231,8 +228,6 @@ describe("HotbarStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("moves items up", () => {
|
it("moves items up", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
hotbarStore.addToHotbar(minikubeCluster);
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
hotbarStore.addToHotbar(awsCluster);
|
||||||
@ -246,8 +241,6 @@ describe("HotbarStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("logs an error if cellIndex is out of bounds", () => {
|
it("logs an error if cellIndex is out of bounds", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.add({ name: "hottest", id: "hottest" });
|
hotbarStore.add({ name: "hottest", id: "hottest" });
|
||||||
hotbarStore.setActiveHotbar("hottest");
|
hotbarStore.setActiveHotbar("hottest");
|
||||||
|
|
||||||
@ -269,22 +262,16 @@ describe("HotbarStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("throws an error if getId is invalid or returns not a string", () => {
|
it("throws an error if getId is invalid or returns not a string", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
expect(() => hotbarStore.addToHotbar({} as any)).toThrowError(TypeError);
|
expect(() => hotbarStore.addToHotbar({} as any)).toThrowError(TypeError);
|
||||||
expect(() => hotbarStore.addToHotbar({ getId: () => true } as any)).toThrowError(TypeError);
|
expect(() => hotbarStore.addToHotbar({ getId: () => true } as any)).toThrowError(TypeError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws an error if getName is invalid or returns not a string", () => {
|
it("throws an error if getName is invalid or returns not a string", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
expect(() => hotbarStore.addToHotbar({ getId: () => "" } as any)).toThrowError(TypeError);
|
expect(() => hotbarStore.addToHotbar({ getId: () => "" } as any)).toThrowError(TypeError);
|
||||||
expect(() => hotbarStore.addToHotbar({ getId: () => "", getName: () => 4 } as any)).toThrowError(TypeError);
|
expect(() => hotbarStore.addToHotbar({ getId: () => "", getName: () => 4 } as any)).toThrowError(TypeError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does nothing when item moved to same cell", () => {
|
it("does nothing when item moved to same cell", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.restackItems(1, 1);
|
hotbarStore.restackItems(1, 1);
|
||||||
|
|
||||||
@ -292,8 +279,6 @@ describe("HotbarStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("new items takes first empty cell", () => {
|
it("new items takes first empty cell", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
hotbarStore.addToHotbar(awsCluster);
|
||||||
hotbarStore.restackItems(0, 3);
|
hotbarStore.restackItems(0, 3);
|
||||||
@ -309,8 +294,6 @@ describe("HotbarStore", () => {
|
|||||||
console.error = jest.fn();
|
console.error = jest.fn();
|
||||||
console.warn = jest.fn();
|
console.warn = jest.fn();
|
||||||
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
|
|
||||||
expect(() => hotbarStore.restackItems(-5, 0)).toThrow();
|
expect(() => hotbarStore.restackItems(-5, 0)).toThrow();
|
||||||
@ -324,8 +307,6 @@ describe("HotbarStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("checks if entity already pinned to hotbar", () => {
|
it("checks if entity already pinned to hotbar", () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
|
|
||||||
expect(hotbarStore.isAddedToActive(testCluster)).toBeTruthy();
|
expect(hotbarStore.isAddedToActive(testCluster)).toBeTruthy();
|
||||||
@ -401,22 +382,24 @@ describe("HotbarStore", () => {
|
|||||||
mockFs(configurationToBeMigrated);
|
mockFs(configurationToBeMigrated);
|
||||||
|
|
||||||
await di.runSetups();
|
await di.runSetups();
|
||||||
|
|
||||||
|
hotbarStore = di.inject(hotbarStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows to retrieve a hotbar", () => {
|
it("allows to retrieve a hotbar", () => {
|
||||||
const hotbar = HotbarStore.getInstance().getById("3caac17f-aec2-4723-9694-ad204465d935");
|
const hotbar = hotbarStore.getById("3caac17f-aec2-4723-9694-ad204465d935");
|
||||||
|
|
||||||
expect(hotbar.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
|
expect(hotbar.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clears cells without entity", () => {
|
it("clears cells without entity", () => {
|
||||||
const items = HotbarStore.getInstance().hotbars[0].items;
|
const items = hotbarStore.hotbars[0].items;
|
||||||
|
|
||||||
expect(items[2]).toBeNull();
|
expect(items[2]).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds extra data to cells with according entity", () => {
|
it("adds extra data to cells with according entity", () => {
|
||||||
const items = HotbarStore.getInstance().hotbars[0].items;
|
const items = hotbarStore.hotbars[0].items;
|
||||||
|
|
||||||
expect(items[0]).toEqual({
|
expect(items[0]).toEqual({
|
||||||
entity: {
|
entity: {
|
||||||
|
|||||||
@ -2,366 +2,9 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import catalogCatalogEntityInjectable from "./catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
import catalogCatalogEntityInjectable from "./catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
||||||
import { action, comparer, observable, makeObservable, computed } from "mobx";
|
import { HotbarStore } from "./hotbar-store";
|
||||||
import { BaseStore } from "./base-store";
|
|
||||||
import migrations from "../migrations/hotbar-store";
|
|
||||||
import { toJS } from "./utils";
|
|
||||||
import { CatalogEntity } from "./catalog";
|
|
||||||
import logger from "../main/logger";
|
|
||||||
import { broadcastMessage } from "./ipc";
|
|
||||||
import {
|
|
||||||
defaultHotbarCells,
|
|
||||||
getEmptyHotbar,
|
|
||||||
Hotbar,
|
|
||||||
CreateHotbarData,
|
|
||||||
CreateHotbarOptions,
|
|
||||||
} from "./hotbar-types";
|
|
||||||
import { hotbarTooManyItemsChannel } from "./ipc/hotbar";
|
|
||||||
import type { GeneralEntity } from "./catalog-entities";
|
|
||||||
|
|
||||||
export interface HotbarStoreModel {
|
|
||||||
hotbars: Hotbar[];
|
|
||||||
activeHotbarId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
catalogCatalogEntity: GeneralEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|
||||||
readonly displayName = "HotbarStore";
|
|
||||||
@observable hotbars: Hotbar[] = [];
|
|
||||||
@observable private _activeHotbarId: string;
|
|
||||||
|
|
||||||
constructor(private dependencies: Dependencies) {
|
|
||||||
super({
|
|
||||||
configName: "lens-hotbar-store",
|
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
|
||||||
syncOptions: {
|
|
||||||
equals: comparer.structural,
|
|
||||||
},
|
|
||||||
migrations,
|
|
||||||
});
|
|
||||||
makeObservable(this);
|
|
||||||
this.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed get activeHotbarId() {
|
|
||||||
return this._activeHotbarId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private hotbarIndexById(id: string) {
|
|
||||||
return this.hotbars.findIndex((hotbar) => hotbar.id === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private hotbarIndex(hotbar: Hotbar) {
|
|
||||||
return this.hotbars.indexOf(hotbar);
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed get activeHotbarIndex() {
|
|
||||||
return this.hotbarIndexById(this.activeHotbarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
protected fromStore(data: Partial<HotbarStoreModel> = {}) {
|
|
||||||
if (!data.hotbars || !data.hotbars.length) {
|
|
||||||
const hotbar = getEmptyHotbar("Default");
|
|
||||||
const {
|
|
||||||
metadata: { uid, name, source },
|
|
||||||
} = this.dependencies.catalogCatalogEntity;
|
|
||||||
const initialItem = { entity: { uid, name, source }};
|
|
||||||
|
|
||||||
hotbar.items[0] = initialItem;
|
|
||||||
|
|
||||||
this.hotbars = [hotbar];
|
|
||||||
} else {
|
|
||||||
this.hotbars = data.hotbars;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hotbars.forEach(ensureExactHotbarItemLength);
|
|
||||||
|
|
||||||
if (data.activeHotbarId) {
|
|
||||||
this.setActiveHotbar(data.activeHotbarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.activeHotbarId) {
|
|
||||||
this.setActiveHotbar(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): HotbarStoreModel {
|
|
||||||
const model: HotbarStoreModel = {
|
|
||||||
hotbars: this.hotbars,
|
|
||||||
activeHotbarId: this.activeHotbarId,
|
|
||||||
};
|
|
||||||
|
|
||||||
return toJS(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
getActive() {
|
|
||||||
return this.getById(this.activeHotbarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getByName(name: string) {
|
|
||||||
return this.hotbars.find((hotbar) => hotbar.name === name);
|
|
||||||
}
|
|
||||||
|
|
||||||
getById(id: string) {
|
|
||||||
return this.hotbars.find((hotbar) => hotbar.id === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
add = action(
|
|
||||||
(
|
|
||||||
data: CreateHotbarData,
|
|
||||||
{ setActive = false }: CreateHotbarOptions = {},
|
|
||||||
) => {
|
|
||||||
const hotbar = getEmptyHotbar(data.name, data.id);
|
|
||||||
|
|
||||||
this.hotbars.push(hotbar);
|
|
||||||
|
|
||||||
if (setActive) {
|
|
||||||
this._activeHotbarId = hotbar.id;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
setHotbarName = action((id: string, name: string) => {
|
|
||||||
const index = this.hotbars.findIndex((hotbar) => hotbar.id === id);
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hotbars = this.hotbars.filter((h) => h !== hotbar);
|
|
||||||
|
|
||||||
if (this.activeHotbarId === hotbar.id) {
|
|
||||||
this.setActiveHotbar(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
@action
|
|
||||||
addToHotbar(item: CatalogEntity, cellIndex?: number) {
|
|
||||||
const hotbar = this.getActive();
|
|
||||||
const uid = item.getId();
|
|
||||||
const name = item.getName();
|
|
||||||
|
|
||||||
if (typeof uid !== "string") {
|
|
||||||
throw new TypeError("CatalogEntity's ID must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof name !== "string") {
|
|
||||||
throw new TypeError("CatalogEntity's NAME must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isAddedToActive(item)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entity = {
|
|
||||||
uid,
|
|
||||||
name,
|
|
||||||
source: item.metadata.source,
|
|
||||||
};
|
|
||||||
const newItem = { entity };
|
|
||||||
|
|
||||||
if (cellIndex === undefined) {
|
|
||||||
// Add item to empty cell
|
|
||||||
const emptyCellIndex = hotbar.items.indexOf(null);
|
|
||||||
|
|
||||||
if (emptyCellIndex != -1) {
|
|
||||||
hotbar.items[emptyCellIndex] = newItem;
|
|
||||||
} else {
|
|
||||||
broadcastMessage(hotbarTooManyItemsChannel);
|
|
||||||
}
|
|
||||||
} else if (0 <= cellIndex && cellIndex < hotbar.items.length) {
|
|
||||||
hotbar.items[cellIndex] = newItem;
|
|
||||||
} else {
|
|
||||||
logger.error(
|
|
||||||
`[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range`,
|
|
||||||
{ entityId: uid, hotbarId: hotbar.id, cellIndex },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
removeFromHotbar(uid: string): void {
|
|
||||||
const hotbar = this.getActive();
|
|
||||||
const index = hotbar.items.findIndex((item) => item?.entity.uid === uid);
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hotbar.items[index] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all hotbar items that reference the `uid`.
|
|
||||||
* @param uid The `EntityId` that each hotbar item refers to
|
|
||||||
* @returns A function that will (in an action) undo the removing of the hotbar items. This function will not complete if the hotbar has changed.
|
|
||||||
*/
|
|
||||||
@action
|
|
||||||
removeAllHotbarItems(uid: string) {
|
|
||||||
for (const hotbar of this.hotbars) {
|
|
||||||
const index = hotbar.items.findIndex((i) => i?.entity.uid === uid);
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
hotbar.items[index] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findClosestEmptyIndex(from: number, direction = 1) {
|
|
||||||
let index = from;
|
|
||||||
|
|
||||||
while (this.getActive().items[index] != null) {
|
|
||||||
index += direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
restackItems(from: number, to: number): void {
|
|
||||||
const { items } = this.getActive();
|
|
||||||
const source = items[from];
|
|
||||||
const moveDown = from < to;
|
|
||||||
|
|
||||||
if (
|
|
||||||
from < 0 ||
|
|
||||||
to < 0 ||
|
|
||||||
from >= items.length ||
|
|
||||||
to >= items.length ||
|
|
||||||
isNaN(from) ||
|
|
||||||
isNaN(to)
|
|
||||||
) {
|
|
||||||
throw new Error("Invalid 'from' or 'to' arguments");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (from == to) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
items.splice(from, 1, null);
|
|
||||||
|
|
||||||
if (items[to] == null) {
|
|
||||||
items.splice(to, 1, source);
|
|
||||||
} else {
|
|
||||||
// Move cells up or down to closes empty cell
|
|
||||||
items.splice(this.findClosestEmptyIndex(to, moveDown ? -1 : 1), 1);
|
|
||||||
items.splice(to, 0, source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switchToPrevious() {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
let index = hotbarStore.activeHotbarIndex - 1;
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
index = hotbarStore.hotbars.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
hotbarStore.setActiveHotbar(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
switchToNext() {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
let index = hotbarStore.activeHotbarIndex + 1;
|
|
||||||
|
|
||||||
if (index >= hotbarStore.hotbars.length) {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hotbarStore.setActiveHotbar(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if entity already pinned to the active hotbar
|
|
||||||
*/
|
|
||||||
isAddedToActive(entity: CatalogEntity | null | undefined): boolean {
|
|
||||||
if (!entity) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
this.getActive().items.findIndex(
|
|
||||||
(item) => item?.entity.uid === entity.getId(),
|
|
||||||
) >= 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function ensures that there are always exactly `defaultHotbarCells`
|
|
||||||
* worth of items in the hotbar.
|
|
||||||
* @param hotbar The hotbar to modify
|
|
||||||
*/
|
|
||||||
function ensureExactHotbarItemLength(hotbar: Hotbar) {
|
|
||||||
// if there are not enough items
|
|
||||||
while (hotbar.items.length < defaultHotbarCells) {
|
|
||||||
hotbar.items.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if for some reason the hotbar was overfilled before, remove as many entries
|
|
||||||
// as needed, but prefer empty slots and items at the end first.
|
|
||||||
while (hotbar.items.length > defaultHotbarCells) {
|
|
||||||
const lastNull = hotbar.items.lastIndexOf(null);
|
|
||||||
|
|
||||||
if (lastNull >= 0) {
|
|
||||||
hotbar.items.splice(lastNull, 1);
|
|
||||||
} else {
|
|
||||||
hotbar.items.length = defaultHotbarCells;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hotbarStoreInjectable = getInjectable({
|
const hotbarStoreInjectable = getInjectable({
|
||||||
id: "hotbar-store",
|
id: "hotbar-store",
|
||||||
@ -373,6 +16,8 @@ const hotbarStoreInjectable = getInjectable({
|
|||||||
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
|
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
causesSideEffects: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default hotbarStoreInjectable;
|
export default hotbarStoreInjectable;
|
||||||
|
|||||||
@ -3,13 +3,358 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import hotbarStoreInjectable from "./hotbar-store.injectable";
|
import { action, comparer, observable, makeObservable, computed } from "mobx";
|
||||||
|
import { BaseStore } from "./base-store";
|
||||||
|
import migrations from "../migrations/hotbar-store";
|
||||||
|
import { toJS } from "./utils";
|
||||||
|
import { CatalogEntity } from "./catalog";
|
||||||
|
import logger from "../main/logger";
|
||||||
|
import { broadcastMessage } from "./ipc";
|
||||||
import {
|
import {
|
||||||
asLegacyGlobalSingletonForExtensionApi,
|
defaultHotbarCells,
|
||||||
} from "../extensions/as-legacy-globals-for-extension-api/as-legacy-global-singleton-object-for-extension-api";
|
getEmptyHotbar,
|
||||||
|
Hotbar,
|
||||||
|
CreateHotbarData,
|
||||||
|
CreateHotbarOptions,
|
||||||
|
} from "./hotbar-types";
|
||||||
|
import { hotbarTooManyItemsChannel } from "./ipc/hotbar";
|
||||||
|
import type { GeneralEntity } from "./catalog-entities";
|
||||||
|
|
||||||
|
export interface HotbarStoreModel {
|
||||||
|
hotbars: Hotbar[];
|
||||||
|
activeHotbarId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
catalogCatalogEntity: GeneralEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||||
|
readonly displayName = "HotbarStore";
|
||||||
|
@observable hotbars: Hotbar[] = [];
|
||||||
|
@observable private _activeHotbarId: string;
|
||||||
|
|
||||||
|
constructor(private dependencies: Dependencies) {
|
||||||
|
super({
|
||||||
|
configName: "lens-hotbar-store",
|
||||||
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
|
syncOptions: {
|
||||||
|
equals: comparer.structural,
|
||||||
|
},
|
||||||
|
migrations,
|
||||||
|
});
|
||||||
|
makeObservable(this);
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get activeHotbarId() {
|
||||||
|
return this._activeHotbarId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated use di.inject(hotbarStoreInjectable) instead.
|
* If `hotbar` points to a known hotbar, make it active. Otherwise, ignore
|
||||||
|
* @param hotbar The hotbar instance, or the index, or its ID
|
||||||
*/
|
*/
|
||||||
export const HotbarStore = asLegacyGlobalSingletonForExtensionApi(hotbarStoreInjectable);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private hotbarIndexById(id: string) {
|
||||||
|
return this.hotbars.findIndex((hotbar) => hotbar.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private hotbarIndex(hotbar: Hotbar) {
|
||||||
|
return this.hotbars.indexOf(hotbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get activeHotbarIndex() {
|
||||||
|
return this.hotbarIndexById(this.activeHotbarId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
protected fromStore(data: Partial<HotbarStoreModel> = {}) {
|
||||||
|
if (!data.hotbars || !data.hotbars.length) {
|
||||||
|
const hotbar = getEmptyHotbar("Default");
|
||||||
|
const {
|
||||||
|
metadata: { uid, name, source },
|
||||||
|
} = this.dependencies.catalogCatalogEntity;
|
||||||
|
const initialItem = { entity: { uid, name, source }};
|
||||||
|
|
||||||
|
hotbar.items[0] = initialItem;
|
||||||
|
|
||||||
|
this.hotbars = [hotbar];
|
||||||
|
} else {
|
||||||
|
this.hotbars = data.hotbars;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hotbars.forEach(ensureExactHotbarItemLength);
|
||||||
|
|
||||||
|
if (data.activeHotbarId) {
|
||||||
|
this.setActiveHotbar(data.activeHotbarId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.activeHotbarId) {
|
||||||
|
this.setActiveHotbar(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): HotbarStoreModel {
|
||||||
|
const model: HotbarStoreModel = {
|
||||||
|
hotbars: this.hotbars,
|
||||||
|
activeHotbarId: this.activeHotbarId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return toJS(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
getActive() {
|
||||||
|
return this.getById(this.activeHotbarId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getByName(name: string) {
|
||||||
|
return this.hotbars.find((hotbar) => hotbar.name === name);
|
||||||
|
}
|
||||||
|
|
||||||
|
getById(id: string) {
|
||||||
|
return this.hotbars.find((hotbar) => hotbar.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
add = action(
|
||||||
|
(
|
||||||
|
data: CreateHotbarData,
|
||||||
|
{ setActive = false }: CreateHotbarOptions = {},
|
||||||
|
) => {
|
||||||
|
const hotbar = getEmptyHotbar(data.name, data.id);
|
||||||
|
|
||||||
|
this.hotbars.push(hotbar);
|
||||||
|
|
||||||
|
if (setActive) {
|
||||||
|
this._activeHotbarId = hotbar.id;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
setHotbarName = action((id: string, name: string) => {
|
||||||
|
const index = this.hotbars.findIndex((hotbar) => hotbar.id === id);
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hotbars = this.hotbars.filter((h) => h !== hotbar);
|
||||||
|
|
||||||
|
if (this.activeHotbarId === hotbar.id) {
|
||||||
|
this.setActiveHotbar(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
@action
|
||||||
|
addToHotbar(item: CatalogEntity, cellIndex?: number) {
|
||||||
|
const hotbar = this.getActive();
|
||||||
|
const uid = item.getId();
|
||||||
|
const name = item.getName();
|
||||||
|
|
||||||
|
if (typeof uid !== "string") {
|
||||||
|
throw new TypeError("CatalogEntity's ID must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof name !== "string") {
|
||||||
|
throw new TypeError("CatalogEntity's NAME must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isAddedToActive(item)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entity = {
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
source: item.metadata.source,
|
||||||
|
};
|
||||||
|
const newItem = { entity };
|
||||||
|
|
||||||
|
if (cellIndex === undefined) {
|
||||||
|
// Add item to empty cell
|
||||||
|
const emptyCellIndex = hotbar.items.indexOf(null);
|
||||||
|
|
||||||
|
if (emptyCellIndex != -1) {
|
||||||
|
hotbar.items[emptyCellIndex] = newItem;
|
||||||
|
} else {
|
||||||
|
broadcastMessage(hotbarTooManyItemsChannel);
|
||||||
|
}
|
||||||
|
} else if (0 <= cellIndex && cellIndex < hotbar.items.length) {
|
||||||
|
hotbar.items[cellIndex] = newItem;
|
||||||
|
} else {
|
||||||
|
logger.error(
|
||||||
|
`[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range`,
|
||||||
|
{ entityId: uid, hotbarId: hotbar.id, cellIndex },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
removeFromHotbar(uid: string): void {
|
||||||
|
const hotbar = this.getActive();
|
||||||
|
const index = hotbar.items.findIndex((item) => item?.entity.uid === uid);
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hotbar.items[index] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all hotbar items that reference the `uid`.
|
||||||
|
* @param uid The `EntityId` that each hotbar item refers to
|
||||||
|
* @returns A function that will (in an action) undo the removing of the hotbar items. This function will not complete if the hotbar has changed.
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
removeAllHotbarItems(uid: string) {
|
||||||
|
for (const hotbar of this.hotbars) {
|
||||||
|
const index = hotbar.items.findIndex((i) => i?.entity.uid === uid);
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
hotbar.items[index] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findClosestEmptyIndex(from: number, direction = 1) {
|
||||||
|
let index = from;
|
||||||
|
|
||||||
|
while (this.getActive().items[index] != null) {
|
||||||
|
index += direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
restackItems(from: number, to: number): void {
|
||||||
|
const { items } = this.getActive();
|
||||||
|
const source = items[from];
|
||||||
|
const moveDown = from < to;
|
||||||
|
|
||||||
|
if (
|
||||||
|
from < 0 ||
|
||||||
|
to < 0 ||
|
||||||
|
from >= items.length ||
|
||||||
|
to >= items.length ||
|
||||||
|
isNaN(from) ||
|
||||||
|
isNaN(to)
|
||||||
|
) {
|
||||||
|
throw new Error("Invalid 'from' or 'to' arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from == to) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
items.splice(from, 1, null);
|
||||||
|
|
||||||
|
if (items[to] == null) {
|
||||||
|
items.splice(to, 1, source);
|
||||||
|
} else {
|
||||||
|
// Move cells up or down to closes empty cell
|
||||||
|
items.splice(this.findClosestEmptyIndex(to, moveDown ? -1 : 1), 1);
|
||||||
|
items.splice(to, 0, source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToPrevious() {
|
||||||
|
let index = this.activeHotbarIndex - 1;
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
index = this.hotbars.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setActiveHotbar(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToNext() {
|
||||||
|
let index = this.activeHotbarIndex + 1;
|
||||||
|
|
||||||
|
if (index >= this.hotbars.length) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setActiveHotbar(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if entity already pinned to the active hotbar
|
||||||
|
*/
|
||||||
|
isAddedToActive(entity: CatalogEntity | null | undefined): boolean {
|
||||||
|
if (!entity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.getActive().items.findIndex(
|
||||||
|
(item) => item?.entity.uid === entity.getId(),
|
||||||
|
) >= 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function ensures that there are always exactly `defaultHotbarCells`
|
||||||
|
* worth of items in the hotbar.
|
||||||
|
* @param hotbar The hotbar to modify
|
||||||
|
*/
|
||||||
|
function ensureExactHotbarItemLength(hotbar: Hotbar) {
|
||||||
|
// if there are not enough items
|
||||||
|
while (hotbar.items.length < defaultHotbarCells) {
|
||||||
|
hotbar.items.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if for some reason the hotbar was overfilled before, remove as many entries
|
||||||
|
// as needed, but prefer empty slots and items at the end first.
|
||||||
|
while (hotbar.items.length > defaultHotbarCells) {
|
||||||
|
const lastNull = hotbar.items.lastIndexOf(null);
|
||||||
|
|
||||||
|
if (lastNull >= 0) {
|
||||||
|
hotbar.items.splice(lastNull, 1);
|
||||||
|
} else {
|
||||||
|
hotbar.items.length = defaultHotbarCells;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import getAbsolutePathInjectable from "../common/path/get-absolute-path.injectab
|
|||||||
import { getAbsolutePathFake } from "../common/test-utils/get-absolute-path-fake";
|
import { getAbsolutePathFake } from "../common/test-utils/get-absolute-path-fake";
|
||||||
import joinPathsInjectable from "../common/path/join-paths.injectable";
|
import joinPathsInjectable from "../common/path/join-paths.injectable";
|
||||||
import { joinPathsFake } from "../common/test-utils/join-paths-fake";
|
import { joinPathsFake } from "../common/test-utils/join-paths-fake";
|
||||||
|
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
|
||||||
|
|
||||||
export const getDiForUnitTesting = (
|
export const getDiForUnitTesting = (
|
||||||
{ doGeneralOverrides } = { doGeneralOverrides: false },
|
{ doGeneralOverrides } = { doGeneralOverrides: false },
|
||||||
@ -65,6 +66,8 @@ export const getDiForUnitTesting = (
|
|||||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||||
di.override(extensionsStoreInjectable, () => ({ isEnabled: ({ id, isBundled }) => false }) as ExtensionsStore);
|
di.override(extensionsStoreInjectable, () => ({ isEnabled: ({ id, isBundled }) => false }) as ExtensionsStore);
|
||||||
|
|
||||||
|
di.override(hotbarStoreInjectable, () => ({}));
|
||||||
|
|
||||||
di.override(fileSystemProvisionerStoreInjectable, () => ({}) as FileSystemProvisionerStore);
|
di.override(fileSystemProvisionerStoreInjectable, () => ({}) as FileSystemProvisionerStore);
|
||||||
|
|
||||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||||
|
|||||||
@ -41,6 +41,8 @@ import navigateToAddClusterInjectable from "../common/front-end-routing/routes/
|
|||||||
import addSyncEntriesInjectable from "./initializers/add-sync-entries.injectable";
|
import addSyncEntriesInjectable from "./initializers/add-sync-entries.injectable";
|
||||||
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
|
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
|
||||||
import { bindEvents } from "./navigation/events";
|
import { bindEvents } from "./navigation/events";
|
||||||
|
import deleteClusterDialogModelInjectable
|
||||||
|
from "./components/delete-cluster-dialog/delete-cluster-dialog-model/delete-cluster-dialog-model.injectable";
|
||||||
|
|
||||||
if (process.isMainFrame) {
|
if (process.isMainFrame) {
|
||||||
SentryInit();
|
SentryInit();
|
||||||
@ -99,6 +101,7 @@ export async function bootstrap(di: DiContainer) {
|
|||||||
logger.info(`${logPrefix} initializing Catalog`);
|
logger.info(`${logPrefix} initializing Catalog`);
|
||||||
initializers.initCatalog({
|
initializers.initCatalog({
|
||||||
openCommandDialog: di.inject(commandOverlayInjectable).open,
|
openCommandDialog: di.inject(commandOverlayInjectable).open,
|
||||||
|
deleteClusterDialogModel: di.inject(deleteClusterDialogModelInjectable),
|
||||||
});
|
});
|
||||||
|
|
||||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
const extensionLoader = di.inject(extensionLoaderInjectable);
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { CatalogCategory } from "../../../api/catalog-entity";
|
|||||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||||
import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns";
|
import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns";
|
||||||
import getCategoryColumnsInjectable, { CategoryColumns, GetCategoryColumnsParams } from "../get-category-columns.injectable";
|
import getCategoryColumnsInjectable, { CategoryColumns, GetCategoryColumnsParams } from "../get-category-columns.injectable";
|
||||||
|
import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable";
|
||||||
|
|
||||||
class TestCategory extends CatalogCategory {
|
class TestCategory extends CatalogCategory {
|
||||||
apiVersion = "catalog.k8slens.dev/v1alpha1";
|
apiVersion = "catalog.k8slens.dev/v1alpha1";
|
||||||
@ -39,6 +40,8 @@ describe("Custom Category Columns", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
di = getDiForUnitTesting();
|
di = getDiForUnitTesting();
|
||||||
|
|
||||||
|
di.override(hotbarStoreInjectable, () => ({}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("without extensions", () => {
|
describe("without extensions", () => {
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.s
|
|||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
import { MenuItem, MenuActions } from "../menu";
|
import { MenuItem, MenuActions } from "../menu";
|
||||||
import type { CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
|
import type { CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
|
||||||
import { HotbarStore } from "../../../common/hotbar-store";
|
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
import { catalogCategoryRegistry, CatalogEntity } from "../../../common/catalog";
|
import { catalogCategoryRegistry, CatalogEntity } from "../../../common/catalog";
|
||||||
import { CatalogAddButton } from "./catalog-add-button";
|
import { CatalogAddButton } from "./catalog-add-button";
|
||||||
@ -39,6 +38,8 @@ import catalogRouteParametersInjectable from "./catalog-route-parameters.injecta
|
|||||||
import { browseCatalogTab } from "./catalog-browse-tab";
|
import { browseCatalogTab } from "./catalog-browse-tab";
|
||||||
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
|
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
|
||||||
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
|
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
|
||||||
|
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
|
||||||
|
import type { HotbarStore } from "../../../common/hotbar-store";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
catalogPreviousActiveTabStorage: { set: (value: string ) => void };
|
catalogPreviousActiveTabStorage: { set: (value: string ) => void };
|
||||||
@ -53,6 +54,7 @@ interface Dependencies {
|
|||||||
};
|
};
|
||||||
|
|
||||||
navigateToCatalog: NavigateToCatalog;
|
navigateToCatalog: NavigateToCatalog;
|
||||||
|
hotbarStore: HotbarStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -125,11 +127,11 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addToHotbar(entity: CatalogEntity): void {
|
addToHotbar(entity: CatalogEntity): void {
|
||||||
HotbarStore.getInstance().addToHotbar(entity);
|
this.props.hotbarStore.addToHotbar(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFromHotbar(entity: CatalogEntity): void {
|
removeFromHotbar(entity: CatalogEntity): void {
|
||||||
HotbarStore.getInstance().removeFromHotbar(entity.getId());
|
this.props.hotbarStore.removeFromHotbar(entity.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
onDetails = (entity: CatalogEntity) => {
|
onDetails = (entity: CatalogEntity) => {
|
||||||
@ -216,7 +218,7 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderName(entity: CatalogEntity) {
|
renderName(entity: CatalogEntity) {
|
||||||
const isItemInHotbar = HotbarStore.getInstance().isAddedToActive(entity);
|
const isItemInHotbar = this.props.hotbarStore.isAddedToActive(entity);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -341,5 +343,6 @@ export const Catalog = withInjectables<Dependencies>( NonInjectedCatalog, {
|
|||||||
routeParameters: di.inject(catalogRouteParametersInjectable),
|
routeParameters: di.inject(catalogRouteParametersInjectable),
|
||||||
navigateToCatalog: di.inject(navigateToCatalogInjectable),
|
navigateToCatalog: di.inject(navigateToCatalogInjectable),
|
||||||
emitEvent: di.inject(appEventBusInjectable).emit,
|
emitEvent: di.inject(appEventBusInjectable).emit,
|
||||||
|
hotbarStore: di.inject(hotbarStoreInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,10 +9,12 @@ import type { CatalogCategory, CatalogEntity } from "../../../common/catalog";
|
|||||||
import type { ItemListLayoutProps } from "../item-object-list";
|
import type { ItemListLayoutProps } from "../item-object-list";
|
||||||
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
|
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
|
||||||
import categoryColumnsInjectable from "./custom-category-columns.injectable";
|
import categoryColumnsInjectable from "./custom-category-columns.injectable";
|
||||||
import { defaultCategoryColumns, browseAllColumns, nameCategoryColumn } from "./internal-category-columns";
|
import { defaultCategoryColumns, browseAllColumns } from "./internal-category-columns";
|
||||||
|
import nameCategoryColumnInjectable from "./name-category-column.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
extensionColumns: IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>>;
|
extensionColumns: IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>>;
|
||||||
|
nameCategoryColumn: RegisteredAdditionalCategoryColumn;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetCategoryColumnsParams {
|
export interface GetCategoryColumnsParams {
|
||||||
@ -21,7 +23,7 @@ export interface GetCategoryColumnsParams {
|
|||||||
|
|
||||||
export type CategoryColumns = Required<Pick<ItemListLayoutProps<CatalogEntity>, "sortingCallbacks" | "searchFilters" | "renderTableContents" | "renderTableHeader">>;
|
export type CategoryColumns = Required<Pick<ItemListLayoutProps<CatalogEntity>, "sortingCallbacks" | "searchFilters" | "renderTableContents" | "renderTableHeader">>;
|
||||||
|
|
||||||
function getSpecificCategoryColumns(activeCategory: CatalogCategory, extensionColumns: IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>>): RegisteredAdditionalCategoryColumn[] {
|
function getSpecificCategoryColumns(activeCategory: CatalogCategory, extensionColumns: IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>>, nameCategoryColumn: RegisteredAdditionalCategoryColumn): RegisteredAdditionalCategoryColumn[] {
|
||||||
const fromExtensions = (
|
const fromExtensions = (
|
||||||
extensionColumns
|
extensionColumns
|
||||||
.get()
|
.get()
|
||||||
@ -41,7 +43,7 @@ function getSpecificCategoryColumns(activeCategory: CatalogCategory, extensionCo
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBrowseAllColumns(): RegisteredAdditionalCategoryColumn[] {
|
function getBrowseAllColumns(nameCategoryColumn: RegisteredAdditionalCategoryColumn): RegisteredAdditionalCategoryColumn[] {
|
||||||
return [
|
return [
|
||||||
...browseAllColumns,
|
...browseAllColumns,
|
||||||
nameCategoryColumn,
|
nameCategoryColumn,
|
||||||
@ -49,11 +51,11 @@ function getBrowseAllColumns(): RegisteredAdditionalCategoryColumn[] {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCategoryColumns = ({ extensionColumns }: Dependencies) => ({ activeCategory }: GetCategoryColumnsParams): CategoryColumns => {
|
const getCategoryColumns = ({ extensionColumns, nameCategoryColumn }: Dependencies) => ({ activeCategory }: GetCategoryColumnsParams): CategoryColumns => {
|
||||||
const allRegistrations = orderBy(
|
const allRegistrations = orderBy(
|
||||||
activeCategory
|
activeCategory
|
||||||
? getSpecificCategoryColumns(activeCategory, extensionColumns)
|
? getSpecificCategoryColumns(activeCategory, extensionColumns, nameCategoryColumn)
|
||||||
: getBrowseAllColumns(),
|
: getBrowseAllColumns(nameCategoryColumn),
|
||||||
"priority",
|
"priority",
|
||||||
"asc",
|
"asc",
|
||||||
);
|
);
|
||||||
@ -89,6 +91,7 @@ const getCategoryColumnsInjectable = getInjectable({
|
|||||||
|
|
||||||
instantiate: (di) => getCategoryColumns({
|
instantiate: (di) => getCategoryColumns({
|
||||||
extensionColumns: di.inject(categoryColumnsInjectable),
|
extensionColumns: di.inject(categoryColumnsInjectable),
|
||||||
|
nameCategoryColumn: di.inject(nameCategoryColumnInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,26 +4,54 @@
|
|||||||
*/
|
*/
|
||||||
import React, { ReactNode, useState } from "react";
|
import React, { ReactNode, useState } from "react";
|
||||||
|
|
||||||
import { HotbarStore } from "../../../common/hotbar-store";
|
|
||||||
import { MenuItem } from "../menu";
|
import { MenuItem } from "../menu";
|
||||||
|
|
||||||
import type { CatalogEntity } from "../../api/catalog-entity";
|
import type { CatalogEntity } from "../../api/catalog-entity";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
|
||||||
|
import type { HotbarStore } from "../../../common/hotbar-store";
|
||||||
|
|
||||||
export function HotbarToggleMenuItem(props: { entity: CatalogEntity; addContent: ReactNode; removeContent: ReactNode }) {
|
interface Dependencies {
|
||||||
const store = HotbarStore.getInstance();
|
hotbarStore: HotbarStore;
|
||||||
const [itemInHotbar, setItemInHotbar] = useState(store.isAddedToActive(props.entity));
|
}
|
||||||
|
|
||||||
|
interface HotbarToggleMenuItemProps {
|
||||||
|
entity: CatalogEntity;
|
||||||
|
addContent: ReactNode;
|
||||||
|
removeContent: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function NonInjectedHotbarToggleMenuItem({
|
||||||
|
addContent,
|
||||||
|
entity,
|
||||||
|
hotbarStore,
|
||||||
|
removeContent,
|
||||||
|
}: Dependencies & HotbarToggleMenuItemProps) {
|
||||||
|
const [itemInHotbar, setItemInHotbar] = useState(hotbarStore.isAddedToActive(entity));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem onClick={() => {
|
<MenuItem onClick={() => {
|
||||||
if (itemInHotbar) {
|
if (itemInHotbar) {
|
||||||
store.removeFromHotbar(props.entity.getId());
|
hotbarStore.removeFromHotbar(entity.getId());
|
||||||
setItemInHotbar(false);
|
setItemInHotbar(false);
|
||||||
} else {
|
} else {
|
||||||
store.addToHotbar(props.entity);
|
hotbarStore.addToHotbar(entity);
|
||||||
setItemInHotbar(true);
|
setItemInHotbar(true);
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
{itemInHotbar ? props.removeContent : props.addContent }
|
{itemInHotbar ? removeContent : addContent }
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const HotbarToggleMenuItem = withInjectables<Dependencies, HotbarToggleMenuItemProps>(
|
||||||
|
NonInjectedHotbarToggleMenuItem,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
hotbarStore: di.inject(hotbarStoreInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
|||||||
@ -6,49 +6,10 @@
|
|||||||
import styles from "./catalog.module.scss";
|
import styles from "./catalog.module.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { HotbarStore } from "../../../common/hotbar-store";
|
|
||||||
import type { CatalogEntity } from "../../api/catalog-entity";
|
|
||||||
import { Avatar } from "../avatar";
|
|
||||||
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
|
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
|
||||||
import { Icon } from "../icon";
|
|
||||||
import { prevDefault } from "../../utils";
|
|
||||||
import { getLabelBadges } from "./helpers";
|
import { getLabelBadges } from "./helpers";
|
||||||
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
|
|
||||||
function renderEntityName(entity: CatalogEntity) {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
const isItemInHotbar = hotbarStore.isAddedToActive(entity);
|
|
||||||
const onClick = prevDefault(
|
|
||||||
isItemInHotbar
|
|
||||||
? () => hotbarStore.removeFromHotbar(entity.getId())
|
|
||||||
: () => hotbarStore.addToHotbar(entity),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Avatar
|
|
||||||
title={entity.getName()}
|
|
||||||
colorHash={`${entity.getName()}-${entity.getSource()}`}
|
|
||||||
src={entity.spec.icon?.src}
|
|
||||||
background={entity.spec.icon?.background}
|
|
||||||
className={styles.catalogAvatar}
|
|
||||||
size={24}
|
|
||||||
>
|
|
||||||
{entity.spec.icon?.material && <Icon material={entity.spec.icon?.material} small/>}
|
|
||||||
</Avatar>
|
|
||||||
<span>{entity.getName()}</span>
|
|
||||||
<Icon
|
|
||||||
small
|
|
||||||
className={styles.pinIcon}
|
|
||||||
material={!isItemInHotbar && "push_pin"}
|
|
||||||
svg={isItemInHotbar ? "push_off" : "push_pin"}
|
|
||||||
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const browseAllColumns: RegisteredAdditionalCategoryColumn[] = [
|
export const browseAllColumns: RegisteredAdditionalCategoryColumn[] = [
|
||||||
{
|
{
|
||||||
id: "kind",
|
id: "kind",
|
||||||
@ -63,20 +24,6 @@ export const browseAllColumns: RegisteredAdditionalCategoryColumn[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const nameCategoryColumn: RegisteredAdditionalCategoryColumn = {
|
|
||||||
id: "name",
|
|
||||||
priority: 0,
|
|
||||||
renderCell: renderEntityName,
|
|
||||||
titleProps: {
|
|
||||||
title: "Name",
|
|
||||||
className: styles.entityName,
|
|
||||||
id: "name",
|
|
||||||
sortBy: "name",
|
|
||||||
},
|
|
||||||
searchFilter: entity => entity.getName(),
|
|
||||||
sortCallback: entity => `name=${entity.getName()}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const defaultCategoryColumns: RegisteredAdditionalCategoryColumn[] = [
|
export const defaultCategoryColumns: RegisteredAdditionalCategoryColumn[] = [
|
||||||
{
|
{
|
||||||
id: "source",
|
id: "source",
|
||||||
|
|||||||
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import styles from "./catalog.module.scss";
|
||||||
|
import type { CatalogEntity } from "../../../common/catalog";
|
||||||
|
import { prevDefault } from "../../utils";
|
||||||
|
import { Avatar } from "../avatar";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
import React from "react";
|
||||||
|
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
|
||||||
|
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
|
||||||
|
import type { HotbarStore } from "../../../common/hotbar-store";
|
||||||
|
|
||||||
|
const renderEntityName = (hotbarStore: HotbarStore) => (entity: CatalogEntity) => {
|
||||||
|
const isItemInHotbar = hotbarStore.isAddedToActive(entity);
|
||||||
|
const onClick = prevDefault(
|
||||||
|
isItemInHotbar
|
||||||
|
? () => hotbarStore.removeFromHotbar(entity.getId())
|
||||||
|
: () => hotbarStore.addToHotbar(entity),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Avatar
|
||||||
|
title={entity.getName()}
|
||||||
|
colorHash={`${entity.getName()}-${entity.getSource()}`}
|
||||||
|
src={entity.spec.icon?.src}
|
||||||
|
background={entity.spec.icon?.background}
|
||||||
|
className={styles.catalogAvatar}
|
||||||
|
size={24}
|
||||||
|
>
|
||||||
|
{entity.spec.icon?.material && <Icon material={entity.spec.icon?.material} small/>}
|
||||||
|
</Avatar>
|
||||||
|
<span>{entity.getName()}</span>
|
||||||
|
<Icon
|
||||||
|
small
|
||||||
|
className={styles.pinIcon}
|
||||||
|
material={!isItemInHotbar && "push_pin"}
|
||||||
|
svg={isItemInHotbar ? "push_off" : "push_pin"}
|
||||||
|
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const nameCategoryColumnInjectable = getInjectable({
|
||||||
|
id: "name-category-column",
|
||||||
|
instantiate: (di): RegisteredAdditionalCategoryColumn => ({
|
||||||
|
id: "name",
|
||||||
|
priority: 0,
|
||||||
|
renderCell: renderEntityName(di.inject(hotbarStoreInjectable)),
|
||||||
|
titleProps: {
|
||||||
|
title: "Name",
|
||||||
|
className: styles.entityName,
|
||||||
|
id: "name",
|
||||||
|
sortBy: "name",
|
||||||
|
},
|
||||||
|
searchFilter: (entity) => entity.getName(),
|
||||||
|
sortCallback: (entity) => `name=${entity.getName()}`,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default nameCategoryColumnInjectable;
|
||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import "@testing-library/jest-dom/extend-expect";
|
import "@testing-library/jest-dom/extend-expect";
|
||||||
import { KubeConfig } from "@kubernetes/client-node";
|
import { KubeConfig } from "@kubernetes/client-node";
|
||||||
import { fireEvent, render } from "@testing-library/react";
|
import { fireEvent } from "@testing-library/react";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as selectEvent from "react-select-event";
|
import * as selectEvent from "react-select-event";
|
||||||
@ -16,6 +16,11 @@ import type { ClusterModel } from "../../../../common/cluster-types";
|
|||||||
import { getDisForUnitTesting } from "../../../../test-utils/get-dis-for-unit-testing";
|
import { getDisForUnitTesting } from "../../../../test-utils/get-dis-for-unit-testing";
|
||||||
import { createClusterInjectionToken } from "../../../../common/cluster/create-cluster-injection-token";
|
import { createClusterInjectionToken } from "../../../../common/cluster/create-cluster-injection-token";
|
||||||
import createContextHandlerInjectable from "../../../../main/context-handler/create-context-handler.injectable";
|
import createContextHandlerInjectable from "../../../../main/context-handler/create-context-handler.injectable";
|
||||||
|
import deleteClusterDialogModelInjectable from "../delete-cluster-dialog-model/delete-cluster-dialog-model.injectable";
|
||||||
|
import type { DeleteClusterDialogModel } from "../delete-cluster-dialog-model/delete-cluster-dialog-model";
|
||||||
|
import type { DiRender } from "../../test-utils/renderFor";
|
||||||
|
import { renderFor } from "../../test-utils/renderFor";
|
||||||
|
import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable";
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
app: {
|
app: {
|
||||||
@ -88,16 +93,23 @@ let config: KubeConfig;
|
|||||||
|
|
||||||
describe("<DeleteClusterDialog />", () => {
|
describe("<DeleteClusterDialog />", () => {
|
||||||
let createCluster: (model: ClusterModel) => Cluster;
|
let createCluster: (model: ClusterModel) => Cluster;
|
||||||
|
let deleteClusterDialogModel: DeleteClusterDialogModel;
|
||||||
|
let render: DiRender;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const { mainDi, runSetups } = getDisForUnitTesting({ doGeneralOverrides: true });
|
const { mainDi, rendererDi, runSetups } = getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
render = renderFor(rendererDi);
|
||||||
|
|
||||||
mainDi.override(createContextHandlerInjectable, () => () => undefined);
|
mainDi.override(createContextHandlerInjectable, () => () => undefined);
|
||||||
|
|
||||||
mockFs();
|
mockFs();
|
||||||
|
|
||||||
|
rendererDi.override(hotbarStoreInjectable, () => ({}));
|
||||||
|
|
||||||
await runSetups();
|
await runSetups();
|
||||||
|
|
||||||
|
deleteClusterDialogModel = rendererDi.inject(deleteClusterDialogModelInjectable);
|
||||||
createCluster = mainDi.inject(createClusterInjectionToken);
|
createCluster = mainDi.inject(createClusterInjectionToken);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -137,7 +149,7 @@ describe("<DeleteClusterDialog />", () => {
|
|||||||
kubeConfigPath: "./temp-kube-config",
|
kubeConfigPath: "./temp-kube-config",
|
||||||
});
|
});
|
||||||
|
|
||||||
DeleteClusterDialog.open({ cluster, config });
|
deleteClusterDialogModel.open({ cluster, config });
|
||||||
const { getByText } = render(<DeleteClusterDialog />);
|
const { getByText } = render(<DeleteClusterDialog />);
|
||||||
|
|
||||||
const message = "The contents of kubeconfig file will be changed!";
|
const message = "The contents of kubeconfig file will be changed!";
|
||||||
@ -155,7 +167,7 @@ describe("<DeleteClusterDialog />", () => {
|
|||||||
kubeConfigPath: "./temp-kube-config",
|
kubeConfigPath: "./temp-kube-config",
|
||||||
});
|
});
|
||||||
|
|
||||||
DeleteClusterDialog.open({ cluster, config });
|
deleteClusterDialogModel.open({ cluster, config });
|
||||||
|
|
||||||
const { getByTestId } = render(<DeleteClusterDialog />);
|
const { getByTestId } = render(<DeleteClusterDialog />);
|
||||||
|
|
||||||
@ -172,7 +184,7 @@ describe("<DeleteClusterDialog />", () => {
|
|||||||
kubeConfigPath: "./temp-kube-config",
|
kubeConfigPath: "./temp-kube-config",
|
||||||
});
|
});
|
||||||
|
|
||||||
DeleteClusterDialog.open({ cluster, config });
|
deleteClusterDialogModel.open({ cluster, config });
|
||||||
|
|
||||||
const { getByText } = render(<DeleteClusterDialog />);
|
const { getByText } = render(<DeleteClusterDialog />);
|
||||||
|
|
||||||
@ -193,7 +205,7 @@ describe("<DeleteClusterDialog />", () => {
|
|||||||
kubeConfigPath: "./temp-kube-config",
|
kubeConfigPath: "./temp-kube-config",
|
||||||
});
|
});
|
||||||
|
|
||||||
DeleteClusterDialog.open({ cluster, config });
|
deleteClusterDialogModel.open({ cluster, config });
|
||||||
|
|
||||||
const { getByText, getByTestId } = render(<DeleteClusterDialog />);
|
const { getByText, getByTestId } = render(<DeleteClusterDialog />);
|
||||||
const link = getByTestId("context-switch");
|
const link = getByTestId("context-switch");
|
||||||
@ -220,7 +232,7 @@ describe("<DeleteClusterDialog />", () => {
|
|||||||
|
|
||||||
const spy = jest.spyOn(cluster, "isInLocalKubeconfig").mockImplementation(() => true);
|
const spy = jest.spyOn(cluster, "isInLocalKubeconfig").mockImplementation(() => true);
|
||||||
|
|
||||||
DeleteClusterDialog.open({ cluster, config });
|
deleteClusterDialogModel.open({ cluster, config });
|
||||||
|
|
||||||
const { getByTestId } = render(<DeleteClusterDialog />);
|
const { getByTestId } = render(<DeleteClusterDialog />);
|
||||||
|
|
||||||
@ -256,7 +268,7 @@ describe("<DeleteClusterDialog />", () => {
|
|||||||
kubeConfigPath: "./temp-kube-config",
|
kubeConfigPath: "./temp-kube-config",
|
||||||
});
|
});
|
||||||
|
|
||||||
DeleteClusterDialog.open({ cluster, config });
|
deleteClusterDialogModel.open({ cluster, config });
|
||||||
|
|
||||||
const { getByTestId } = render(<DeleteClusterDialog />);
|
const { getByTestId } = render(<DeleteClusterDialog />);
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { DeleteClusterDialogModel } from "./delete-cluster-dialog-model";
|
||||||
|
|
||||||
|
const deleteClusterDialogModelInjectable = getInjectable({
|
||||||
|
id: "delete-cluster-dialog-model",
|
||||||
|
instantiate: () => new DeleteClusterDialogModel(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default deleteClusterDialogModelInjectable;
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { KubeConfig } from "@kubernetes/client-node";
|
||||||
|
import { observable, makeObservable, action } from "mobx";
|
||||||
|
import type { Cluster } from "../../../../common/cluster/cluster";
|
||||||
|
|
||||||
|
export class DeleteClusterDialogModel {
|
||||||
|
isOpen = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
makeObservable(this, {
|
||||||
|
isOpen: observable,
|
||||||
|
open: action,
|
||||||
|
close: action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster: Cluster;
|
||||||
|
config: KubeConfig;
|
||||||
|
|
||||||
|
open = ({ cluster, config }: { cluster: Cluster; config: KubeConfig }) => {
|
||||||
|
this.isOpen = true;
|
||||||
|
|
||||||
|
this.cluster = cluster;
|
||||||
|
this.config = config;
|
||||||
|
};
|
||||||
|
|
||||||
|
close = () => {
|
||||||
|
this.isOpen = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -9,52 +9,35 @@ import { observer } from "mobx-react";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import type { KubeConfig } from "@kubernetes/client-node";
|
|
||||||
import type { Cluster } from "../../../common/cluster/cluster";
|
|
||||||
import { saveKubeconfig } from "./save-config";
|
import { saveKubeconfig } from "./save-config";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { HotbarStore } from "../../../common/hotbar-store";
|
|
||||||
import { boundMethod } from "autobind-decorator";
|
import { boundMethod } from "autobind-decorator";
|
||||||
import { Dialog } from "../dialog";
|
import { Dialog } from "../dialog";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Select } from "../select";
|
import { Select } from "../select";
|
||||||
import { Checkbox } from "../checkbox";
|
import { Checkbox } from "../checkbox";
|
||||||
import { requestClearClusterAsDeleting, requestDeleteCluster, requestSetClusterAsDeleting } from "../../ipc";
|
import { requestClearClusterAsDeleting, requestDeleteCluster, requestSetClusterAsDeleting } from "../../ipc";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
|
||||||
|
import type { DeleteClusterDialogModel } from "./delete-cluster-dialog-model/delete-cluster-dialog-model";
|
||||||
|
import deleteClusterDialogModelInjectable from "./delete-cluster-dialog-model/delete-cluster-dialog-model.injectable";
|
||||||
|
import type { HotbarStore } from "../../../common/hotbar-store";
|
||||||
|
|
||||||
interface DialogState {
|
interface Dependencies {
|
||||||
isOpen: boolean;
|
hotbarStore: HotbarStore;
|
||||||
config?: KubeConfig;
|
model: DeleteClusterDialogModel;
|
||||||
cluster?: Cluster;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialogState: DialogState = observable({
|
|
||||||
isOpen: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface DeleteClusterDialogProps {}
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class DeleteClusterDialog extends React.Component<DeleteClusterDialogProps> {
|
class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
||||||
@observable showContextSwitch = false;
|
@observable showContextSwitch = false;
|
||||||
@observable newCurrentContext = "";
|
@observable newCurrentContext = "";
|
||||||
|
|
||||||
constructor(props: DeleteClusterDialogProps) {
|
constructor(props: Dependencies) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static open({ config, cluster }: Partial<DialogState>) {
|
|
||||||
dialogState.isOpen = true;
|
|
||||||
dialogState.config = config;
|
|
||||||
dialogState.cluster = cluster;
|
|
||||||
}
|
|
||||||
|
|
||||||
static close() {
|
|
||||||
dialogState.isOpen = false;
|
|
||||||
dialogState.cluster = null;
|
|
||||||
dialogState.config = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
onOpen() {
|
onOpen() {
|
||||||
this.newCurrentContext = "";
|
this.newCurrentContext = "";
|
||||||
@ -66,25 +49,25 @@ export class DeleteClusterDialog extends React.Component<DeleteClusterDialogProp
|
|||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
onClose() {
|
onClose() {
|
||||||
DeleteClusterDialog.close();
|
this.props.model.close();
|
||||||
this.showContextSwitch = false;
|
this.showContextSwitch = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeContext() {
|
removeContext() {
|
||||||
dialogState.config.contexts = dialogState.config.contexts.filter(item =>
|
this.props.model.config.contexts = this.props.model.config.contexts.filter(item =>
|
||||||
item.name !== dialogState.cluster.contextName,
|
item.name !== this.props.model.cluster.contextName,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
changeCurrentContext() {
|
changeCurrentContext() {
|
||||||
if (this.newCurrentContext && this.showContextSwitch) {
|
if (this.newCurrentContext && this.showContextSwitch) {
|
||||||
dialogState.config.currentContext = this.newCurrentContext;
|
this.props.model.config.currentContext = this.newCurrentContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
async onDelete() {
|
async onDelete() {
|
||||||
const { cluster, config } = dialogState;
|
const { cluster, config } = this.props.model;
|
||||||
|
|
||||||
await requestSetClusterAsDeleting(cluster.id);
|
await requestSetClusterAsDeleting(cluster.id);
|
||||||
this.removeContext();
|
this.removeContext();
|
||||||
@ -92,7 +75,7 @@ export class DeleteClusterDialog extends React.Component<DeleteClusterDialogProp
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await saveKubeconfig(config, cluster.kubeConfigPath);
|
await saveKubeconfig(config, cluster.kubeConfigPath);
|
||||||
HotbarStore.getInstance().removeAllHotbarItems(cluster.id);
|
this.props.hotbarStore.removeAllHotbarItems(cluster.id);
|
||||||
await requestDeleteCluster(cluster.id);
|
await requestDeleteCluster(cluster.id);
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
Notifications.error(`Cannot remove cluster, failed to process config file. ${error}`);
|
Notifications.error(`Cannot remove cluster, failed to process config file. ${error}`);
|
||||||
@ -104,7 +87,7 @@ export class DeleteClusterDialog extends React.Component<DeleteClusterDialogProp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@computed get disableDelete() {
|
@computed get disableDelete() {
|
||||||
const { cluster, config } = dialogState;
|
const { cluster, config } = this.props.model;
|
||||||
const noContextsAvailable = config.contexts.filter(context => context.name !== cluster.contextName).length == 0;
|
const noContextsAvailable = config.contexts.filter(context => context.name !== cluster.contextName).length == 0;
|
||||||
const newContextNotSelected = this.newCurrentContext === "";
|
const newContextNotSelected = this.newCurrentContext === "";
|
||||||
|
|
||||||
@ -116,12 +99,12 @@ export class DeleteClusterDialog extends React.Component<DeleteClusterDialogProp
|
|||||||
}
|
}
|
||||||
|
|
||||||
isCurrentContext() {
|
isCurrentContext() {
|
||||||
return dialogState.config.currentContext == dialogState.cluster.contextName;
|
return this.props.model.config.currentContext == this.props.model.cluster.contextName;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCurrentContextSwitch() {
|
renderCurrentContextSwitch() {
|
||||||
if (!this.showContextSwitch) return null;
|
if (!this.showContextSwitch) return null;
|
||||||
const { cluster, config } = dialogState;
|
const { cluster, config } = this.props.model;
|
||||||
const contexts = config.contexts.filter(context => context.name !== cluster.contextName);
|
const contexts = config.contexts.filter(context => context.name !== cluster.contextName);
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
@ -146,7 +129,7 @@ export class DeleteClusterDialog extends React.Component<DeleteClusterDialogProp
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderDeleteMessage() {
|
renderDeleteMessage() {
|
||||||
const { cluster } = dialogState;
|
const { cluster } = this.props.model;
|
||||||
|
|
||||||
if (cluster.isInLocalKubeconfig()) {
|
if (cluster.isInLocalKubeconfig()) {
|
||||||
return (
|
return (
|
||||||
@ -164,7 +147,7 @@ export class DeleteClusterDialog extends React.Component<DeleteClusterDialogProp
|
|||||||
}
|
}
|
||||||
|
|
||||||
getWarningMessage() {
|
getWarningMessage() {
|
||||||
const { cluster, config } = dialogState;
|
const { cluster, config } = this.props.model;
|
||||||
const contexts = config.contexts.filter(context => context.name !== cluster.contextName);
|
const contexts = config.contexts.filter(context => context.name !== cluster.contextName);
|
||||||
|
|
||||||
if (!contexts.length) {
|
if (!contexts.length) {
|
||||||
@ -206,7 +189,7 @@ export class DeleteClusterDialog extends React.Component<DeleteClusterDialogProp
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { cluster, config, isOpen } = dialogState;
|
const { cluster, config, isOpen } = this.props.model;
|
||||||
|
|
||||||
if (!cluster || !config) return null;
|
if (!cluster || !config) return null;
|
||||||
|
|
||||||
@ -258,3 +241,14 @@ export class DeleteClusterDialog extends React.Component<DeleteClusterDialogProp
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DeleteClusterDialog = withInjectables<Dependencies>(
|
||||||
|
NonInjectedDeleteClusterDialog,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di) => ({
|
||||||
|
hotbarStore: di.inject(hotbarStoreInjectable),
|
||||||
|
model: di.inject(deleteClusterDialogModelInjectable),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import React from "react";
|
|||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||||
import { DiRender, renderFor } from "../../test-utils/renderFor";
|
import { DiRender, renderFor } from "../../test-utils/renderFor";
|
||||||
import hotbarStoreInjectable, { HotbarStore } from "../../../../common/hotbar-store.injectable";
|
import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable";
|
||||||
import { ThemeStore } from "../../../theme.store";
|
import { ThemeStore } from "../../../theme.store";
|
||||||
import { ConfirmDialog } from "../../confirm-dialog";
|
import { ConfirmDialog } from "../../confirm-dialog";
|
||||||
import { UserStore } from "../../../../common/user-store";
|
import { UserStore } from "../../../../common/user-store";
|
||||||
@ -18,6 +18,7 @@ import mockFs from "mock-fs";
|
|||||||
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import getConfigurationFileModelInjectable from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
import appVersionInjectable from "../../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||||
|
import type { HotbarStore } from "../../../../common/hotbar-store";
|
||||||
|
|
||||||
const mockHotbars: { [id: string]: any } = {
|
const mockHotbars: { [id: string]: any } = {
|
||||||
"1": {
|
"1": {
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { observer } from "mobx-react";
|
|||||||
import { HotbarEntityIcon } from "./hotbar-entity-icon";
|
import { HotbarEntityIcon } from "./hotbar-entity-icon";
|
||||||
import { cssNames, IClassName } from "../../utils";
|
import { cssNames, IClassName } from "../../utils";
|
||||||
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||||
import { HotbarStore } from "../../../common/hotbar-store";
|
|
||||||
import type { CatalogEntity } from "../../api/catalog-entity";
|
import type { CatalogEntity } from "../../api/catalog-entity";
|
||||||
import { DragDropContext, Draggable, Droppable, type DropResult } from "react-beautiful-dnd";
|
import { DragDropContext, Draggable, Droppable, type DropResult } from "react-beautiful-dnd";
|
||||||
import { HotbarSelector } from "./hotbar-selector";
|
import { HotbarSelector } from "./hotbar-selector";
|
||||||
@ -18,26 +17,33 @@ import { HotbarCell } from "./hotbar-cell";
|
|||||||
import { HotbarIcon } from "./hotbar-icon";
|
import { HotbarIcon } from "./hotbar-icon";
|
||||||
import { defaultHotbarCells, HotbarItem } from "../../../common/hotbar-types";
|
import { defaultHotbarCells, HotbarItem } from "../../../common/hotbar-types";
|
||||||
import { action, makeObservable, observable } from "mobx";
|
import { action, makeObservable, observable } from "mobx";
|
||||||
|
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import type { HotbarStore } from "../../../common/hotbar-store";
|
||||||
|
|
||||||
export interface HotbarMenuProps {
|
export interface HotbarMenuProps {
|
||||||
className?: IClassName;
|
className?: IClassName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
hotbarStore: HotbarStore;
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class HotbarMenu extends React.Component<HotbarMenuProps> {
|
class NonInjectedHotbarMenu extends React.Component<Dependencies & HotbarMenuProps> {
|
||||||
@observable draggingOver = false;
|
@observable draggingOver = false;
|
||||||
|
|
||||||
constructor(props: HotbarMenuProps) {
|
constructor(props: Dependencies & HotbarMenuProps) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get hotbar() {
|
get hotbar() {
|
||||||
return HotbarStore.getInstance().getActive();
|
return this.props.hotbarStore.getActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
getEntity(item: HotbarItem) {
|
getEntity(item: HotbarItem) {
|
||||||
const hotbar = HotbarStore.getInstance().getActive();
|
const hotbar = this.props.hotbarStore.getActive();
|
||||||
|
|
||||||
if (!hotbar) {
|
if (!hotbar) {
|
||||||
return null;
|
return null;
|
||||||
@ -64,17 +70,17 @@ export class HotbarMenu extends React.Component<HotbarMenuProps> {
|
|||||||
const from = parseInt(source.droppableId);
|
const from = parseInt(source.droppableId);
|
||||||
const to = parseInt(destination.droppableId);
|
const to = parseInt(destination.droppableId);
|
||||||
|
|
||||||
HotbarStore.getInstance().restackItems(from, to);
|
this.props.hotbarStore.restackItems(from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItem(uid: string) {
|
removeItem(uid: string) {
|
||||||
const hotbar = HotbarStore.getInstance();
|
const hotbar = this.props.hotbarStore;
|
||||||
|
|
||||||
hotbar.removeFromHotbar(uid);
|
hotbar.removeFromHotbar(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
addItem(entity: CatalogEntity, index = -1) {
|
addItem(entity: CatalogEntity, index = -1) {
|
||||||
const hotbar = HotbarStore.getInstance();
|
const hotbar = this.props.hotbarStore;
|
||||||
|
|
||||||
hotbar.addToHotbar(entity, index);
|
hotbar.addToHotbar(entity, index);
|
||||||
}
|
}
|
||||||
@ -160,8 +166,7 @@ export class HotbarMenu extends React.Component<HotbarMenuProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className } = this.props;
|
const { className, hotbarStore } = this.props;
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
const hotbar = hotbarStore.getActive();
|
const hotbar = hotbarStore.getActive();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -176,3 +181,14 @@ export class HotbarMenu extends React.Component<HotbarMenuProps> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const HotbarMenu = withInjectables<Dependencies, HotbarMenuProps>(
|
||||||
|
NonInjectedHotbarMenu,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
hotbarStore: di.inject(hotbarStoreInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -6,13 +6,14 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Select } from "../select";
|
import { Select } from "../select";
|
||||||
import hotbarStoreInjectable, { HotbarStore } from "../../../common/hotbar-store.injectable";
|
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
|
||||||
import type { CommandOverlay } from "../command-palette";
|
import type { CommandOverlay } from "../command-palette";
|
||||||
import { HotbarAddCommand } from "./hotbar-add-command";
|
import { HotbarAddCommand } from "./hotbar-add-command";
|
||||||
import { HotbarRemoveCommand } from "./hotbar-remove-command";
|
import { HotbarRemoveCommand } from "./hotbar-remove-command";
|
||||||
import { HotbarRenameCommand } from "./hotbar-rename-command";
|
import { HotbarRenameCommand } from "./hotbar-rename-command";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
||||||
|
import type { HotbarStore } from "../../../common/hotbar-store";
|
||||||
|
|
||||||
const addActionId = "__add__";
|
const addActionId = "__add__";
|
||||||
const removeActionId = "__remove__";
|
const removeActionId = "__remove__";
|
||||||
|
|||||||
@ -5,17 +5,14 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import "@testing-library/jest-dom/extend-expect";
|
import "@testing-library/jest-dom/extend-expect";
|
||||||
import { render, fireEvent } from "@testing-library/react";
|
import { fireEvent } from "@testing-library/react";
|
||||||
import { SidebarCluster } from "../sidebar-cluster";
|
import { SidebarCluster } from "../sidebar-cluster";
|
||||||
import { KubernetesCluster } from "../../../../common/catalog-entities";
|
import { KubernetesCluster } from "../../../../common/catalog-entities";
|
||||||
|
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||||
jest.mock("../../../../common/hotbar-store", () => ({
|
import type { DiRender } from "../../test-utils/renderFor";
|
||||||
HotbarStore: {
|
import { renderFor } from "../../test-utils/renderFor";
|
||||||
getInstance: () => ({
|
import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable";
|
||||||
isAddedToActive: jest.fn(),
|
import type { HotbarStore } from "../../../../common/hotbar-store";
|
||||||
}),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const clusterEntity = new KubernetesCluster({
|
const clusterEntity = new KubernetesCluster({
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -34,6 +31,18 @@ const clusterEntity = new KubernetesCluster({
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("<SidebarCluster/>", () => {
|
describe("<SidebarCluster/>", () => {
|
||||||
|
let render: DiRender;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
di.override(hotbarStoreInjectable, () => ({
|
||||||
|
isAddedToActive: () => {},
|
||||||
|
}) as unknown as HotbarStore);
|
||||||
|
|
||||||
|
render = renderFor(di);
|
||||||
|
});
|
||||||
|
|
||||||
it("renders w/o errors", () => {
|
it("renders w/o errors", () => {
|
||||||
const { container } = render(<SidebarCluster clusterEntity={clusterEntity}/>);
|
const { container } = render(<SidebarCluster clusterEntity={clusterEntity}/>);
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
import styles from "./sidebar-cluster.module.scss";
|
import styles from "./sidebar-cluster.module.scss";
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { HotbarStore } from "../../../common/hotbar-store";
|
|
||||||
import { broadcastMessage } from "../../../common/ipc";
|
import { broadcastMessage } from "../../../common/ipc";
|
||||||
import type { CatalogEntity, CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
|
import type { CatalogEntity, CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
|
||||||
import { IpcRendererNavigationEvents } from "../../navigation/events";
|
import { IpcRendererNavigationEvents } from "../../navigation/events";
|
||||||
@ -16,6 +15,9 @@ import { navigate } from "../../navigation";
|
|||||||
import { Menu, MenuItem } from "../menu";
|
import { Menu, MenuItem } from "../menu";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
import { Tooltip } from "../tooltip";
|
import { Tooltip } from "../tooltip";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
|
||||||
|
import type { HotbarStore } from "../../../common/hotbar-store";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
|
||||||
const contextMenu: CatalogEntityContextMenuContext = observable({
|
const contextMenu: CatalogEntityContextMenuContext = observable({
|
||||||
@ -60,7 +62,15 @@ function renderLoadingSidebarCluster() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SidebarCluster = observer(({ clusterEntity }: { clusterEntity: CatalogEntity }) => {
|
interface Dependencies {
|
||||||
|
hotbarStore: HotbarStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SidebarClusterProps {
|
||||||
|
clusterEntity: CatalogEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NonInjectedSidebarCluster = observer(({ clusterEntity, hotbarStore }: Dependencies & SidebarClusterProps) => {
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
|
|
||||||
if (!clusterEntity) {
|
if (!clusterEntity) {
|
||||||
@ -68,8 +78,7 @@ export const SidebarCluster = observer(({ clusterEntity }: { clusterEntity: Cata
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onMenuOpen = () => {
|
const onMenuOpen = () => {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
const isAddedToActive = hotbarStore.isAddedToActive(clusterEntity);
|
||||||
const isAddedToActive = HotbarStore.getInstance().isAddedToActive(clusterEntity);
|
|
||||||
const title = isAddedToActive
|
const title = isAddedToActive
|
||||||
? "Remove from Hotbar"
|
? "Remove from Hotbar"
|
||||||
: "Add to Hotbar";
|
: "Add to Hotbar";
|
||||||
@ -140,3 +149,14 @@ export const SidebarCluster = observer(({ clusterEntity }: { clusterEntity: Cata
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const SidebarCluster = withInjectables<Dependencies, SidebarClusterProps>(
|
||||||
|
NonInjectedSidebarCluster,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
hotbarStore: di.inject(hotbarStoreInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import getAbsolutePathInjectable from "../common/path/get-absolute-path.injectab
|
|||||||
import { getAbsolutePathFake } from "../common/test-utils/get-absolute-path-fake";
|
import { getAbsolutePathFake } from "../common/test-utils/get-absolute-path-fake";
|
||||||
import joinPathsInjectable from "../common/path/join-paths.injectable";
|
import joinPathsInjectable from "../common/path/join-paths.injectable";
|
||||||
import { joinPathsFake } from "../common/test-utils/join-paths-fake";
|
import { joinPathsFake } from "../common/test-utils/join-paths-fake";
|
||||||
|
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
|
||||||
|
|
||||||
export const getDiForUnitTesting = (
|
export const getDiForUnitTesting = (
|
||||||
{ doGeneralOverrides } = { doGeneralOverrides: false },
|
{ doGeneralOverrides } = { doGeneralOverrides: false },
|
||||||
@ -62,6 +63,8 @@ export const getDiForUnitTesting = (
|
|||||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||||
di.override(extensionsStoreInjectable, () => ({ isEnabled: ({ id, isBundled }) => false }) as ExtensionsStore);
|
di.override(extensionsStoreInjectable, () => ({ isEnabled: ({ id, isBundled }) => false }) as ExtensionsStore);
|
||||||
|
|
||||||
|
di.override(hotbarStoreInjectable, () => ({}));
|
||||||
|
|
||||||
di.override(fileSystemProvisionerStoreInjectable, () => ({}) as FileSystemProvisionerStore);
|
di.override(fileSystemProvisionerStoreInjectable, () => ({}) as FileSystemProvisionerStore);
|
||||||
|
|
||||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||||
|
|||||||
@ -10,9 +10,9 @@ import { ClusterStore } from "../../common/cluster-store/cluster-store";
|
|||||||
import { catalogCategoryRegistry } from "../api/catalog-category-registry";
|
import { catalogCategoryRegistry } from "../api/catalog-category-registry";
|
||||||
import { WeblinkAddCommand } from "../components/catalog-entities/weblink-add-command";
|
import { WeblinkAddCommand } from "../components/catalog-entities/weblink-add-command";
|
||||||
import { loadConfigFromString } from "../../common/kube-helpers";
|
import { loadConfigFromString } from "../../common/kube-helpers";
|
||||||
import { DeleteClusterDialog } from "../components/delete-cluster-dialog";
|
import type { DeleteClusterDialogModel } from "../components/delete-cluster-dialog/delete-cluster-dialog-model/delete-cluster-dialog-model";
|
||||||
|
|
||||||
async function onClusterDelete(clusterId: string) {
|
async function onClusterDelete(clusterId: string, deleteClusterDialogModel: DeleteClusterDialogModel) {
|
||||||
const cluster = ClusterStore.getInstance().getById(clusterId);
|
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|
||||||
if (!cluster) {
|
if (!cluster) {
|
||||||
@ -25,14 +25,15 @@ async function onClusterDelete(clusterId: string) {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteClusterDialog.open({ cluster, config });
|
deleteClusterDialogModel.open({ cluster, config });
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
openCommandDialog: (component: React.ReactElement) => void;
|
openCommandDialog: (component: React.ReactElement) => void;
|
||||||
|
deleteClusterDialogModel: DeleteClusterDialogModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initCatalog({ openCommandDialog }: Dependencies) {
|
export function initCatalog({ openCommandDialog, deleteClusterDialogModel }: Dependencies) {
|
||||||
catalogCategoryRegistry
|
catalogCategoryRegistry
|
||||||
.getForGroupKind("entity.k8slens.dev", "WebLink")
|
.getForGroupKind("entity.k8slens.dev", "WebLink")
|
||||||
.on("catalogAddMenu", ctx => {
|
.on("catalogAddMenu", ctx => {
|
||||||
@ -50,7 +51,7 @@ export function initCatalog({ openCommandDialog }: Dependencies) {
|
|||||||
context.menuItems.push({
|
context.menuItems.push({
|
||||||
title: "Remove",
|
title: "Remove",
|
||||||
icon: "delete",
|
icon: "delete",
|
||||||
onClick: () => onClusterDelete(entity.getId()),
|
onClick: () => onClusterDelete(entity.getId(), deleteClusterDialogModel),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user