mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fully split apart the hotbar storage
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
f1f8e198a2
commit
ae32375beb
@ -1,356 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { anyObject } from "jest-mock-extended";
|
|
||||||
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
|
||||||
import hotbarStoreInjectable from "../hotbars/store.injectable";
|
|
||||||
import type { HotbarStore } from "../hotbars/store";
|
|
||||||
import catalogEntityRegistryInjectable from "../../main/catalog/entity-registry.injectable";
|
|
||||||
import { computed } from "mobx";
|
|
||||||
import hasCategoryForEntityInjectable from "../catalog/has-category-for-entity.injectable";
|
|
||||||
import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
|
||||||
import loggerInjectable from "../logger.injectable";
|
|
||||||
import type { Logger } from "../logger";
|
|
||||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
|
||||||
import writeJsonSyncInjectable from "../fs/write-json-sync.injectable";
|
|
||||||
|
|
||||||
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
|
|
||||||
return {
|
|
||||||
getName: jest.fn(() => data.metadata?.name),
|
|
||||||
getId: jest.fn(() => data.metadata?.uid),
|
|
||||||
getSource: jest.fn(() => data.metadata?.source ?? "unknown"),
|
|
||||||
isEnabled: jest.fn(() => data.status?.enabled ?? true),
|
|
||||||
onContextMenuOpen: jest.fn(),
|
|
||||||
onSettingsOpen: jest.fn(),
|
|
||||||
metadata: {},
|
|
||||||
spec: {},
|
|
||||||
status: {},
|
|
||||||
...data,
|
|
||||||
} as CatalogEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("HotbarStore", () => {
|
|
||||||
let di: DiContainer;
|
|
||||||
let hotbarStore: HotbarStore;
|
|
||||||
let testCluster: CatalogEntity;
|
|
||||||
let minikubeCluster: CatalogEntity;
|
|
||||||
let awsCluster: CatalogEntity;
|
|
||||||
let loggerMock: jest.Mocked<Logger>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
di = getDiForUnitTesting();
|
|
||||||
|
|
||||||
testCluster = getMockCatalogEntity({
|
|
||||||
apiVersion: "v1",
|
|
||||||
kind: "Cluster",
|
|
||||||
status: {
|
|
||||||
phase: "Running",
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
uid: "some-test-id",
|
|
||||||
name: "my-test-cluster",
|
|
||||||
source: "local",
|
|
||||||
labels: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
minikubeCluster = getMockCatalogEntity({
|
|
||||||
apiVersion: "v1",
|
|
||||||
kind: "Cluster",
|
|
||||||
status: {
|
|
||||||
phase: "Running",
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
uid: "some-minikube-id",
|
|
||||||
name: "my-minikube-cluster",
|
|
||||||
source: "local",
|
|
||||||
labels: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
awsCluster = getMockCatalogEntity({
|
|
||||||
apiVersion: "v1",
|
|
||||||
kind: "Cluster",
|
|
||||||
status: {
|
|
||||||
phase: "Running",
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
uid: "some-aws-id",
|
|
||||||
name: "my-aws-cluster",
|
|
||||||
source: "local",
|
|
||||||
labels: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
di.override(hasCategoryForEntityInjectable, () => () => true);
|
|
||||||
|
|
||||||
loggerMock = {
|
|
||||||
warn: jest.fn(),
|
|
||||||
debug: jest.fn(),
|
|
||||||
error: jest.fn(),
|
|
||||||
info: jest.fn(),
|
|
||||||
silly: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
di.override(loggerInjectable, () => loggerMock);
|
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
|
||||||
|
|
||||||
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
|
||||||
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
|
|
||||||
|
|
||||||
catalogEntityRegistry.addComputedSource("some-id", computed(() => [
|
|
||||||
testCluster,
|
|
||||||
minikubeCluster,
|
|
||||||
awsCluster,
|
|
||||||
catalogCatalogEntity,
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("given no previous data in store, running all migrations", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
hotbarStore = di.inject(hotbarStoreInjectable);
|
|
||||||
|
|
||||||
hotbarStore.load();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("load", () => {
|
|
||||||
it("loads one hotbar by default", () => {
|
|
||||||
expect(hotbarStore.hotbars.length).toEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("add", () => {
|
|
||||||
it("adds a hotbar", () => {
|
|
||||||
hotbarStore.add({ name: "hottest" });
|
|
||||||
expect(hotbarStore.hotbars.length).toEqual(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hotbar items", () => {
|
|
||||||
it("initially creates 12 empty cells", () => {
|
|
||||||
expect(hotbarStore.getActive().items.length).toEqual(12);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("initially adds catalog entity as first item", () => {
|
|
||||||
expect(hotbarStore.getActive().items[0]?.entity.name).toEqual("Catalog");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("adds items", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
|
||||||
|
|
||||||
expect(items.length).toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("removes items", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.removeFromHotbar("some-test-id");
|
|
||||||
hotbarStore.removeFromHotbar("catalog-entity");
|
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
|
||||||
|
|
||||||
expect(items).toStrictEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does nothing if removing with invalid uid", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.removeFromHotbar("invalid uid");
|
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
|
||||||
|
|
||||||
expect(items.length).toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("moves item to empty cell", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[6]).toBeNull();
|
|
||||||
|
|
||||||
hotbarStore.restackItems(1, 5);
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[5]).toBeTruthy();
|
|
||||||
expect(hotbarStore.getActive().items[5]?.entity.uid).toEqual("some-test-id");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("moves items down", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
|
||||||
|
|
||||||
// aws -> catalog
|
|
||||||
hotbarStore.restackItems(3, 0);
|
|
||||||
|
|
||||||
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
|
|
||||||
|
|
||||||
expect(items.slice(0, 4)).toEqual(["some-aws-id", "catalog-entity", "some-test-id", "some-minikube-id"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("moves items up", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
|
||||||
|
|
||||||
// test -> aws
|
|
||||||
hotbarStore.restackItems(1, 3);
|
|
||||||
|
|
||||||
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
|
|
||||||
|
|
||||||
expect(items.slice(0, 4)).toEqual(["catalog-entity", "some-minikube-id", "some-aws-id", "some-test-id"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("logs an error if cellIndex is out of bounds", () => {
|
|
||||||
hotbarStore.add({ name: "hottest", id: "hottest" });
|
|
||||||
hotbarStore.setActiveHotbar("hottest");
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster, -1);
|
|
||||||
expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster, 12);
|
|
||||||
expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster, 13);
|
|
||||||
expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws an error if getId is invalid or returns not a string", () => {
|
|
||||||
expect(() => hotbarStore.addToHotbar({} 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", () => {
|
|
||||||
expect(() => hotbarStore.addToHotbar({ getId: () => "" } as any)).toThrowError(TypeError);
|
|
||||||
expect(() => hotbarStore.addToHotbar({ getId: () => "", getName: () => 4 } as any)).toThrowError(TypeError);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does nothing when item moved to same cell", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.restackItems(1, 1);
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[1]?.entity.uid).toEqual("some-test-id");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("new items takes first empty cell", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
|
||||||
hotbarStore.restackItems(0, 3);
|
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[0]?.entity.uid).toEqual("some-minikube-id");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws if invalid arguments provided", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
|
|
||||||
expect(() => hotbarStore.restackItems(-5, 0)).toThrow();
|
|
||||||
expect(() => hotbarStore.restackItems(2, -1)).toThrow();
|
|
||||||
expect(() => hotbarStore.restackItems(14, 1)).toThrow();
|
|
||||||
expect(() => hotbarStore.restackItems(11, 112)).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("checks if entity already pinned to hotbar", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
|
|
||||||
expect(hotbarStore.isAddedToActive(testCluster)).toBeTruthy();
|
|
||||||
expect(hotbarStore.isAddedToActive(awsCluster)).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const writeJsonSync = di.inject(writeJsonSyncInjectable);
|
|
||||||
|
|
||||||
writeJsonSync("/some-directory-for-user-data/lens-hotbar-store.json", {
|
|
||||||
__internal__: {
|
|
||||||
migrations: {
|
|
||||||
version: "5.0.0-beta.3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hotbars: [
|
|
||||||
{
|
|
||||||
id: "3caac17f-aec2-4723-9694-ad204465d935",
|
|
||||||
name: "myhotbar",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
entity: {
|
|
||||||
uid: "some-aws-id",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
entity: {
|
|
||||||
uid: "55b42c3c7ba3b04193416cda405269a5",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
entity: {
|
|
||||||
uid: "176fd331968660832f62283219d7eb6e",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
entity: {
|
|
||||||
uid: "61c4fb45528840ebad1badc25da41d14",
|
|
||||||
name: "user1-context",
|
|
||||||
source: "local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
entity: {
|
|
||||||
uid: "27d6f99fe9e7548a6e306760bfe19969",
|
|
||||||
name: "foo2",
|
|
||||||
source: "local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
entity: {
|
|
||||||
uid: "c0b20040646849bb4dcf773e43a0bf27",
|
|
||||||
name: "multinode-demo",
|
|
||||||
source: "local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
di.override(storeMigrationVersionInjectable, () => "5.0.0-beta.10");
|
|
||||||
|
|
||||||
hotbarStore = di.inject(hotbarStoreInjectable);
|
|
||||||
|
|
||||||
hotbarStore.load();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows to retrieve a hotbar", () => {
|
|
||||||
const hotbar = hotbarStore.findById("3caac17f-aec2-4723-9694-ad204465d935");
|
|
||||||
|
|
||||||
expect(hotbar?.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clears cells without entity", () => {
|
|
||||||
const items = hotbarStore.hotbars[0].items;
|
|
||||||
|
|
||||||
expect(items[2]).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("adds extra data to cells with according entity", () => {
|
|
||||||
const items = hotbarStore.hotbars[0].items;
|
|
||||||
|
|
||||||
expect(items[0]).toEqual({
|
|
||||||
entity: {
|
|
||||||
name: "my-aws-cluster",
|
|
||||||
source: "local",
|
|
||||||
uid: "some-aws-id",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import hotbarStoreInjectable from "./store.injectable";
|
|
||||||
import type { CreateHotbarData, CreateHotbarOptions } from "./types";
|
|
||||||
|
|
||||||
export type AddHotbar = (data: CreateHotbarData, opts?: CreateHotbarOptions) => void;
|
|
||||||
|
|
||||||
const addHotbarInjectable = getInjectable({
|
|
||||||
id: "add-hotbar",
|
|
||||||
instantiate: (di): AddHotbar => {
|
|
||||||
const store = di.inject(hotbarStoreInjectable);
|
|
||||||
|
|
||||||
return (data, opts) => store.add(data, opts);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default addHotbarInjectable;
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
|
||||||
import { HotbarStore } from "./store";
|
|
||||||
import loggerInjectable from "../logger.injectable";
|
|
||||||
import persistentStorageMigrationsInjectable from "../persistent-storage/migrations.injectable";
|
|
||||||
import { hotbarStoreMigrationInjectionToken } from "./migrations-token";
|
|
||||||
import createPersistentStorageInjectable from "../persistent-storage/create.injectable";
|
|
||||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
|
||||||
|
|
||||||
const hotbarStoreInjectable = getInjectable({
|
|
||||||
id: "hotbar-store",
|
|
||||||
|
|
||||||
instantiate: (di) => new HotbarStore({
|
|
||||||
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
|
|
||||||
logger: di.inject(loggerInjectable),
|
|
||||||
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
|
|
||||||
migrations: di.inject(persistentStorageMigrationsInjectable, hotbarStoreMigrationInjectionToken),
|
|
||||||
createPersistentStorage: di.inject(createPersistentStorageInjectable),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default hotbarStoreInjectable;
|
|
||||||
@ -1,382 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { IObservableValue } from "mobx";
|
|
||||||
import { runInAction, action, comparer, observable } from "mobx";
|
|
||||||
import type { CatalogEntity } from "../catalog";
|
|
||||||
import { broadcastMessage } from "../ipc";
|
|
||||||
import type { Hotbar, CreateHotbarData, CreateHotbarOptions } from "./types";
|
|
||||||
import { defaultHotbarCells, getEmptyHotbar } from "./types";
|
|
||||||
import { hotbarTooManyItemsChannel } from "../ipc/hotbar";
|
|
||||||
import type { GeneralEntity } from "../catalog-entities";
|
|
||||||
import type { Logger } from "../logger";
|
|
||||||
import assert from "assert";
|
|
||||||
import { getShortName } from "../catalog/helpers";
|
|
||||||
import type { Migrations } from "conf/dist/source/types";
|
|
||||||
import type { CreatePersistentStorage, PersistentStorage } from "../persistent-storage/create.injectable";
|
|
||||||
|
|
||||||
export interface HotbarStoreModel {
|
|
||||||
hotbars: Hotbar[];
|
|
||||||
activeHotbarId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
readonly catalogCatalogEntity: GeneralEntity;
|
|
||||||
readonly logger: Logger;
|
|
||||||
readonly storeMigrationVersion: string;
|
|
||||||
readonly migrations: Migrations<Record<string, unknown>>;
|
|
||||||
createPersistentStorage: CreatePersistentStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HotbarStore {
|
|
||||||
private readonly store: PersistentStorage;
|
|
||||||
|
|
||||||
readonly hotbars = observable.array<Hotbar>();
|
|
||||||
|
|
||||||
readonly activeHotbarId = observable.box() as IObservableValue<string>;
|
|
||||||
|
|
||||||
constructor(protected readonly dependencies: Dependencies) {
|
|
||||||
this.store = this.dependencies.createPersistentStorage<HotbarStoreModel>({
|
|
||||||
configName: "lens-hotbar-store",
|
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
|
||||||
syncOptions: {
|
|
||||||
equals: comparer.structural,
|
|
||||||
},
|
|
||||||
projectVersion: this.dependencies.storeMigrationVersion,
|
|
||||||
migrations: this.dependencies.migrations,
|
|
||||||
fromStore: action((data) => {
|
|
||||||
if (!data.hotbars || !data.hotbars.length) {
|
|
||||||
const hotbar = getEmptyHotbar("Default");
|
|
||||||
const {
|
|
||||||
metadata: {
|
|
||||||
uid,
|
|
||||||
name,
|
|
||||||
source,
|
|
||||||
},
|
|
||||||
} = this.dependencies.catalogCatalogEntity;
|
|
||||||
|
|
||||||
hotbar.items[0] = {
|
|
||||||
entity: {
|
|
||||||
uid,
|
|
||||||
name,
|
|
||||||
source,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
this.hotbars.replace([hotbar]);
|
|
||||||
} else {
|
|
||||||
this.hotbars.replace(data.hotbars);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const hotbar of this.hotbars) {
|
|
||||||
ensureExactHotbarItemLength(hotbar);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.activeHotbarId) {
|
|
||||||
this.activeHotbarId.set(data.activeHotbarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.activeHotbarId.get()) {
|
|
||||||
this.activeHotbarId.set(this.hotbars[0].id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeHotbarExists = this.hotbars.findIndex(hotbar => hotbar.id === this.activeHotbarId.get()) >= 0;
|
|
||||||
|
|
||||||
if (!activeHotbarExists) {
|
|
||||||
this.activeHotbarId.set(this.hotbars[0].id);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
toJSON: () => ({
|
|
||||||
hotbars: this.hotbars.toJSON(),
|
|
||||||
activeHotbarId: this.activeHotbarId.get(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
load() {
|
|
||||||
this.store.loadAndStartSyncing();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
runInAction(() => {
|
|
||||||
if (typeof hotbar === "number") {
|
|
||||||
if (hotbar >= 0 && hotbar < this.hotbars.length) {
|
|
||||||
this.activeHotbarId.set(this.hotbars[hotbar].id);
|
|
||||||
}
|
|
||||||
} else if (typeof hotbar === "string") {
|
|
||||||
if (this.findById(hotbar)) {
|
|
||||||
this.activeHotbarId.set(hotbar);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.hotbars.indexOf(hotbar) >= 0) {
|
|
||||||
this.activeHotbarId.set(hotbar.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private getActiveHotbarIndex() {
|
|
||||||
return this.hotbars.findIndex((hotbar) => hotbar.id === this.activeHotbarId.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
getActive(): Hotbar {
|
|
||||||
const hotbar = this.findById(this.activeHotbarId.get());
|
|
||||||
|
|
||||||
assert(hotbar, "There MUST always be an active hotbar");
|
|
||||||
|
|
||||||
return hotbar;
|
|
||||||
}
|
|
||||||
|
|
||||||
findByName(name: string) {
|
|
||||||
return this.hotbars.find((hotbar) => hotbar.name === name);
|
|
||||||
}
|
|
||||||
|
|
||||||
findById(id: string) {
|
|
||||||
return this.hotbars.find((hotbar) => hotbar.id === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
add(data: CreateHotbarData, { setActive = false }: CreateHotbarOptions = {}) {
|
|
||||||
runInAction(() => {
|
|
||||||
const hotbar = getEmptyHotbar(data.name, data.id);
|
|
||||||
|
|
||||||
this.hotbars.push(hotbar);
|
|
||||||
|
|
||||||
if (setActive) {
|
|
||||||
this.activeHotbarId.set(hotbar.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setHotbarName(id: string, name: string): void {
|
|
||||||
runInAction(() => {
|
|
||||||
const index = this.hotbars.findIndex((hotbar) => hotbar.id === id);
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
return this.dependencies.logger.warn(
|
|
||||||
`[HOTBAR-STORE]: cannot setHotbarName: unknown id`,
|
|
||||||
{ id },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hotbars[index].name = name;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(hotbar: Hotbar) {
|
|
||||||
runInAction(() => {
|
|
||||||
assert(this.hotbars.length >= 2, "Cannot remove the last hotbar");
|
|
||||||
|
|
||||||
this.hotbars.replace(this.hotbars.filter((h) => h.id !== hotbar.id));
|
|
||||||
|
|
||||||
if (this.activeHotbarId.get() === hotbar.id) {
|
|
||||||
this.activeHotbarId.set(this.hotbars[0].id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addToHotbar(item: CatalogEntity, cellIndex?: number) {
|
|
||||||
runInAction(() => {
|
|
||||||
|
|
||||||
const hotbar = this.getActive();
|
|
||||||
const uid = item.getId();
|
|
||||||
const name = item.getName();
|
|
||||||
const shortName = getShortName(item);
|
|
||||||
|
|
||||||
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 (typeof shortName !== "string") {
|
|
||||||
throw new TypeError("CatalogEntity's SHORT_NAME must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isAddedToActive(item)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entity = {
|
|
||||||
uid,
|
|
||||||
name,
|
|
||||||
source: item.metadata.source,
|
|
||||||
shortName,
|
|
||||||
};
|
|
||||||
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 {
|
|
||||||
this.dependencies.logger.error(
|
|
||||||
`[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range`,
|
|
||||||
{ entityId: uid, hotbarId: hotbar.id, cellIndex },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
removeFromHotbar(uid: string): void {
|
|
||||||
runInAction(() => {
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
removeAllHotbarItems(uid: string) {
|
|
||||||
runInAction(() => {
|
|
||||||
for (const hotbar of this.hotbars) {
|
|
||||||
const index = hotbar.items.findIndex((i) => i?.entity.uid === uid);
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
hotbar.items[index] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private findClosestEmptyIndex(from: number, direction = 1) {
|
|
||||||
let index = from;
|
|
||||||
const hotbar = this.getActive();
|
|
||||||
|
|
||||||
while (hotbar.items[index] != null) {
|
|
||||||
index += direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
restackItems(from: number, to: number): void {
|
|
||||||
runInAction(() => {
|
|
||||||
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() {
|
|
||||||
runInAction(() => {
|
|
||||||
let index = this.getActiveHotbarIndex() - 1;
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
index = this.hotbars.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setActiveHotbar(index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
switchToNext() {
|
|
||||||
runInAction(() => {
|
|
||||||
let index = this.getActiveHotbarIndex() + 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const indexInActiveHotbar = this.getActive().items.findIndex(item => item?.entity.uid === entity.getId());
|
|
||||||
|
|
||||||
return indexInActiveHotbar >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDisplayLabel(hotbar: Hotbar): string {
|
|
||||||
return `${this.getDisplayIndex(hotbar)}: ${hotbar.name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDisplayIndex(hotbar: Hotbar): string {
|
|
||||||
const index = this.hotbars.indexOf(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import activeHotbarIdInjectable from "./active-id.injectable";
|
||||||
|
import computeHotbarIndexInjectable from "./compute-hotbar-index.injectable";
|
||||||
|
|
||||||
|
const activeHotbarIndexInjectable = getInjectable({
|
||||||
|
id: "active-hotbar-index",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const computeHotbarIndex = di.inject(computeHotbarIndexInjectable);
|
||||||
|
const activeHotbarId = di.inject(activeHotbarIdInjectable);
|
||||||
|
|
||||||
|
return computed(() => {
|
||||||
|
const activeId = activeHotbarId.get();
|
||||||
|
|
||||||
|
return (activeId && computeHotbarIndex(activeId)) || 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default activeHotbarIndexInjectable;
|
||||||
@ -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 { observable } from "mobx";
|
||||||
|
|
||||||
|
const activeHotbarIdInjectable = getInjectable({
|
||||||
|
id: "active-hotbar-id",
|
||||||
|
instantiate: () => observable.box<string>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default activeHotbarIdInjectable;
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import activeHotbarIdInjectable from "./active-id.injectable";
|
||||||
|
import hotbarsStateInjectable from "./state.injectable";
|
||||||
|
|
||||||
|
const activeHotbarInjectable = getInjectable({
|
||||||
|
id: "active-hotbar",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const state = di.inject(hotbarsStateInjectable);
|
||||||
|
const activeHotbarId = di.inject(activeHotbarIdInjectable);
|
||||||
|
|
||||||
|
return computed(() => {
|
||||||
|
const id = activeHotbarId.get();
|
||||||
|
|
||||||
|
return (id && state.get(id)) || undefined;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default activeHotbarInjectable;
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* 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 { action } from "mobx";
|
||||||
|
import type { CreateHotbarData, CreateHotbarOptions } from "./types";
|
||||||
|
import activeHotbarIdInjectable from "./active-id.injectable";
|
||||||
|
import hotbarsStateInjectable from "./state.injectable";
|
||||||
|
import createHotbarInjectable from "./create-hotbar.injectable";
|
||||||
|
|
||||||
|
export type AddHotbar = (data: CreateHotbarData, { setActive }?: CreateHotbarOptions) => void;
|
||||||
|
|
||||||
|
const addHotbarInjectable = getInjectable({
|
||||||
|
id: "add-hotbar",
|
||||||
|
instantiate: (di): AddHotbar => {
|
||||||
|
const state = di.inject(hotbarsStateInjectable);
|
||||||
|
const activeHotbarId = di.inject(activeHotbarIdInjectable);
|
||||||
|
const createHotbar = di.inject(createHotbarInjectable);
|
||||||
|
|
||||||
|
return action((data, { setActive = false } = {}) => {
|
||||||
|
const hotbar = createHotbar(data);
|
||||||
|
|
||||||
|
state.set(hotbar.id, hotbar);
|
||||||
|
|
||||||
|
if (setActive) {
|
||||||
|
activeHotbarId.set(hotbar.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default addHotbarInjectable;
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* 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 computeHotbarIndexInjectable from "./compute-hotbar-index.injectable";
|
||||||
|
|
||||||
|
export type ComputeDisplayIndex = (hotbarId: string) => string;
|
||||||
|
|
||||||
|
const computeDisplayIndexInjectable = getInjectable({
|
||||||
|
id: "compute-display-index",
|
||||||
|
instantiate: (di): ComputeDisplayIndex => {
|
||||||
|
const computeHotbarIndex = di.inject(computeHotbarIndexInjectable);
|
||||||
|
|
||||||
|
return (hotbarId) => `${computeHotbarIndex(hotbarId) + 1}`;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default computeDisplayIndexInjectable;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { Hotbar } from "./hotbar";
|
||||||
|
import computeDisplayIndexInjectable from "./compute-display-index.injectable";
|
||||||
|
|
||||||
|
export type ComputeHotbarDisplayLabel = (hotbar: Hotbar) => string;
|
||||||
|
|
||||||
|
const computeHotbarDisplayLabelInjectable = getInjectable({
|
||||||
|
id: "compute-hotbar-display-label",
|
||||||
|
instantiate: (di): ComputeHotbarDisplayLabel => {
|
||||||
|
const computeDisplayIndex = di.inject(computeDisplayIndexInjectable);
|
||||||
|
|
||||||
|
return (hotbar) => `${computeDisplayIndex(hotbar.id)}: ${hotbar.name}`;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default computeHotbarDisplayLabelInjectable;
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* 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 hotbarsStateInjectable from "./state.injectable";
|
||||||
|
|
||||||
|
export type ComputeHotbarIndex = (hotbarId: string) => number;
|
||||||
|
|
||||||
|
const computeHotbarIndexInjectable = getInjectable({
|
||||||
|
id: "compute-hotbar-index",
|
||||||
|
instantiate: (di): ComputeHotbarIndex => {
|
||||||
|
const state = di.inject(hotbarsStateInjectable);
|
||||||
|
|
||||||
|
return (hotbarId) => {
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
for (const hotbar of state.values()) {
|
||||||
|
if (hotbar.id === hotbarId) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default computeHotbarIndexInjectable;
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { CreateHotbarData } from "./types";
|
||||||
|
import prefixedLoggerInjectable from "../../../../common/logger/prefixed-logger.injectable";
|
||||||
|
import type { HotbarDependencies } from "./hotbar";
|
||||||
|
import { Hotbar } from "./hotbar";
|
||||||
|
|
||||||
|
export type CreateHotbar = (data: CreateHotbarData) => Hotbar;
|
||||||
|
|
||||||
|
const createHotbarInjectable = getInjectable({
|
||||||
|
id: "create-hotbar",
|
||||||
|
instantiate: (di): CreateHotbar => {
|
||||||
|
const deps: HotbarDependencies = {
|
||||||
|
logger: di.inject(prefixedLoggerInjectable, "HOTBAR"),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (data) => new Hotbar(deps, data);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createHotbarInjectable;
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { iter } from "@k8slens/utilities";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { Hotbar } from "./hotbar";
|
||||||
|
import hotbarsStateInjectable from "./state.injectable";
|
||||||
|
|
||||||
|
export type FindHotbarByName = (name: string) => Hotbar | undefined;
|
||||||
|
|
||||||
|
const findHotbarByNameInjectable = getInjectable({
|
||||||
|
id: "find-hotbar-by-name",
|
||||||
|
instantiate: (di): FindHotbarByName => {
|
||||||
|
const state = di.inject(hotbarsStateInjectable);
|
||||||
|
|
||||||
|
return (name) => iter.find(state.values(), hotbar => hotbar.name.get() === name);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default findHotbarByNameInjectable;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { Hotbar } from "./hotbar";
|
||||||
|
import hotbarsStateInjectable from "./state.injectable";
|
||||||
|
|
||||||
|
export type GetHotbarById = (id: string) => Hotbar | undefined;
|
||||||
|
|
||||||
|
const getHotbarByIdInjectable = getInjectable({
|
||||||
|
id: "get-hotbar-by-id",
|
||||||
|
instantiate: (di): GetHotbarById => {
|
||||||
|
const state = di.inject(hotbarsStateInjectable);
|
||||||
|
|
||||||
|
return (id) => state.get(id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getHotbarByIdInjectable;
|
||||||
175
packages/core/src/features/hotbar/storage/common/hotbar.ts
Normal file
175
packages/core/src/features/hotbar/storage/common/hotbar.ts
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type IObservableValue, type IObservableArray, observable, runInAction, toJS } from "mobx";
|
||||||
|
import type { CatalogEntity } from "../../../../common/catalog";
|
||||||
|
import { getShortName } from "../../../../common/catalog/helpers";
|
||||||
|
import type { HotbarItem, CreateHotbarData } from "./types";
|
||||||
|
import { defaultHotbarCells } from "./types";
|
||||||
|
import { broadcastMessage } from "../../../../common/ipc";
|
||||||
|
import { hotbarTooManyItemsChannel } from "../../../../common/ipc/hotbar";
|
||||||
|
import * as uuid from "uuid";
|
||||||
|
import type { Logger } from "../../../../common/logger";
|
||||||
|
import { tuple } from "@k8slens/utilities";
|
||||||
|
|
||||||
|
export interface HotbarDependencies {
|
||||||
|
readonly logger: Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HotbarData {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly items: (HotbarItem | null)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Hotbar {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: IObservableValue<string>;
|
||||||
|
readonly items: IObservableArray<HotbarItem | null>;
|
||||||
|
|
||||||
|
constructor(private readonly dependencies: HotbarDependencies, data: CreateHotbarData) {
|
||||||
|
this.id = data.id ?? uuid.v4();
|
||||||
|
this.name = observable.box(data.name);
|
||||||
|
this.items = observable.array(data.items ?? tuple.filled(defaultHotbarCells, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
isFull() {
|
||||||
|
for (const item of this.items) {
|
||||||
|
if (!item) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasEntity(entityId: string) {
|
||||||
|
return this.items.findIndex(item => item?.entity.uid === entityId) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private findClosestEmptyIndex(from: number, direction = 1) {
|
||||||
|
let index = from;
|
||||||
|
|
||||||
|
while (this.items[index] != null) {
|
||||||
|
index += direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
restack(from: number, to: number) {
|
||||||
|
runInAction(() => {
|
||||||
|
const source = this.items[from];
|
||||||
|
const moveDown = from < to;
|
||||||
|
|
||||||
|
if (
|
||||||
|
from < 0 ||
|
||||||
|
to < 0 ||
|
||||||
|
from >= this.items.length ||
|
||||||
|
to >= this.items.length ||
|
||||||
|
isNaN(from) ||
|
||||||
|
isNaN(to)
|
||||||
|
) {
|
||||||
|
throw new Error("Invalid 'from' or 'to' arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from == to) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.items.splice(from, 1, null);
|
||||||
|
|
||||||
|
if (this.items[to] == null) {
|
||||||
|
this.items.splice(to, 1, source);
|
||||||
|
} else {
|
||||||
|
// Move cells up or down to closes empty cell
|
||||||
|
this.items.splice(this.findClosestEmptyIndex(to, moveDown ? -1 : 1), 1);
|
||||||
|
this.items.splice(to, 0, source);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleEntity(item: CatalogEntity) {
|
||||||
|
runInAction(() => {
|
||||||
|
if (this.hasEntity(item.getId())) {
|
||||||
|
this.removeEntity(item.getId());
|
||||||
|
} else {
|
||||||
|
this.addEntity(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEntity(uid: string) {
|
||||||
|
runInAction(() => {
|
||||||
|
const index = this.items.findIndex((item) => item?.entity.uid === uid);
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.items[index] = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addEntity(item: CatalogEntity, cellIndex?: number) {
|
||||||
|
const uid = item.getId();
|
||||||
|
const name = item.getName();
|
||||||
|
const shortName = getShortName(item);
|
||||||
|
|
||||||
|
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 (typeof shortName !== "string") {
|
||||||
|
throw new TypeError("CatalogEntity's SHORT_NAME must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasEntity(item.getId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entity = {
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
source: item.metadata.source,
|
||||||
|
shortName,
|
||||||
|
};
|
||||||
|
const newItem = { entity };
|
||||||
|
|
||||||
|
if (cellIndex === undefined) {
|
||||||
|
// Add item to empty cell
|
||||||
|
const emptyCellIndex = this.items.indexOf(null);
|
||||||
|
|
||||||
|
if (emptyCellIndex >= 0) {
|
||||||
|
runInAction(() => {
|
||||||
|
this.items[emptyCellIndex] = newItem;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
broadcastMessage(hotbarTooManyItemsChannel);
|
||||||
|
}
|
||||||
|
} else if (0 <= cellIndex && cellIndex < this.items.length) {
|
||||||
|
runInAction(() => {
|
||||||
|
this.items[cellIndex] = newItem;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.dependencies.logger.error(
|
||||||
|
"cannot pin entity to hotbar outside of index range",
|
||||||
|
{ entityId: uid, hotbarId: this.id, cellIndex },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): HotbarData {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
items: toJS(this.items),
|
||||||
|
name: this.name.get(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import hotbarsStateInjectable from "./state.injectable";
|
||||||
|
|
||||||
|
const hotbarsInjectable = getInjectable({
|
||||||
|
id: "hotbars",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const state = di.inject(hotbarsStateInjectable);
|
||||||
|
|
||||||
|
return computed(() => [...state.values()]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default hotbarsInjectable;
|
||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
import type { MigrationDeclaration } from "../persistent-storage/migrations.injectable";
|
import type { MigrationDeclaration } from "../../../../common/persistent-storage/migrations.injectable";
|
||||||
|
|
||||||
export const hotbarStoreMigrationInjectionToken = getInjectionToken<MigrationDeclaration>({
|
export const hotbarStoreMigrationInjectionToken = getInjectionToken<MigrationDeclaration>({
|
||||||
id: "hotbar-store-migration-token",
|
id: "hotbar-store-migration-token",
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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 { action } from "mobx";
|
||||||
|
import hotbarsInjectable from "./hotbars.injectable";
|
||||||
|
|
||||||
|
export type RemoveEntityFromAllHotbars = (entityId: string) => void;
|
||||||
|
|
||||||
|
const removeEntityFromAllHotbarsInjectable = getInjectable({
|
||||||
|
id: "remove-entity-from-all-hotbars",
|
||||||
|
instantiate: (di): RemoveEntityFromAllHotbars => {
|
||||||
|
const hotbars = di.inject(hotbarsInjectable);
|
||||||
|
|
||||||
|
return action((entityId) => {
|
||||||
|
for (const hotbar of hotbars.get()) {
|
||||||
|
hotbar.removeEntity(entityId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default removeEntityFromAllHotbarsInjectable;
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { iter } from "@k8slens/utilities";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import assert from "assert";
|
||||||
|
import { action } from "mobx";
|
||||||
|
import activeHotbarIdInjectable from "./active-id.injectable";
|
||||||
|
import type { Hotbar } from "./hotbar";
|
||||||
|
import hotbarsStateInjectable from "./state.injectable";
|
||||||
|
|
||||||
|
export type RemoveHotbar = (hotbar: Hotbar) => void;
|
||||||
|
|
||||||
|
const removeHotbarInjectable = getInjectable({
|
||||||
|
id: "remove-hotbar",
|
||||||
|
instantiate: (di): RemoveHotbar => {
|
||||||
|
const state = di.inject(hotbarsStateInjectable);
|
||||||
|
const activeHotbarId = di.inject(activeHotbarIdInjectable);
|
||||||
|
|
||||||
|
return action((hotbar) => {
|
||||||
|
assert(state.size >= 2, "Cannot remove the last hotbar");
|
||||||
|
|
||||||
|
state.delete(hotbar.id);
|
||||||
|
|
||||||
|
if (activeHotbarId.get() === hotbar.id) {
|
||||||
|
activeHotbarId.set(iter.first(state.values())?.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default removeHotbarInjectable;
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { iter } from "@k8slens/utilities";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { action } from "mobx";
|
||||||
|
import activeHotbarIdInjectable from "./active-id.injectable";
|
||||||
|
import type { Hotbar } from "./hotbar";
|
||||||
|
import hotbarsStateInjectable from "./state.injectable";
|
||||||
|
|
||||||
|
export type SetAsActiveHotbar = (desc: Hotbar | number | string) => void;
|
||||||
|
|
||||||
|
const setAsActiveHotbarInjectable = getInjectable({
|
||||||
|
id: "set-as-active-hotbar",
|
||||||
|
instantiate: (di): SetAsActiveHotbar => {
|
||||||
|
const hotbarsState = di.inject(hotbarsStateInjectable);
|
||||||
|
const activeHotbarId = di.inject(activeHotbarIdInjectable);
|
||||||
|
|
||||||
|
return action((desc) => {
|
||||||
|
if (typeof desc === "number") {
|
||||||
|
const hotbar = iter.nth(hotbarsState.values(), desc);
|
||||||
|
|
||||||
|
if (hotbar) {
|
||||||
|
activeHotbarId.set(hotbar.id);
|
||||||
|
}
|
||||||
|
} else if (typeof desc === "string") {
|
||||||
|
if (hotbarsState.has(desc)) {
|
||||||
|
activeHotbarId.set(desc);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hotbarsState.has(desc.id)) {
|
||||||
|
activeHotbarId.set(desc.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default setAsActiveHotbarInjectable;
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { observable } from "mobx";
|
||||||
|
import type { Hotbar } from "./hotbar";
|
||||||
|
|
||||||
|
const hotbarsStateInjectable = getInjectable({
|
||||||
|
id: "hotbars-state",
|
||||||
|
instantiate: () => observable.map<string, Hotbar>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default hotbarsStateInjectable;
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { iter } from "@k8slens/utilities";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { action, comparer } from "mobx";
|
||||||
|
import catalogCatalogEntityInjectable from "../../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
||||||
|
import { hotbarStoreMigrationInjectionToken } from "./migrations-token";
|
||||||
|
import { defaultHotbarCells } from "./types";
|
||||||
|
import createPersistentStorageInjectable from "../../../../common/persistent-storage/create.injectable";
|
||||||
|
import persistentStorageMigrationsInjectable from "../../../../common/persistent-storage/migrations.injectable";
|
||||||
|
import storeMigrationVersionInjectable from "../../../../common/vars/store-migration-version.injectable";
|
||||||
|
import activeHotbarIdInjectable from "./active-id.injectable";
|
||||||
|
import createHotbarInjectable from "./create-hotbar.injectable";
|
||||||
|
import type { Hotbar, HotbarData } from "./hotbar";
|
||||||
|
import hotbarsStateInjectable from "./state.injectable";
|
||||||
|
|
||||||
|
export interface HotbarStoreModel {
|
||||||
|
hotbars: HotbarData[];
|
||||||
|
activeHotbarId: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hotbarsPersistentStorageInjectable = getInjectable({
|
||||||
|
id: "hotbars-persistent-storage",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const state = di.inject(hotbarsStateInjectable);
|
||||||
|
const createPersistentStorage = di.inject(createPersistentStorageInjectable);
|
||||||
|
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
|
||||||
|
const activeHotbarId = di.inject(activeHotbarIdInjectable);
|
||||||
|
const createHotbar = di.inject(createHotbarInjectable);
|
||||||
|
|
||||||
|
return createPersistentStorage<HotbarStoreModel>({
|
||||||
|
configName: "lens-hotbar-store",
|
||||||
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
|
syncOptions: {
|
||||||
|
equals: comparer.structural,
|
||||||
|
},
|
||||||
|
projectVersion: di.inject(storeMigrationVersionInjectable),
|
||||||
|
migrations: di.inject(persistentStorageMigrationsInjectable, hotbarStoreMigrationInjectionToken),
|
||||||
|
fromStore: action((data) => {
|
||||||
|
if (!data.hotbars || !data.hotbars.length) {
|
||||||
|
const hotbar = createHotbar({
|
||||||
|
name: "Default",
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
metadata: {
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
source,
|
||||||
|
},
|
||||||
|
} = catalogCatalogEntity;
|
||||||
|
|
||||||
|
hotbar.items[0] = {
|
||||||
|
entity: {
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
source,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
state.replace([[hotbar.id, hotbar]]);
|
||||||
|
} else {
|
||||||
|
state.replace(data.hotbars.map((hotbar) => [hotbar.id, createHotbar(hotbar)]));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const hotbar of state.values()) {
|
||||||
|
ensureExactHotbarItemLength(hotbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.activeHotbarId) {
|
||||||
|
activeHotbarId.set(data.activeHotbarId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstHotbarId = iter.first(state.values())?.id;
|
||||||
|
|
||||||
|
if (!activeHotbarId.get()) {
|
||||||
|
activeHotbarId.set(firstHotbarId);
|
||||||
|
} else if (!iter.find(state.values(), hotbar => hotbar.id === activeHotbarId.get())) {
|
||||||
|
activeHotbarId.set(firstHotbarId);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
toJSON: () => ({
|
||||||
|
hotbars: iter.chain(state.values())
|
||||||
|
.map(hotbar => hotbar.toJSON())
|
||||||
|
.toArray(),
|
||||||
|
activeHotbarId: activeHotbarId.get(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default hotbarsPersistentStorageInjectable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 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 { action } from "mobx";
|
||||||
|
import activeHotbarIndexInjectable from "./active-hotbar-index.injectable";
|
||||||
|
import setAsActiveHotbarInjectable from "./set-as-active.injectable";
|
||||||
|
import hotbarsStateInjectable from "./state.injectable";
|
||||||
|
|
||||||
|
export type SwitchToNextHotbar = () => void;
|
||||||
|
|
||||||
|
const switchToNextHotbarInjectable = getInjectable({
|
||||||
|
id: "switch-to-next-hotbar",
|
||||||
|
instantiate: (di): SwitchToNextHotbar => {
|
||||||
|
const setAsActiveHotbar = di.inject(setAsActiveHotbarInjectable);
|
||||||
|
const activeHotbarIndex = di.inject(activeHotbarIndexInjectable);
|
||||||
|
const state = di.inject(hotbarsStateInjectable);
|
||||||
|
|
||||||
|
return action(() => {
|
||||||
|
const index = activeHotbarIndex.get() + 1;
|
||||||
|
|
||||||
|
if (index >= state.size) {
|
||||||
|
setAsActiveHotbar(0);
|
||||||
|
} else {
|
||||||
|
setAsActiveHotbar(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default switchToNextHotbarInjectable;
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 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 { action } from "mobx";
|
||||||
|
import activeHotbarIndexInjectable from "./active-hotbar-index.injectable";
|
||||||
|
import setAsActiveHotbarInjectable from "./set-as-active.injectable";
|
||||||
|
import hotbarsStateInjectable from "./state.injectable";
|
||||||
|
|
||||||
|
export type SwitchToPreviousHotbar = () => void;
|
||||||
|
|
||||||
|
const switchToPreviousHotbarInjectable = getInjectable({
|
||||||
|
id: "switch-to-previous-hotbar",
|
||||||
|
instantiate: (di): SwitchToPreviousHotbar => {
|
||||||
|
const setAsActiveHotbar = di.inject(setAsActiveHotbarInjectable);
|
||||||
|
const activeHotbarIndex = di.inject(activeHotbarIndexInjectable);
|
||||||
|
const state = di.inject(hotbarsStateInjectable);
|
||||||
|
|
||||||
|
return action(() => {
|
||||||
|
const index = activeHotbarIndex.get() - 1;
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
setAsActiveHotbar(state.size - 1);
|
||||||
|
} else {
|
||||||
|
setAsActiveHotbar(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default switchToPreviousHotbarInjectable;
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 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 activeHotbarInjectable from "./active.injectable";
|
||||||
|
import type { Hotbar } from "./hotbar";
|
||||||
|
|
||||||
|
export type ActiveHotbarModel = Pick<Hotbar, "hasEntity" | "addEntity" | "removeEntity" | "toggleEntity">;
|
||||||
|
|
||||||
|
const activeHotbarModelInjectable = getInjectable({
|
||||||
|
id: "active-hotbar-model",
|
||||||
|
instantiate: (di): ActiveHotbarModel => {
|
||||||
|
const activeHotbar = di.inject(activeHotbarInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasEntity: (entityId) => activeHotbar.get()?.hasEntity(entityId) ?? false,
|
||||||
|
toggleEntity: (entity) => activeHotbar.get()?.toggleEntity(entity),
|
||||||
|
addEntity: (entity) => activeHotbar.get()?.addEntity(entity),
|
||||||
|
removeEntity: (entityId) => activeHotbar.get()?.removeEntity(entityId),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default activeHotbarModelInjectable;
|
||||||
@ -3,9 +3,6 @@
|
|||||||
* 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 * as uuid from "uuid";
|
|
||||||
import type { Tuple } from "@k8slens/utilities";
|
|
||||||
import { tuple } from "@k8slens/utilities";
|
|
||||||
|
|
||||||
export interface HotbarItem {
|
export interface HotbarItem {
|
||||||
entity: {
|
entity: {
|
||||||
@ -18,12 +15,10 @@ export interface HotbarItem {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Hotbar = Required<CreateHotbarData>;
|
|
||||||
|
|
||||||
export interface CreateHotbarData {
|
export interface CreateHotbarData {
|
||||||
id?: string;
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
items?: Tuple<HotbarItem | null, typeof defaultHotbarCells>;
|
items?: (HotbarItem | null)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateHotbarOptions {
|
export interface CreateHotbarOptions {
|
||||||
@ -31,11 +26,3 @@ export interface CreateHotbarOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const defaultHotbarCells = 12; // Number is chosen to easy hit any item with keyboard
|
export const defaultHotbarCells = 12; // Number is chosen to easy hit any item with keyboard
|
||||||
|
|
||||||
export function getEmptyHotbar(name: string, id: string = uuid.v4()): Hotbar {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
items: tuple.filled(defaultHotbarCells, null),
|
|
||||||
name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Cleans up a store that had the state related data stored
|
||||||
|
import catalogCatalogEntityInjectable from "../../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token";
|
||||||
|
import createHotbarInjectable from "../common/create-hotbar.injectable";
|
||||||
|
|
||||||
|
const v500Alpha0HotbarStoreMigrationInjectable = getInjectable({
|
||||||
|
id: "v5.0.0-alpha.0-hotbar-store-migration",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
version: "5.0.0-alpha.0",
|
||||||
|
run(store) {
|
||||||
|
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
|
||||||
|
const createHotbar = di.inject(createHotbarInjectable);
|
||||||
|
const hotbar = createHotbar({ name: "default" });
|
||||||
|
|
||||||
|
hotbar.addEntity(catalogCatalogEntity);
|
||||||
|
|
||||||
|
store.set("hotbars", [hotbar.toJSON()]);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
injectionToken: hotbarStoreMigrationInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default v500Alpha0HotbarStoreMigrationInjectable;
|
||||||
|
|
||||||
@ -4,10 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Cleans up a store that had the state related data stored
|
// Cleans up a store that had the state related data stored
|
||||||
import type { Hotbar } from "../../../common/hotbars/types";
|
|
||||||
import * as uuid from "uuid";
|
import * as uuid from "uuid";
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { hotbarStoreMigrationInjectionToken } from "../../../common/hotbars/migrations-token";
|
import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token";
|
||||||
|
import type { HotbarData } from "../common/hotbar";
|
||||||
|
|
||||||
const v500Alpha2HotbarStoreMigrationInjectable = getInjectable({
|
const v500Alpha2HotbarStoreMigrationInjectable = getInjectable({
|
||||||
id: "v5.0.0-alpha.2-hotbar-store-migration",
|
id: "v5.0.0-alpha.2-hotbar-store-migration",
|
||||||
@ -15,7 +15,7 @@ const v500Alpha2HotbarStoreMigrationInjectable = getInjectable({
|
|||||||
version: "5.0.0-alpha.2",
|
version: "5.0.0-alpha.2",
|
||||||
run(store) {
|
run(store) {
|
||||||
const rawHotbars = store.get("hotbars");
|
const rawHotbars = store.get("hotbars");
|
||||||
const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars : [];
|
const hotbars: HotbarData[] = Array.isArray(rawHotbars) ? rawHotbars : [];
|
||||||
|
|
||||||
store.set("hotbars", hotbars.map(({ id, ...rest }) => ({
|
store.set("hotbars", hotbars.map(({ id, ...rest }) => ({
|
||||||
id: id || uuid.v4(),
|
id: id || uuid.v4(),
|
||||||
@ -0,0 +1,168 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as uuid from "uuid";
|
||||||
|
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import catalogCatalogEntityInjectable from "../../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
||||||
|
import { isDefined, isErrnoException } from "@k8slens/utilities";
|
||||||
|
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token";
|
||||||
|
import readJsonSyncInjectable from "../../../../common/fs/read-json-sync.injectable";
|
||||||
|
import loggerInjectable from "../../../../common/logger.injectable";
|
||||||
|
import { generateNewIdFor } from "../../../../common/utils/generate-new-id-for";
|
||||||
|
import type { ClusterModel } from "../../../../common/cluster-types";
|
||||||
|
import { defaultHotbarCells } from "../common/types";
|
||||||
|
import type { HotbarData } from "../common/hotbar";
|
||||||
|
import createHotbarInjectable from "../common/create-hotbar.injectable";
|
||||||
|
|
||||||
|
interface Pre500WorkspaceStoreModel {
|
||||||
|
workspaces: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Pre500ClusterModel extends ClusterModel {
|
||||||
|
workspace?: string;
|
||||||
|
workspaces?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Pre500ClusterStoreModel {
|
||||||
|
clusters?: Pre500ClusterModel[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const v500Beta10HotbarStoreMigrationInjectable = getInjectable({
|
||||||
|
id: "v5.0.0-beta.10-hotbar-store-migration",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
version: "5.0.0-beta.10",
|
||||||
|
run(store) {
|
||||||
|
const userDataPath = di.inject(directoryForUserDataInjectable);
|
||||||
|
const joinPaths = di.inject(joinPathsInjectable);
|
||||||
|
const readJsonSync = di.inject(readJsonSyncInjectable);
|
||||||
|
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
const createHotbar = di.inject(createHotbarInjectable);
|
||||||
|
const rawHotbars = store.get("hotbars");
|
||||||
|
const hotbars: HotbarData[] = Array.isArray(rawHotbars) ? rawHotbars.filter(h => h && typeof h === "object") : [];
|
||||||
|
|
||||||
|
// Hotbars might be empty, if some of the previous migrations weren't run
|
||||||
|
if (hotbars.length === 0) {
|
||||||
|
const hotbar = createHotbar({ name: "default" });
|
||||||
|
|
||||||
|
hotbar.addEntity(catalogCatalogEntity);
|
||||||
|
hotbars.push(hotbar.toJSON());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const workspaceStoreData: Pre500WorkspaceStoreModel = readJsonSync(joinPaths(userDataPath, "lens-workspace-store.json"));
|
||||||
|
const { clusters = [] }: Pre500ClusterStoreModel = readJsonSync(joinPaths(userDataPath, "lens-cluster-store.json"));
|
||||||
|
const workspaceHotbars = new Map<string, HotbarData>(); // mapping from WorkspaceId to HotBar
|
||||||
|
|
||||||
|
for (const { id, name } of workspaceStoreData.workspaces) {
|
||||||
|
logger.info(`Creating new hotbar for ${name}`);
|
||||||
|
workspaceHotbars.set(id, {
|
||||||
|
id: uuid.v4(),
|
||||||
|
items: [],
|
||||||
|
name: `Workspace: ${name}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// grab the default named hotbar or the first.
|
||||||
|
const defaultHotbarIndex = Math.max(0, hotbars.findIndex(hotbar => hotbar.name === "default"));
|
||||||
|
const [{ name, id, items }] = hotbars.splice(defaultHotbarIndex, 1);
|
||||||
|
|
||||||
|
workspaceHotbars.set("default", {
|
||||||
|
name,
|
||||||
|
id,
|
||||||
|
items: items.filter(isDefined),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const cluster of clusters) {
|
||||||
|
const uid = generateNewIdFor(cluster);
|
||||||
|
|
||||||
|
for (const workspaceId of cluster.workspaces ?? [cluster.workspace].filter(isDefined)) {
|
||||||
|
const workspaceHotbar = workspaceHotbars.get(workspaceId);
|
||||||
|
|
||||||
|
if (!workspaceHotbar) {
|
||||||
|
logger.info(`Cluster ${uid} has unknown workspace ID, skipping`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Adding cluster ${uid} to ${workspaceHotbar.name}`);
|
||||||
|
|
||||||
|
if (workspaceHotbar?.items.length < defaultHotbarCells) {
|
||||||
|
workspaceHotbar.items.push({
|
||||||
|
entity: {
|
||||||
|
uid: generateNewIdFor(cluster),
|
||||||
|
name: cluster.preferences?.clusterName || cluster.contextName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const hotbar of workspaceHotbars.values()) {
|
||||||
|
if (hotbar.items.length === 0) {
|
||||||
|
logger.info(`Skipping ${hotbar.name} due to it being empty`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (hotbar.items.length < defaultHotbarCells) {
|
||||||
|
hotbar.items.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
hotbars.push(hotbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finally, make sure that the catalog entity hotbar item is in place.
|
||||||
|
* Just in case something else removed it.
|
||||||
|
*
|
||||||
|
* if every hotbar has elements that all not the `catalog-entity` item
|
||||||
|
*/
|
||||||
|
if (hotbars.every(hotbar => hotbar.items.every(item => item?.entity?.uid !== "catalog-entity"))) {
|
||||||
|
// note, we will add a new whole hotbar here called "default" if that was previously removed
|
||||||
|
const defaultHotbarIndex = hotbars.findIndex(hotbar => hotbar.name === "default");
|
||||||
|
|
||||||
|
if (defaultHotbarIndex >= 0) {
|
||||||
|
const defaultHotbar = createHotbar(hotbars[defaultHotbarIndex]);
|
||||||
|
|
||||||
|
if (defaultHotbar.isFull()) {
|
||||||
|
// making a new hotbar is less destructive if the first hotbar
|
||||||
|
// called "default" is full than overriding a hotbar item
|
||||||
|
const hotbar = createHotbar({ name: "initial" });
|
||||||
|
|
||||||
|
hotbar.addEntity(catalogCatalogEntity);
|
||||||
|
hotbars.unshift(hotbar.toJSON());
|
||||||
|
} else {
|
||||||
|
defaultHotbar.addEntity(catalogCatalogEntity);
|
||||||
|
hotbars[defaultHotbarIndex] = defaultHotbar.toJSON();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const hotbar = createHotbar({ name: "default" });
|
||||||
|
|
||||||
|
hotbar.addEntity(catalogCatalogEntity);
|
||||||
|
hotbars.unshift(hotbar.toJSON());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// ignore files being missing
|
||||||
|
if (isErrnoException(error) && error.code !== "ENOENT") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store.set("hotbars", hotbars);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
injectionToken: hotbarStoreMigrationInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default v500Beta10HotbarStoreMigrationInjectable;
|
||||||
|
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* 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 catalogEntityRegistryInjectable from "../../../../main/catalog/entity-registry.injectable";
|
||||||
|
import type { HotbarData } from "../common/hotbar";
|
||||||
|
import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token";
|
||||||
|
|
||||||
|
const v500Beta5HotbarStoreMigrationInjectable = getInjectable({
|
||||||
|
id: "v500-beta5-hotbar-store-migration",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
version: "5.0.0-beta.5",
|
||||||
|
run(store) {
|
||||||
|
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||||
|
const rawHotbars = store.get("hotbars");
|
||||||
|
const hotbars: HotbarData[] = Array.isArray(rawHotbars) ? rawHotbars : [];
|
||||||
|
|
||||||
|
for (const hotbar of hotbars) {
|
||||||
|
for (let i = 0; i < hotbar.items.length; i += 1) {
|
||||||
|
const item = hotbar.items[i];
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entity = catalogEntityRegistry.findById(item.entity.uid);
|
||||||
|
|
||||||
|
if (!entity) {
|
||||||
|
// Clear disabled item
|
||||||
|
hotbar.items[i] = null;
|
||||||
|
} else {
|
||||||
|
// Save additional data
|
||||||
|
item.entity = {
|
||||||
|
...item.entity,
|
||||||
|
name: entity.metadata.name,
|
||||||
|
source: entity.metadata.source,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store.set("hotbars", hotbars);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
injectionToken: hotbarStoreMigrationInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default v500Beta5HotbarStoreMigrationInjectable;
|
||||||
|
|
||||||
@ -3,21 +3,21 @@
|
|||||||
* 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 hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
|
|
||||||
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
|
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
|
||||||
import setupSyncingOfGeneralCatalogEntitiesInjectable from "../../../../main/start-main-application/runnables/setup-syncing-of-general-catalog-entities.injectable";
|
import setupSyncingOfGeneralCatalogEntitiesInjectable from "../../../../main/start-main-application/runnables/setup-syncing-of-general-catalog-entities.injectable";
|
||||||
|
import hotbarsPersistentStorageInjectable from "../common/storage.injectable";
|
||||||
|
|
||||||
const initHotbarStoreInjectable = getInjectable({
|
const loadHotbarStorageInjectable = getInjectable({
|
||||||
id: "init-hotbar-store",
|
id: "load-hotbar-storage",
|
||||||
instantiate: (di) => ({
|
instantiate: (di) => ({
|
||||||
run: () => {
|
run: () => {
|
||||||
const hotbarStore = di.inject(hotbarStoreInjectable);
|
const storage = di.inject(hotbarsPersistentStorageInjectable);
|
||||||
|
|
||||||
hotbarStore.load();
|
storage.loadAndStartSyncing();
|
||||||
},
|
},
|
||||||
runAfter: setupSyncingOfGeneralCatalogEntitiesInjectable,
|
runAfter: setupSyncingOfGeneralCatalogEntitiesInjectable,
|
||||||
}),
|
}),
|
||||||
injectionToken: onLoadOfApplicationInjectionToken,
|
injectionToken: onLoadOfApplicationInjectionToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default initHotbarStoreInjectable;
|
export default loadHotbarStorageInjectable;
|
||||||
@ -3,21 +3,21 @@
|
|||||||
* 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 hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
|
|
||||||
import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens";
|
import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens";
|
||||||
import initClusterStoreInjectable from "../../../cluster/storage/renderer/init.injectable";
|
import initClusterStoreInjectable from "../../../cluster/storage/renderer/init.injectable";
|
||||||
|
import hotbarsPersistentStorageInjectable from "../common/storage.injectable";
|
||||||
|
|
||||||
const initHotbarStoreInjectable = getInjectable({
|
const loadHotbarStorageInjectable = getInjectable({
|
||||||
id: "init-hotbar-store",
|
id: "load-hotbar-storage",
|
||||||
instantiate: (di) => ({
|
instantiate: (di) => ({
|
||||||
run: () => {
|
run: () => {
|
||||||
const hotbarStore = di.inject(hotbarStoreInjectable);
|
const storage = di.inject(hotbarsPersistentStorageInjectable);
|
||||||
|
|
||||||
hotbarStore.load();
|
storage.loadAndStartSyncing();
|
||||||
},
|
},
|
||||||
runAfter: initClusterStoreInjectable,
|
runAfter: initClusterStoreInjectable,
|
||||||
}),
|
}),
|
||||||
injectionToken: beforeFrameStartsSecondInjectionToken,
|
injectionToken: beforeFrameStartsSecondInjectionToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default initHotbarStoreInjectable;
|
export default loadHotbarStorageInjectable;
|
||||||
@ -0,0 +1,373 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { anyObject } from "jest-mock-extended";
|
||||||
|
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../../../common/catalog";
|
||||||
|
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
|
||||||
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
|
import catalogEntityRegistryInjectable from "../../../main/catalog/entity-registry.injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import hasCategoryForEntityInjectable from "../../../common/catalog/has-category-for-entity.injectable";
|
||||||
|
import catalogCatalogEntityInjectable from "../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
||||||
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
|
import type { Logger } from "../../../common/logger";
|
||||||
|
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import storeMigrationVersionInjectable from "../../../common/vars/store-migration-version.injectable";
|
||||||
|
import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable";
|
||||||
|
import type { SetAsActiveHotbar } from "./common/set-as-active.injectable";
|
||||||
|
import setAsActiveHotbarInjectable from "./common/set-as-active.injectable";
|
||||||
|
import hotbarsPersistentStorageInjectable from "./common/storage.injectable";
|
||||||
|
import type { Hotbar } from "./common/hotbar";
|
||||||
|
import hotbarsInjectable from "./common/hotbars.injectable";
|
||||||
|
import activeHotbarInjectable from "./common/active.injectable";
|
||||||
|
import type { AddHotbar } from "./common/add.injectable";
|
||||||
|
import type { GetHotbarById } from "./common/get-by-id.injectable";
|
||||||
|
import getHotbarByIdInjectable from "./common/get-by-id.injectable";
|
||||||
|
import addHotbarInjectable from "./common/add.injectable";
|
||||||
|
import { defaultHotbarCells } from "./common/types";
|
||||||
|
|
||||||
|
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
|
||||||
|
return {
|
||||||
|
getName: jest.fn(() => data.metadata?.name),
|
||||||
|
getId: jest.fn(() => data.metadata?.uid),
|
||||||
|
getSource: jest.fn(() => data.metadata?.source ?? "unknown"),
|
||||||
|
isEnabled: jest.fn(() => data.status?.enabled ?? true),
|
||||||
|
onContextMenuOpen: jest.fn(),
|
||||||
|
onSettingsOpen: jest.fn(),
|
||||||
|
metadata: {},
|
||||||
|
spec: {},
|
||||||
|
status: {},
|
||||||
|
...data,
|
||||||
|
} as CatalogEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Hotbars technical tests", () => {
|
||||||
|
let di: DiContainer;
|
||||||
|
let testCluster: CatalogEntity;
|
||||||
|
let minikubeCluster: CatalogEntity;
|
||||||
|
let awsCluster: CatalogEntity;
|
||||||
|
let loggerMock: jest.Mocked<Logger>;
|
||||||
|
let setAsActiveHotbar: SetAsActiveHotbar;
|
||||||
|
let hotbars: IComputedValue<Hotbar[]>;
|
||||||
|
let activeHotbar: IComputedValue<Hotbar | undefined>;
|
||||||
|
let addHotbar: AddHotbar;
|
||||||
|
let getHotbarById: GetHotbarById;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
di = getDiForUnitTesting();
|
||||||
|
|
||||||
|
testCluster = getMockCatalogEntity({
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Cluster",
|
||||||
|
status: {
|
||||||
|
phase: "Running",
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
uid: "some-test-id",
|
||||||
|
name: "my-test-cluster",
|
||||||
|
source: "local",
|
||||||
|
labels: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
minikubeCluster = getMockCatalogEntity({
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Cluster",
|
||||||
|
status: {
|
||||||
|
phase: "Running",
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
uid: "some-minikube-id",
|
||||||
|
name: "my-minikube-cluster",
|
||||||
|
source: "local",
|
||||||
|
labels: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
awsCluster = getMockCatalogEntity({
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Cluster",
|
||||||
|
status: {
|
||||||
|
phase: "Running",
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
uid: "some-aws-id",
|
||||||
|
name: "my-aws-cluster",
|
||||||
|
source: "local",
|
||||||
|
labels: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
di.override(hasCategoryForEntityInjectable, () => () => true);
|
||||||
|
|
||||||
|
loggerMock = {
|
||||||
|
warn: jest.fn(),
|
||||||
|
debug: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
silly: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
di.override(loggerInjectable, () => loggerMock);
|
||||||
|
|
||||||
|
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
||||||
|
|
||||||
|
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||||
|
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
|
||||||
|
|
||||||
|
catalogEntityRegistry.addComputedSource("some-id", computed(() => [
|
||||||
|
testCluster,
|
||||||
|
minikubeCluster,
|
||||||
|
awsCluster,
|
||||||
|
catalogCatalogEntity,
|
||||||
|
]));
|
||||||
|
|
||||||
|
setAsActiveHotbar = di.inject(setAsActiveHotbarInjectable);
|
||||||
|
hotbars = di.inject(hotbarsInjectable);
|
||||||
|
activeHotbar = di.inject(activeHotbarInjectable);
|
||||||
|
addHotbar = di.inject(addHotbarInjectable);
|
||||||
|
getHotbarById = di.inject(getHotbarByIdInjectable);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given no previous data in store, running all migrations", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
di.override(storeMigrationVersionInjectable, () => "9999.0.0");
|
||||||
|
di.inject(hotbarsPersistentStorageInjectable).loadAndStartSyncing();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("load", () => {
|
||||||
|
it("loads one hotbar by default", () => {
|
||||||
|
expect(hotbars.get().length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("add", () => {
|
||||||
|
it("adds a hotbar", () => {
|
||||||
|
addHotbar({ name: "hottest" });
|
||||||
|
expect(hotbars.get().length).toEqual(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("hotbar items", () => {
|
||||||
|
it("initially creates default number of empty cells", () => {
|
||||||
|
expect(activeHotbar.get()?.items?.length).toEqual(defaultHotbarCells);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initially adds catalog entity as first item", () => {
|
||||||
|
expect(activeHotbar.get()?.items[0]?.entity.name).toEqual("Catalog");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds items", () => {
|
||||||
|
activeHotbar.get()?.addEntity(testCluster);
|
||||||
|
const items = activeHotbar.get()?.items.filter(Boolean);
|
||||||
|
|
||||||
|
expect(items?.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes items", () => {
|
||||||
|
activeHotbar.get()?.addEntity(testCluster);
|
||||||
|
activeHotbar.get()?.removeEntity("some-test-id");
|
||||||
|
activeHotbar.get()?.removeEntity("catalog-entity");
|
||||||
|
const items = activeHotbar.get()?.items.filter(Boolean);
|
||||||
|
|
||||||
|
expect(items).toStrictEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does nothing if removing with invalid uid", () => {
|
||||||
|
activeHotbar.get()?.addEntity(testCluster);
|
||||||
|
activeHotbar.get()?.removeEntity("invalid uid");
|
||||||
|
const items = activeHotbar.get()?.items.filter(Boolean);
|
||||||
|
|
||||||
|
expect(items?.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("moves item to empty cell", () => {
|
||||||
|
activeHotbar.get()?.addEntity(testCluster);
|
||||||
|
activeHotbar.get()?.addEntity(minikubeCluster);
|
||||||
|
activeHotbar.get()?.addEntity(awsCluster);
|
||||||
|
|
||||||
|
expect(activeHotbar.get()?.items[6]).toBeNull();
|
||||||
|
|
||||||
|
activeHotbar.get()?.restack(1, 5);
|
||||||
|
|
||||||
|
expect(activeHotbar.get()?.items[5]).toBeTruthy();
|
||||||
|
expect(activeHotbar.get()?.items[5]?.entity.uid).toEqual("some-test-id");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("moves items down", () => {
|
||||||
|
activeHotbar.get()?.addEntity(testCluster);
|
||||||
|
activeHotbar.get()?.addEntity(minikubeCluster);
|
||||||
|
activeHotbar.get()?.addEntity(awsCluster);
|
||||||
|
|
||||||
|
// aws -> catalog
|
||||||
|
activeHotbar.get()?.restack(3, 0);
|
||||||
|
|
||||||
|
const items = activeHotbar.get()?.items.map(item => item?.entity.uid || null);
|
||||||
|
|
||||||
|
expect(items?.slice(0, 4)).toEqual(["some-aws-id", "catalog-entity", "some-test-id", "some-minikube-id"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("moves items up", () => {
|
||||||
|
activeHotbar.get()?.addEntity(testCluster);
|
||||||
|
activeHotbar.get()?.addEntity(minikubeCluster);
|
||||||
|
activeHotbar.get()?.addEntity(awsCluster);
|
||||||
|
|
||||||
|
// test -> aws
|
||||||
|
activeHotbar.get()?.restack(1, 3);
|
||||||
|
|
||||||
|
const items = activeHotbar.get()?.items.map(item => item?.entity.uid || null);
|
||||||
|
|
||||||
|
expect(items?.slice(0, 4)).toEqual(["catalog-entity", "some-minikube-id", "some-aws-id", "some-test-id"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs an error if cellIndex is out of bounds", () => {
|
||||||
|
addHotbar({ name: "hottest", id: "hottest" });
|
||||||
|
setAsActiveHotbar("hottest");
|
||||||
|
|
||||||
|
activeHotbar.get()?.addEntity(testCluster, -1);
|
||||||
|
expect(loggerMock.error).toBeCalledWith("[HOTBAR]: cannot pin entity to hotbar outside of index range", anyObject());
|
||||||
|
|
||||||
|
activeHotbar.get()?.addEntity(testCluster, 12);
|
||||||
|
expect(loggerMock.error).toBeCalledWith("[HOTBAR]: cannot pin entity to hotbar outside of index range", anyObject());
|
||||||
|
|
||||||
|
activeHotbar.get()?.addEntity(testCluster, 13);
|
||||||
|
expect(loggerMock.error).toBeCalledWith("[HOTBAR]: cannot pin entity to hotbar outside of index range", anyObject());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws an error if getId is invalid or returns not a string", () => {
|
||||||
|
expect(() => activeHotbar.get()?.addEntity({} as any)).toThrowError(TypeError);
|
||||||
|
expect(() => activeHotbar.get()?.addEntity({ getId: () => true } as any)).toThrowError(TypeError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws an error if getName is invalid or returns not a string", () => {
|
||||||
|
expect(() => activeHotbar.get()?.addEntity({ getId: () => "" } as any)).toThrowError(TypeError);
|
||||||
|
expect(() => activeHotbar.get()?.addEntity({ getId: () => "", getName: () => 4 } as any)).toThrowError(TypeError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does nothing when item moved to same cell", () => {
|
||||||
|
activeHotbar.get()?.addEntity(testCluster);
|
||||||
|
activeHotbar.get()?.restack(1, 1);
|
||||||
|
|
||||||
|
expect(activeHotbar.get()?.items[1]?.entity.uid).toEqual("some-test-id");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("new items takes first empty cell", () => {
|
||||||
|
activeHotbar.get()?.addEntity(testCluster);
|
||||||
|
activeHotbar.get()?.addEntity(awsCluster);
|
||||||
|
activeHotbar.get()?.restack(0, 3);
|
||||||
|
activeHotbar.get()?.addEntity(minikubeCluster);
|
||||||
|
|
||||||
|
expect(activeHotbar.get()?.items[0]?.entity.uid).toEqual("some-minikube-id");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws if invalid arguments provided", () => {
|
||||||
|
activeHotbar.get()?.addEntity(testCluster);
|
||||||
|
|
||||||
|
expect(() => activeHotbar.get()?.restack(-5, 0)).toThrow();
|
||||||
|
expect(() => activeHotbar.get()?.restack(2, -1)).toThrow();
|
||||||
|
expect(() => activeHotbar.get()?.restack(14, 1)).toThrow();
|
||||||
|
expect(() => activeHotbar.get()?.restack(11, 112)).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks if entity already pinned to hotbar", () => {
|
||||||
|
activeHotbar.get()?.addEntity(testCluster);
|
||||||
|
|
||||||
|
expect(activeHotbar.get()?.hasEntity(testCluster.getId())).toBeTruthy();
|
||||||
|
expect(activeHotbar.get()?.hasEntity(awsCluster.getId())).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const writeJsonSync = di.inject(writeJsonSyncInjectable);
|
||||||
|
|
||||||
|
writeJsonSync("/some-directory-for-user-data/lens-hotbar-store.json", {
|
||||||
|
__internal__: {
|
||||||
|
migrations: {
|
||||||
|
version: "5.0.0-beta.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hotbars: [
|
||||||
|
{
|
||||||
|
id: "3caac17f-aec2-4723-9694-ad204465d935",
|
||||||
|
name: "myhotbar",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
entity: {
|
||||||
|
uid: "some-aws-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entity: {
|
||||||
|
uid: "55b42c3c7ba3b04193416cda405269a5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entity: {
|
||||||
|
uid: "176fd331968660832f62283219d7eb6e",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entity: {
|
||||||
|
uid: "61c4fb45528840ebad1badc25da41d14",
|
||||||
|
name: "user1-context",
|
||||||
|
source: "local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entity: {
|
||||||
|
uid: "27d6f99fe9e7548a6e306760bfe19969",
|
||||||
|
name: "foo2",
|
||||||
|
source: "local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
entity: {
|
||||||
|
uid: "c0b20040646849bb4dcf773e43a0bf27",
|
||||||
|
name: "multinode-demo",
|
||||||
|
source: "local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
di.override(storeMigrationVersionInjectable, () => "5.0.0-beta.10");
|
||||||
|
|
||||||
|
di.inject(hotbarsPersistentStorageInjectable).loadAndStartSyncing();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows to retrieve a hotbar", () => {
|
||||||
|
const hotbar = getHotbarById("3caac17f-aec2-4723-9694-ad204465d935");
|
||||||
|
|
||||||
|
expect(hotbar?.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears cells without entity", () => {
|
||||||
|
const items = hotbars.get()[0].items;
|
||||||
|
|
||||||
|
expect(items[2]).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds extra data to cells with according entity", () => {
|
||||||
|
const items = hotbars.get()[0].items;
|
||||||
|
|
||||||
|
expect(items[0]).toEqual({
|
||||||
|
entity: {
|
||||||
|
name: "my-aws-cluster",
|
||||||
|
source: "local",
|
||||||
|
uid: "some-aws-id",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Cleans up a store that had the state related data stored
|
|
||||||
import { getEmptyHotbar } from "../../../common/hotbars/types";
|
|
||||||
import catalogCatalogEntityInjectable from "../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { hotbarStoreMigrationInjectionToken } from "../../../common/hotbars/migrations-token";
|
|
||||||
|
|
||||||
const v500Alpha0HotbarStoreMigrationInjectable = getInjectable({
|
|
||||||
id: "v5.0.0-alpha.0-hotbar-store-migration",
|
|
||||||
instantiate: (di) => {
|
|
||||||
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
|
|
||||||
|
|
||||||
return {
|
|
||||||
version: "5.0.0-alpha.0",
|
|
||||||
run(store) {
|
|
||||||
const hotbar = getEmptyHotbar("default");
|
|
||||||
|
|
||||||
const { metadata: { uid, name, source }} = catalogCatalogEntity;
|
|
||||||
|
|
||||||
hotbar.items[0] = { entity: { uid, name, source }};
|
|
||||||
|
|
||||||
store.set("hotbars", [hotbar]);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
injectionToken: hotbarStoreMigrationInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default v500Alpha0HotbarStoreMigrationInjectable;
|
|
||||||
|
|
||||||
@ -1,182 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as uuid from "uuid";
|
|
||||||
import type { Hotbar, HotbarItem } from "../../../common/hotbars/types";
|
|
||||||
import { defaultHotbarCells, getEmptyHotbar } from "../../../common/hotbars/types";
|
|
||||||
import { getLegacyGlobalDiForExtensionApi } from "../../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
|
||||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
import catalogCatalogEntityInjectable from "../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
|
||||||
import { isDefined, isErrnoException } from "@k8slens/utilities";
|
|
||||||
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { hotbarStoreMigrationInjectionToken } from "../../../common/hotbars/migrations-token";
|
|
||||||
import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable";
|
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
|
||||||
import { generateNewIdFor } from "../../../common/utils/generate-new-id-for";
|
|
||||||
import type { ClusterModel } from "../../../common/cluster-types";
|
|
||||||
|
|
||||||
interface Pre500WorkspaceStoreModel {
|
|
||||||
workspaces: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PartialHotbar {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
items: (null | HotbarItem)[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Pre500ClusterModel extends ClusterModel {
|
|
||||||
workspace?: string;
|
|
||||||
workspaces?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Pre500ClusterStoreModel {
|
|
||||||
clusters?: Pre500ClusterModel[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const v500Beta10HotbarStoreMigrationInjectable = getInjectable({
|
|
||||||
id: "v5.0.0-beta.10-hotbar-store-migration",
|
|
||||||
instantiate: (di) => {
|
|
||||||
const userDataPath = di.inject(directoryForUserDataInjectable);
|
|
||||||
const joinPaths = di.inject(joinPathsInjectable);
|
|
||||||
const readJsonSync = di.inject(readJsonSyncInjectable);
|
|
||||||
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
|
|
||||||
const logger = di.inject(loggerInjectable);
|
|
||||||
|
|
||||||
return {
|
|
||||||
version: "5.0.0-beta.10",
|
|
||||||
run(store) {
|
|
||||||
const rawHotbars = store.get("hotbars");
|
|
||||||
const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars.filter(h => h && typeof h === "object") : [];
|
|
||||||
|
|
||||||
|
|
||||||
// Hotbars might be empty, if some of the previous migrations weren't run
|
|
||||||
if (hotbars.length === 0) {
|
|
||||||
const hotbar = getEmptyHotbar("default");
|
|
||||||
const { metadata: { uid, name, source }} = catalogCatalogEntity;
|
|
||||||
|
|
||||||
hotbar.items[0] = { entity: { uid, name, source }};
|
|
||||||
|
|
||||||
hotbars.push(hotbar);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const workspaceStoreData: Pre500WorkspaceStoreModel = readJsonSync(joinPaths(userDataPath, "lens-workspace-store.json"));
|
|
||||||
const { clusters = [] }: Pre500ClusterStoreModel = readJsonSync(joinPaths(userDataPath, "lens-cluster-store.json"));
|
|
||||||
const workspaceHotbars = new Map<string, PartialHotbar>(); // mapping from WorkspaceId to HotBar
|
|
||||||
|
|
||||||
for (const { id, name } of workspaceStoreData.workspaces) {
|
|
||||||
logger.info(`Creating new hotbar for ${name}`);
|
|
||||||
workspaceHotbars.set(id, {
|
|
||||||
id: uuid.v4(), // don't use the old IDs as they aren't necessarily UUIDs
|
|
||||||
items: [],
|
|
||||||
name: `Workspace: ${name}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// grab the default named hotbar or the first.
|
|
||||||
const defaultHotbarIndex = Math.max(0, hotbars.findIndex(hotbar => hotbar.name === "default"));
|
|
||||||
const [{ name, id, items }] = hotbars.splice(defaultHotbarIndex, 1);
|
|
||||||
|
|
||||||
workspaceHotbars.set("default", {
|
|
||||||
name,
|
|
||||||
id,
|
|
||||||
items: items.filter(isDefined),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const cluster of clusters) {
|
|
||||||
const uid = generateNewIdFor(cluster);
|
|
||||||
|
|
||||||
for (const workspaceId of cluster.workspaces ?? [cluster.workspace].filter(isDefined)) {
|
|
||||||
const workspaceHotbar = workspaceHotbars.get(workspaceId);
|
|
||||||
|
|
||||||
if (!workspaceHotbar) {
|
|
||||||
logger.info(`Cluster ${uid} has unknown workspace ID, skipping`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Adding cluster ${uid} to ${workspaceHotbar.name}`);
|
|
||||||
|
|
||||||
if (workspaceHotbar?.items.length < defaultHotbarCells) {
|
|
||||||
workspaceHotbar.items.push({
|
|
||||||
entity: {
|
|
||||||
uid: generateNewIdFor(cluster),
|
|
||||||
name: cluster.preferences?.clusterName || cluster.contextName,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const hotbar of workspaceHotbars.values()) {
|
|
||||||
if (hotbar.items.length === 0) {
|
|
||||||
logger.info(`Skipping ${hotbar.name} due to it being empty`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (hotbar.items.length < defaultHotbarCells) {
|
|
||||||
hotbar.items.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
hotbars.push(hotbar as Hotbar);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finally, make sure that the catalog entity hotbar item is in place.
|
|
||||||
* Just in case something else removed it.
|
|
||||||
*
|
|
||||||
* if every hotbar has elements that all not the `catalog-entity` item
|
|
||||||
*/
|
|
||||||
if (hotbars.every(hotbar => hotbar.items.every(item => item?.entity?.uid !== "catalog-entity"))) {
|
|
||||||
// note, we will add a new whole hotbar here called "default" if that was previously removed
|
|
||||||
const di = getLegacyGlobalDiForExtensionApi();
|
|
||||||
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
|
|
||||||
|
|
||||||
const defaultHotbar = hotbars.find(hotbar => hotbar.name === "default");
|
|
||||||
const { metadata: { uid, name, source }} = catalogCatalogEntity;
|
|
||||||
|
|
||||||
if (defaultHotbar) {
|
|
||||||
const freeIndex = defaultHotbar.items.findIndex(i => i === null);
|
|
||||||
|
|
||||||
if (freeIndex === -1) {
|
|
||||||
// making a new hotbar is less destructive if the first hotbar
|
|
||||||
// called "default" is full than overriding a hotbar item
|
|
||||||
const hotbar = getEmptyHotbar("initial");
|
|
||||||
|
|
||||||
hotbar.items[0] = { entity: { uid, name, source }};
|
|
||||||
hotbars.unshift(hotbar);
|
|
||||||
} else {
|
|
||||||
defaultHotbar.items[freeIndex] = { entity: { uid, name, source }};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const hotbar = getEmptyHotbar("default");
|
|
||||||
|
|
||||||
hotbar.items[0] = { entity: { uid, name, source }};
|
|
||||||
hotbars.unshift(hotbar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
// ignore files being missing
|
|
||||||
if (isErrnoException(error) && error.code !== "ENOENT") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
store.set("hotbars", hotbars);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
injectionToken: hotbarStoreMigrationInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default v500Beta10HotbarStoreMigrationInjectable;
|
|
||||||
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Hotbar } from "../../../common/hotbars/types";
|
|
||||||
import catalogEntityRegistryInjectable from "../../catalog/entity-registry.injectable";
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { hotbarStoreMigrationInjectionToken } from "../../../common/hotbars/migrations-token";
|
|
||||||
|
|
||||||
const v500Beta5HotbarStoreMigrationInjectable = getInjectable({
|
|
||||||
id: "v500-beta5-hotbar-store-migration",
|
|
||||||
instantiate: (di) => {
|
|
||||||
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
|
||||||
|
|
||||||
return {
|
|
||||||
version: "5.0.0-beta.5",
|
|
||||||
run(store) {
|
|
||||||
const rawHotbars = store.get("hotbars");
|
|
||||||
const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars : [];
|
|
||||||
|
|
||||||
for (const hotbar of hotbars) {
|
|
||||||
for (let i = 0; i < hotbar.items.length; i += 1) {
|
|
||||||
const item = hotbar.items[i];
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entity = catalogEntityRegistry.findById(item.entity.uid);
|
|
||||||
|
|
||||||
if (!entity) {
|
|
||||||
// Clear disabled item
|
|
||||||
hotbar.items[i] = null;
|
|
||||||
} else {
|
|
||||||
// Save additional data
|
|
||||||
item.entity = {
|
|
||||||
...item.entity,
|
|
||||||
name: entity.metadata.name,
|
|
||||||
source: entity.metadata.source,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
store.set("hotbars", hotbars);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
injectionToken: hotbarStoreMigrationInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default v500Beta5HotbarStoreMigrationInjectable;
|
|
||||||
|
|
||||||
@ -11,7 +11,6 @@ import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
|||||||
import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns";
|
import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns";
|
||||||
import type { CategoryColumns, GetCategoryColumnsParams } from "../columns/get.injectable";
|
import type { CategoryColumns, GetCategoryColumnsParams } from "../columns/get.injectable";
|
||||||
import getCategoryColumnsInjectable from "../columns/get.injectable";
|
import getCategoryColumnsInjectable from "../columns/get.injectable";
|
||||||
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
|
|
||||||
import extensionInjectable from "../../../../extensions/extension-loader/extension/extension.injectable";
|
import extensionInjectable from "../../../../extensions/extension-loader/extension/extension.injectable";
|
||||||
import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable";
|
import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable";
|
||||||
|
|
||||||
@ -46,7 +45,6 @@ describe("Custom Category Columns", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
di = getDiForUnitTesting();
|
di = getDiForUnitTesting();
|
||||||
|
|
||||||
di.override(hotbarStoreInjectable, () => ({}));
|
|
||||||
di.override(currentlyInClusterFrameInjectable, () => false);
|
di.override(currentlyInClusterFrameInjectable, () => false);
|
||||||
|
|
||||||
getCategoryColumns = di.inject(getCategoryColumnsInjectable);
|
getCategoryColumns = di.inject(getCategoryColumnsInjectable);
|
||||||
|
|||||||
@ -28,8 +28,6 @@ import type { VisitEntityContextMenu } from "../../../common/catalog/visit-entit
|
|||||||
import visitEntityContextMenuInjectable from "../../../common/catalog/visit-entity-context-menu.injectable";
|
import visitEntityContextMenuInjectable from "../../../common/catalog/visit-entity-context-menu.injectable";
|
||||||
import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||||
import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||||
import type { HotbarStore } from "../../../common/hotbars/store";
|
|
||||||
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
|
||||||
import type { Logger } from "../../../common/logger";
|
import type { Logger } from "../../../common/logger";
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
|
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
|
||||||
@ -51,6 +49,8 @@ import type { OnCatalogEntityListClick } from "./entity-details/on-catalog-click
|
|||||||
import onCatalogEntityListClickInjectable from "./entity-details/on-catalog-click.injectable";
|
import onCatalogEntityListClickInjectable from "./entity-details/on-catalog-click.injectable";
|
||||||
import type { ShowEntityDetails } from "./entity-details/show.injectable";
|
import type { ShowEntityDetails } from "./entity-details/show.injectable";
|
||||||
import showEntityDetailsInjectable from "./entity-details/show.injectable";
|
import showEntityDetailsInjectable from "./entity-details/show.injectable";
|
||||||
|
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
|
||||||
|
import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
catalogPreviousActiveTabStorage: StorageLayer<string | null>;
|
catalogPreviousActiveTabStorage: StorageLayer<string | null>;
|
||||||
@ -65,13 +65,13 @@ interface Dependencies {
|
|||||||
kind: IComputedValue<string>;
|
kind: IComputedValue<string>;
|
||||||
};
|
};
|
||||||
navigateToCatalog: NavigateToCatalog;
|
navigateToCatalog: NavigateToCatalog;
|
||||||
hotbarStore: HotbarStore;
|
|
||||||
catalogCategoryRegistry: CatalogCategoryRegistry;
|
catalogCategoryRegistry: CatalogCategoryRegistry;
|
||||||
visitEntityContextMenu: VisitEntityContextMenu;
|
visitEntityContextMenu: VisitEntityContextMenu;
|
||||||
navigate: Navigate;
|
navigate: Navigate;
|
||||||
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
|
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
|
||||||
showErrorNotification: ShowNotification;
|
showErrorNotification: ShowNotification;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
|
activeHotbar: IComputedValue<Hotbar | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -156,11 +156,11 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addToHotbar(entity: CatalogEntity): void {
|
addToHotbar(entity: CatalogEntity): void {
|
||||||
this.props.hotbarStore.addToHotbar(entity);
|
this.props.activeHotbar.get()?.addEntity(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFromHotbar(entity: CatalogEntity): void {
|
removeFromHotbar(entity: CatalogEntity): void {
|
||||||
this.props.hotbarStore.removeFromHotbar(entity.getId());
|
this.props.activeHotbar.get()?.removeEntity(entity.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
onTabChange = action((tabId: string | null) => {
|
onTabChange = action((tabId: string | null) => {
|
||||||
@ -323,7 +323,7 @@ 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(emitAppEventInjectable),
|
emitEvent: di.inject(emitAppEventInjectable),
|
||||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
activeHotbar: di.inject(activeHotbarInjectable),
|
||||||
catalogCategoryRegistry: di.inject(catalogCategoryRegistryInjectable),
|
catalogCategoryRegistry: di.inject(catalogCategoryRegistryInjectable),
|
||||||
visitEntityContextMenu: di.inject(visitEntityContextMenuInjectable),
|
visitEntityContextMenu: di.inject(visitEntityContextMenuInjectable),
|
||||||
navigate: di.inject(navigateInjectable),
|
navigate: di.inject(navigateInjectable),
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import styles from "../catalog.module.scss";
|
||||||
|
import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
|
||||||
|
import renderNamedCategoryColumnCellInjectable from "./render-named-category-column-cell.injectable";
|
||||||
|
|
||||||
|
const namedCategoryColumnInjectable = getInjectable({
|
||||||
|
id: "name-category-column",
|
||||||
|
instantiate: (di): RegisteredAdditionalCategoryColumn => ({
|
||||||
|
id: "name",
|
||||||
|
priority: 0,
|
||||||
|
renderCell: di.inject(renderNamedCategoryColumnCellInjectable),
|
||||||
|
titleProps: {
|
||||||
|
title: "Name",
|
||||||
|
className: styles.entityName,
|
||||||
|
id: "name",
|
||||||
|
sortBy: "name",
|
||||||
|
},
|
||||||
|
searchFilter: (entity) => entity.getName(),
|
||||||
|
sortCallback: (entity) => `name=${entity.getName()}`,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default namedCategoryColumnInjectable;
|
||||||
@ -1,65 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import styles from "../catalog.module.scss";
|
|
||||||
import type { CatalogEntity } from "../../../../common/catalog";
|
|
||||||
import { prevDefault } from "@k8slens/utilities";
|
|
||||||
import { Avatar } from "../../avatar";
|
|
||||||
import { Icon } from "../../icon";
|
|
||||||
import React from "react";
|
|
||||||
import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
|
|
||||||
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
|
|
||||||
import type { HotbarStore } from "../../../../common/hotbars/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}
|
|
||||||
svg={isItemInHotbar ? "push_off" : "push_pin"}
|
|
||||||
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const namedCategoryColumnInjectable = 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 namedCategoryColumnInjectable;
|
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* 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 React from "react";
|
||||||
|
import activeHotbarInjectable from "../../../../features/hotbar/storage/common/active.injectable";
|
||||||
|
import { Avatar } from "../../avatar";
|
||||||
|
import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
|
||||||
|
import { Icon } from "../../icon";
|
||||||
|
import { prevDefault } from "@k8slens/utilities";
|
||||||
|
|
||||||
|
const renderNamedCategoryColumnCellInjectable = getInjectable({
|
||||||
|
id: "render-named-category-column-cell",
|
||||||
|
instantiate: (di): RegisteredAdditionalCategoryColumn["renderCell"] => {
|
||||||
|
const activeHotbar = di.inject(activeHotbarInjectable);
|
||||||
|
|
||||||
|
return (entity) => {
|
||||||
|
const hotbar = activeHotbar.get();
|
||||||
|
|
||||||
|
if (!hotbar) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isItemInHotbar = hotbar.hasEntity(entity.getId());
|
||||||
|
const onClick = prevDefault((
|
||||||
|
isItemInHotbar
|
||||||
|
? () => hotbar.removeEntity(entity.getId())
|
||||||
|
: () => hotbar.addEntity(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}
|
||||||
|
svg={isItemInHotbar ? "push_off" : "push_pin"}
|
||||||
|
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default renderNamedCategoryColumnCellInjectable;
|
||||||
@ -9,11 +9,12 @@ 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 { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
import type { IComputedValue } from "mobx";
|
||||||
import type { HotbarStore } from "../../../common/hotbars/store";
|
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
|
||||||
|
import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
hotbarStore: HotbarStore;
|
activeHotbar: IComputedValue<Hotbar | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HotbarToggleMenuItemProps {
|
interface HotbarToggleMenuItemProps {
|
||||||
@ -25,19 +26,19 @@ interface HotbarToggleMenuItemProps {
|
|||||||
function NonInjectedHotbarToggleMenuItem({
|
function NonInjectedHotbarToggleMenuItem({
|
||||||
addContent,
|
addContent,
|
||||||
entity,
|
entity,
|
||||||
hotbarStore,
|
activeHotbar,
|
||||||
removeContent,
|
removeContent,
|
||||||
}: Dependencies & HotbarToggleMenuItemProps) {
|
}: Dependencies & HotbarToggleMenuItemProps) {
|
||||||
const [itemInHotbar, setItemInHotbar] = useState(hotbarStore.isAddedToActive(entity));
|
const [itemInHotbar, setItemInHotbar] = useState(activeHotbar.get()?.hasEntity(entity.getId()) ?? false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (itemInHotbar) {
|
if (itemInHotbar) {
|
||||||
hotbarStore.removeFromHotbar(entity.getId());
|
activeHotbar.get()?.removeEntity(entity.getId());
|
||||||
setItemInHotbar(false);
|
setItemInHotbar(false);
|
||||||
} else {
|
} else {
|
||||||
hotbarStore.addToHotbar(entity);
|
activeHotbar.get()?.addEntity(entity);
|
||||||
setItemInHotbar(true);
|
setItemInHotbar(true);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -47,14 +48,10 @@ function NonInjectedHotbarToggleMenuItem({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HotbarToggleMenuItem = withInjectables<Dependencies, HotbarToggleMenuItemProps>(
|
export const HotbarToggleMenuItem = withInjectables<Dependencies, HotbarToggleMenuItemProps>(NonInjectedHotbarToggleMenuItem, {
|
||||||
NonInjectedHotbarToggleMenuItem,
|
|
||||||
|
|
||||||
{
|
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
|
||||||
...props,
|
...props,
|
||||||
|
activeHotbar: di.inject(activeHotbarInjectable),
|
||||||
}),
|
}),
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
|
|||||||
@ -15,9 +15,7 @@ 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 type { HotbarStore } from "../../../common/hotbars/store";
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
|
||||||
import type { DeleteClusterDialogState } from "./state.injectable";
|
import type { DeleteClusterDialogState } from "./state.injectable";
|
||||||
import deleteClusterDialogStateInjectable from "./state.injectable";
|
import deleteClusterDialogStateInjectable from "./state.injectable";
|
||||||
import type { RequestSetClusterAsDeleting } from "../../../features/cluster/delete-dialog/renderer/request-set-as-deleting.injectable";
|
import type { RequestSetClusterAsDeleting } from "../../../features/cluster/delete-dialog/renderer/request-set-as-deleting.injectable";
|
||||||
@ -32,16 +30,18 @@ import showErrorNotificationInjectable from "../notifications/show-error-notific
|
|||||||
import { isCurrentContext } from "./is-current-context";
|
import { isCurrentContext } from "./is-current-context";
|
||||||
import type { IsInLocalKubeconfig } from "./is-in-local-kubeconfig.injectable";
|
import type { IsInLocalKubeconfig } from "./is-in-local-kubeconfig.injectable";
|
||||||
import isInLocalKubeconfigInjectable from "./is-in-local-kubeconfig.injectable";
|
import isInLocalKubeconfigInjectable from "./is-in-local-kubeconfig.injectable";
|
||||||
|
import type { RemoveEntityFromAllHotbars } from "../../../features/hotbar/storage/common/remove-entity-from-all.injectable";
|
||||||
|
import removeEntityFromAllHotbarsInjectable from "../../../features/hotbar/storage/common/remove-entity-from-all.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
state: IObservableValue<DeleteClusterDialogState | undefined>;
|
state: IObservableValue<DeleteClusterDialogState | undefined>;
|
||||||
hotbarStore: HotbarStore;
|
|
||||||
requestSetClusterAsDeleting: RequestSetClusterAsDeleting;
|
requestSetClusterAsDeleting: RequestSetClusterAsDeleting;
|
||||||
requestDeleteCluster: RequestDeleteCluster;
|
requestDeleteCluster: RequestDeleteCluster;
|
||||||
requestClearClusterAsDeleting: RequestClearClusterAsDeleting;
|
requestClearClusterAsDeleting: RequestClearClusterAsDeleting;
|
||||||
showErrorNotification: ShowNotification;
|
showErrorNotification: ShowNotification;
|
||||||
saveKubeconfig: SaveKubeconfig;
|
saveKubeconfig: SaveKubeconfig;
|
||||||
isInLocalKubeconfig: IsInLocalKubeconfig;
|
isInLocalKubeconfig: IsInLocalKubeconfig;
|
||||||
|
removeEntityFromAllHotbars: RemoveEntityFromAllHotbars;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -65,7 +65,7 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.props.saveKubeconfig(config, cluster.kubeConfigPath.get());
|
await this.props.saveKubeconfig(config, cluster.kubeConfigPath.get());
|
||||||
this.props.hotbarStore.removeAllHotbarItems(cluster.id);
|
this.props.removeEntityFromAllHotbars(cluster.id);
|
||||||
await this.props.requestDeleteCluster(cluster.id);
|
await this.props.requestDeleteCluster(cluster.id);
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
this.props.showErrorNotification(`Cannot remove cluster, failed to process config file. ${error}`);
|
this.props.showErrorNotification(`Cannot remove cluster, failed to process config file. ${error}`);
|
||||||
@ -267,7 +267,6 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
|||||||
|
|
||||||
export const DeleteClusterDialog = withInjectables<Dependencies>(NonInjectedDeleteClusterDialog, {
|
export const DeleteClusterDialog = withInjectables<Dependencies>(NonInjectedDeleteClusterDialog, {
|
||||||
getProps: (di) => ({
|
getProps: (di) => ({
|
||||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
|
||||||
state: di.inject(deleteClusterDialogStateInjectable),
|
state: di.inject(deleteClusterDialogStateInjectable),
|
||||||
requestSetClusterAsDeleting: di.inject(requestSetClusterAsDeletingInjectable),
|
requestSetClusterAsDeleting: di.inject(requestSetClusterAsDeletingInjectable),
|
||||||
requestClearClusterAsDeleting: di.inject(requestClearClusterAsDeletingInjectable),
|
requestClearClusterAsDeleting: di.inject(requestClearClusterAsDeletingInjectable),
|
||||||
@ -275,5 +274,6 @@ export const DeleteClusterDialog = withInjectables<Dependencies>(NonInjectedDele
|
|||||||
saveKubeconfig: di.inject(saveKubeconfigInjectable),
|
saveKubeconfig: di.inject(saveKubeconfigInjectable),
|
||||||
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
||||||
isInLocalKubeconfig: di.inject(isInLocalKubeconfigInjectable),
|
isInLocalKubeconfig: di.inject(isInLocalKubeconfigInjectable),
|
||||||
|
removeEntityFromAllHotbars: di.inject(removeEntityFromAllHotbarsInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,97 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<HotbarRemoveCommand /> renders w/o errors 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="Select theme-dark css-b62m3t-container"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
|
id="react-select-2-live-region"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
id="aria-selection"
|
||||||
|
>
|
||||||
|
option , selected.
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
id="aria-context"
|
||||||
|
>
|
||||||
|
2 results available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
aria-atomic="false"
|
||||||
|
aria-live="polite"
|
||||||
|
aria-relevant="additions text"
|
||||||
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="Select__control Select__control--is-focused Select__control--menu-is-open css-t3ipsp-control"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Select__value-container css-1fdsijx-ValueContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Select__placeholder css-1jqq78o-placeholder"
|
||||||
|
id="react-select-2-placeholder"
|
||||||
|
>
|
||||||
|
Remove hotbar
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="Select__input-container css-qbdosj-Input"
|
||||||
|
data-value=""
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-autocomplete="list"
|
||||||
|
aria-controls="react-select-2-listbox"
|
||||||
|
aria-describedby="react-select-2-placeholder"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-owns="react-select-2-listbox"
|
||||||
|
autocapitalize="none"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
class="Select__input"
|
||||||
|
id="react-select-2-input"
|
||||||
|
role="combobox"
|
||||||
|
spellcheck="false"
|
||||||
|
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
|
||||||
|
tabindex="0"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="Select__indicators css-1hb7zxy-IndicatorsContainer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="theme-dark Select__menu css-1nmdiq5-menu"
|
||||||
|
id="react-select-2-listbox"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Select__menu-list css-1n6sfyn-MenuList"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-disabled="false"
|
||||||
|
class="Select__option css-10wo9uf-option"
|
||||||
|
id="react-select-2-option-0"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
1: default
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
aria-disabled="false"
|
||||||
|
class="Select__option css-10wo9uf-option"
|
||||||
|
id="react-select-2-option-1"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
2: non-default
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@ -5,75 +5,56 @@
|
|||||||
|
|
||||||
import "@testing-library/jest-dom/extend-expect";
|
import "@testing-library/jest-dom/extend-expect";
|
||||||
import { HotbarRemoveCommand } from "../hotbar-remove-command";
|
import { HotbarRemoveCommand } from "../hotbar-remove-command";
|
||||||
|
import type { RenderResult } from "@testing-library/react";
|
||||||
import { fireEvent } from "@testing-library/react";
|
import { fireEvent } from "@testing-library/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
|
||||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||||
import type { DiRender } from "../../test-utils/renderFor";
|
|
||||||
import { renderFor } from "../../test-utils/renderFor";
|
import { renderFor } from "../../test-utils/renderFor";
|
||||||
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
|
|
||||||
import { ConfirmDialog } from "../../confirm-dialog";
|
import { ConfirmDialog } from "../../confirm-dialog";
|
||||||
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 type { HotbarStore } from "../../../../common/hotbars/store";
|
import hotbarsStateInjectable from "../../../../features/hotbar/storage/common/state.injectable";
|
||||||
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
|
import type { CreateHotbar } from "../../../../features/hotbar/storage/common/create-hotbar.injectable";
|
||||||
|
import createHotbarInjectable from "../../../../features/hotbar/storage/common/create-hotbar.injectable";
|
||||||
const mockHotbars: Partial<Record<string, any>> = {
|
import type { IComputedValue } from "mobx";
|
||||||
"1": {
|
import type { Hotbar } from "../../../../features/hotbar/storage/common/hotbar";
|
||||||
id: "1",
|
import hotbarsInjectable from "../../../../features/hotbar/storage/common/hotbars.injectable";
|
||||||
name: "Default",
|
|
||||||
items: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("<HotbarRemoveCommand />", () => {
|
describe("<HotbarRemoveCommand />", () => {
|
||||||
let di: DiContainer;
|
let result: RenderResult;
|
||||||
let render: DiRender;
|
let createHotbar: CreateHotbar;
|
||||||
|
let hotbars: IComputedValue<Hotbar[]>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
di = getDiForUnitTesting();
|
const di = getDiForUnitTesting();
|
||||||
|
const render = renderFor(di);
|
||||||
|
|
||||||
di.override(storesAndApisCanBeCreatedInjectable, () => true);
|
|
||||||
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
||||||
|
|
||||||
render = renderFor(di);
|
createHotbar = di.inject(createHotbarInjectable);
|
||||||
});
|
hotbars = di.inject(hotbarsInjectable);
|
||||||
|
|
||||||
it("renders w/o errors", () => {
|
const hotbarsState = di.inject(hotbarsStateInjectable);
|
||||||
di.override(hotbarStoreInjectable, () => ({
|
const defaultHotbar = createHotbar({ name: "default" });
|
||||||
hotbars: [mockHotbars["1"]],
|
const nonDefaultHotbar = createHotbar({ name: "non-default" });
|
||||||
getById: (id: string) => mockHotbars[id],
|
|
||||||
remove: () => {
|
|
||||||
},
|
|
||||||
hotbarIndex: () => 0,
|
|
||||||
getDisplayLabel: () => "1: Default",
|
|
||||||
}) as unknown as HotbarStore);
|
|
||||||
|
|
||||||
const { container } = render(<HotbarRemoveCommand />);
|
hotbarsState.set(defaultHotbar.id, defaultHotbar);
|
||||||
|
hotbarsState.set(nonDefaultHotbar.id, nonDefaultHotbar);
|
||||||
|
|
||||||
expect(container).toBeInstanceOf(HTMLElement);
|
result = render((
|
||||||
});
|
|
||||||
|
|
||||||
it("calls remove if you click on the entry", () => {
|
|
||||||
const removeMock = jest.fn();
|
|
||||||
|
|
||||||
di.override(hotbarStoreInjectable, () => ({
|
|
||||||
hotbars: [mockHotbars["1"]],
|
|
||||||
getById: (id: string) => mockHotbars[id],
|
|
||||||
remove: removeMock,
|
|
||||||
hotbarIndex: () => 0,
|
|
||||||
getDisplayLabel: () => "1: Default",
|
|
||||||
}) as unknown as HotbarStore);
|
|
||||||
|
|
||||||
const { getByText } = render(
|
|
||||||
<>
|
<>
|
||||||
<HotbarRemoveCommand />
|
<HotbarRemoveCommand />
|
||||||
<ConfirmDialog />
|
<ConfirmDialog />
|
||||||
</>,
|
</>
|
||||||
);
|
));
|
||||||
|
});
|
||||||
|
|
||||||
fireEvent.click(getByText("1: Default"));
|
it("renders w/o errors", () => {
|
||||||
fireEvent.click(getByText("Remove Hotbar"));
|
expect(result.container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
expect(removeMock).toHaveBeenCalled();
|
it("calls remove if you click on the entry", () => {
|
||||||
|
fireEvent.click(result.getByText("1: default"));
|
||||||
|
fireEvent.click(result.getByText("Remove Hotbar"));
|
||||||
|
expect(hotbars.get().length).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,15 +7,15 @@ import React from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { InputValidator } from "../input";
|
import type { InputValidator } from "../input";
|
||||||
import { Input } from "../input";
|
import { Input } from "../input";
|
||||||
import type { CreateHotbarData, CreateHotbarOptions } from "../../../common/hotbars/types";
|
|
||||||
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 uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable";
|
import uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable";
|
||||||
import addHotbarInjectable from "../../../common/hotbars/add-hotbar.injectable";
|
import type { AddHotbar } from "../../../features/hotbar/storage/common/add.injectable";
|
||||||
|
import addHotbarInjectable from "../../../features/hotbar/storage/common/add.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
closeCommandOverlay: () => void;
|
closeCommandOverlay: () => void;
|
||||||
addHotbar: (data: CreateHotbarData, opts: CreateHotbarOptions) => void;
|
addHotbar: AddHotbar;
|
||||||
uniqueHotbarName: InputValidator<boolean>;
|
uniqueHotbarName: InputValidator<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import "./hotbar-menu.scss";
|
import "./hotbar-menu.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { HotbarEntityIcon } from "./hotbar-entity-icon";
|
import { HotbarEntityIcon } from "./hotbar-entity-icon";
|
||||||
import type { IClassName } from "@k8slens/utilities";
|
import type { IClassName } from "@k8slens/utilities";
|
||||||
@ -16,57 +16,46 @@ import { DragDropContext, Draggable, Droppable, type DropResult } from "react-be
|
|||||||
import { HotbarSelector } from "./hotbar-selector";
|
import { HotbarSelector } from "./hotbar-selector";
|
||||||
import { HotbarCell } from "./hotbar-cell";
|
import { HotbarCell } from "./hotbar-cell";
|
||||||
import { HotbarIcon } from "./hotbar-icon";
|
import { HotbarIcon } from "./hotbar-icon";
|
||||||
import type { HotbarItem } from "../../../common/hotbars/types";
|
import type { HotbarItem } from "../../../features/hotbar/storage/common/types";
|
||||||
import { defaultHotbarCells } from "../../../common/hotbars/types";
|
import { defaultHotbarCells } from "../../../features/hotbar/storage/common/types";
|
||||||
import { action, makeObservable, observable } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import type { HotbarStore } from "../../../common/hotbars/store";
|
|
||||||
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
|
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
|
||||||
|
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
|
||||||
|
import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable";
|
||||||
|
|
||||||
export interface HotbarMenuProps {
|
export interface HotbarMenuProps {
|
||||||
className?: IClassName;
|
className?: IClassName;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
hotbarStore: HotbarStore;
|
activeHotbar: IComputedValue<Hotbar | undefined>;
|
||||||
entityRegistry: CatalogEntityRegistry;
|
entityRegistry: CatalogEntityRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
const NonInjectedHotbarMenu = observer((props: Dependencies & HotbarMenuProps) => {
|
||||||
class NonInjectedHotbarMenu extends React.Component<Dependencies & HotbarMenuProps> {
|
const {
|
||||||
@observable draggingOver = false;
|
activeHotbar,
|
||||||
|
entityRegistry,
|
||||||
|
className,
|
||||||
|
} = props;
|
||||||
|
|
||||||
constructor(props: Dependencies & HotbarMenuProps) {
|
const [draggingOver, setDraggingOver] = useState(false);
|
||||||
super(props);
|
const hotbar = activeHotbar.get();
|
||||||
makeObservable(this);
|
|
||||||
|
const getEntity = (item: HotbarItem | null) => {
|
||||||
|
if (!item) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
get hotbar() {
|
return entityRegistry.getById(item.entity.uid);
|
||||||
return this.props.hotbarStore.getActive();
|
};
|
||||||
}
|
const onDragStart = () => setDraggingOver(true);
|
||||||
|
const onDragEnd = (result: DropResult) => {
|
||||||
|
setDraggingOver(false);
|
||||||
|
|
||||||
getEntity(item: HotbarItem | null) {
|
|
||||||
const hotbar = this.props.hotbarStore.getActive();
|
|
||||||
|
|
||||||
if (!hotbar || !item) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.props.entityRegistry.getById(item.entity.uid) ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
onDragStart() {
|
|
||||||
this.draggingOver = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
onDragEnd(result: DropResult) {
|
|
||||||
const { source, destination } = result;
|
const { source, destination } = result;
|
||||||
|
|
||||||
this.draggingOver = false;
|
|
||||||
|
|
||||||
if (!destination) { // Dropped outside of the list
|
if (!destination) { // Dropped outside of the list
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -74,34 +63,26 @@ class NonInjectedHotbarMenu extends React.Component<Dependencies & HotbarMenuPro
|
|||||||
const from = parseInt(source.droppableId);
|
const from = parseInt(source.droppableId);
|
||||||
const to = parseInt(destination.droppableId);
|
const to = parseInt(destination.droppableId);
|
||||||
|
|
||||||
this.props.hotbarStore.restackItems(from, to);
|
hotbar?.restack(from, to);
|
||||||
}
|
|
||||||
|
|
||||||
removeItem = (uid: string) => {
|
|
||||||
const hotbar = this.props.hotbarStore;
|
|
||||||
|
|
||||||
hotbar.removeFromHotbar(uid);
|
|
||||||
};
|
};
|
||||||
|
const removeItem = (entityId: string) => {
|
||||||
addItem = (entity: CatalogEntity, index = -1) => {
|
hotbar?.removeEntity(entityId);
|
||||||
const hotbar = this.props.hotbarStore;
|
|
||||||
|
|
||||||
hotbar.addToHotbar(entity, index);
|
|
||||||
};
|
};
|
||||||
|
const addItem = (entity: CatalogEntity) => {
|
||||||
getMoveAwayDirection(entityId: string | undefined | null, cellIndex: number) {
|
hotbar?.addEntity(entity);
|
||||||
if (!entityId) {
|
};
|
||||||
|
const getMoveAwayDirection = (entityId: string | undefined | null, cellIndex: number) => {
|
||||||
|
if (!entityId || !hotbar) {
|
||||||
return "animateDown";
|
return "animateDown";
|
||||||
}
|
}
|
||||||
|
|
||||||
const draggableItemIndex = this.hotbar.items.findIndex(item => item?.entity.uid == entityId);
|
const draggableItemIndex = hotbar.items.findIndex(item => item?.entity.uid == entityId);
|
||||||
|
|
||||||
return draggableItemIndex > cellIndex ? "animateDown" : "animateUp";
|
return draggableItemIndex > cellIndex ? "animateDown" : "animateUp";
|
||||||
}
|
};
|
||||||
|
|
||||||
renderGrid() {
|
const renderGrid = () => hotbar?.items.map((item, index) => {
|
||||||
return this.hotbar.items.map((item, index) => {
|
const entity = getEntity(item);
|
||||||
const entity = this.getEntity(item);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Droppable droppableId={`${index}`} key={index}>
|
<Droppable droppableId={`${index}`} key={index}>
|
||||||
@ -115,7 +96,7 @@ class NonInjectedHotbarMenu extends React.Component<Dependencies & HotbarMenuPro
|
|||||||
isDraggingOver: snapshot.isDraggingOver,
|
isDraggingOver: snapshot.isDraggingOver,
|
||||||
isDraggingOwner: snapshot.draggingOverWith == entity?.getId(),
|
isDraggingOwner: snapshot.draggingOverWith == entity?.getId(),
|
||||||
},
|
},
|
||||||
this.getMoveAwayDirection(snapshot.draggingOverWith, index),
|
getMoveAwayDirection(snapshot.draggingOverWith, index),
|
||||||
)}
|
)}
|
||||||
{...provided.droppableProps}
|
{...provided.droppableProps}
|
||||||
>
|
>
|
||||||
@ -125,32 +106,28 @@ class NonInjectedHotbarMenu extends React.Component<Dependencies & HotbarMenuPro
|
|||||||
key={item.entity.uid}
|
key={item.entity.uid}
|
||||||
index={0}
|
index={0}
|
||||||
>
|
>
|
||||||
{(provided, snapshot) => {
|
{(provided, snapshot) => (
|
||||||
const style = {
|
|
||||||
zIndex: defaultHotbarCells - index,
|
|
||||||
position: "absolute",
|
|
||||||
...provided.draggableProps.style,
|
|
||||||
} as React.CSSProperties;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
<div
|
||||||
key={item.entity.uid}
|
key={item.entity.uid}
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
{...provided.dragHandleProps}
|
{...provided.dragHandleProps}
|
||||||
style={style}
|
style={{
|
||||||
|
zIndex: defaultHotbarCells - index,
|
||||||
|
position: "absolute" as const,
|
||||||
|
...provided.draggableProps.style,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{entity ? (
|
{entity ? (
|
||||||
<HotbarEntityIcon
|
<HotbarEntityIcon
|
||||||
key={index}
|
key={index}
|
||||||
index={index}
|
index={index}
|
||||||
entity={entity}
|
entity={entity}
|
||||||
onClick={() => this.props.entityRegistry.onRun(entity)}
|
onClick={() => entityRegistry.onRun(entity)}
|
||||||
className={cssNames({ isDragging: snapshot.isDragging })}
|
className={cssNames({ isDragging: snapshot.isDragging })}
|
||||||
remove={this.removeItem}
|
remove={removeItem}
|
||||||
add={this.addItem}
|
add={addItem}
|
||||||
size={40}
|
size={40} />
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<HotbarIcon
|
<HotbarIcon
|
||||||
uid={`hotbar-icon-${item.entity.uid}`}
|
uid={`hotbar-icon-${item.entity.uid}`}
|
||||||
@ -160,16 +137,14 @@ class NonInjectedHotbarMenu extends React.Component<Dependencies & HotbarMenuPro
|
|||||||
menuItems={[
|
menuItems={[
|
||||||
{
|
{
|
||||||
title: "Remove from Hotbar",
|
title: "Remove from Hotbar",
|
||||||
onClick: () => this.removeItem(item.entity.uid),
|
onClick: () => removeItem(item.entity.uid),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
disabled
|
disabled
|
||||||
size={40}
|
size={40} />
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
)}
|
||||||
}}
|
|
||||||
</Draggable>
|
</Draggable>
|
||||||
)}
|
)}
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
@ -178,31 +153,25 @@ class NonInjectedHotbarMenu extends React.Component<Dependencies & HotbarMenuPro
|
|||||||
</Droppable>
|
</Droppable>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { className, hotbarStore } = this.props;
|
|
||||||
const hotbar = hotbarStore.getActive();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("HotbarMenu flex column", { draggingOver: this.draggingOver }, className)}>
|
<div className={cssNames("HotbarMenu flex column", { draggingOver }, className)}>
|
||||||
<div className="HotbarItems flex column gaps">
|
<div className="HotbarItems flex column gaps">
|
||||||
<DragDropContext
|
<DragDropContext
|
||||||
onDragStart={() => this.onDragStart()}
|
onDragStart={() => onDragStart()}
|
||||||
onDragEnd={(result) => this.onDragEnd(result)}>
|
onDragEnd={(result) => onDragEnd(result)}>
|
||||||
{this.renderGrid()}
|
{renderGrid()}
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
</div>
|
</div>
|
||||||
<HotbarSelector hotbar={hotbar} />
|
<HotbarSelector />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
export const HotbarMenu = withInjectables<Dependencies, HotbarMenuProps>(NonInjectedHotbarMenu, {
|
export const HotbarMenu = withInjectables<Dependencies, HotbarMenuProps>(NonInjectedHotbarMenu, {
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
...props,
|
...props,
|
||||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
|
||||||
entityRegistry: di.inject(catalogEntityRegistryInjectable),
|
entityRegistry: di.inject(catalogEntityRegistryInjectable),
|
||||||
|
activeHotbar: di.inject(activeHotbarInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,23 +6,32 @@
|
|||||||
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 from "../../../common/hotbars/store.injectable";
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
||||||
import type { HotbarStore } from "../../../common/hotbars/store";
|
|
||||||
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
|
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
|
||||||
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
|
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
|
||||||
|
import type { ComputeHotbarDisplayLabel } from "../../../features/hotbar/storage/common/compute-display-label.injectable";
|
||||||
|
import computeHotbarDisplayLabelInjectable from "../../../features/hotbar/storage/common/compute-display-label.injectable";
|
||||||
|
import hotbarsInjectable from "../../../features/hotbar/storage/common/hotbars.injectable";
|
||||||
|
import type { RemoveHotbar } from "../../../features/hotbar/storage/common/remove.injectable";
|
||||||
|
import removeHotbarInjectable from "../../../features/hotbar/storage/common/remove.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
closeCommandOverlay: () => void;
|
closeCommandOverlay: () => void;
|
||||||
openConfirmDialog: OpenConfirmDialog;
|
openConfirmDialog: OpenConfirmDialog;
|
||||||
hotbarStore: HotbarStore;
|
hotbars: IComputedValue<Hotbar[]>;
|
||||||
|
computeHotbarDisplayLabel: ComputeHotbarDisplayLabel;
|
||||||
|
removeHotbar: RemoveHotbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedHotbarRemoveCommand = observer(({
|
const NonInjectedHotbarRemoveCommand = observer(({
|
||||||
closeCommandOverlay,
|
closeCommandOverlay,
|
||||||
hotbarStore,
|
|
||||||
openConfirmDialog,
|
openConfirmDialog,
|
||||||
|
hotbars,
|
||||||
|
computeHotbarDisplayLabel,
|
||||||
|
removeHotbar,
|
||||||
}: Dependencies) => (
|
}: Dependencies) => (
|
||||||
<Select
|
<Select
|
||||||
menuPortalTarget={null}
|
menuPortalTarget={null}
|
||||||
@ -38,14 +47,14 @@ const NonInjectedHotbarRemoveCommand = observer(({
|
|||||||
primary: false,
|
primary: false,
|
||||||
accent: true,
|
accent: true,
|
||||||
},
|
},
|
||||||
ok: () => hotbarStore.remove(option.value),
|
ok: () => removeHotbar(option.value),
|
||||||
message: (
|
message: (
|
||||||
<div className="confirm flex column gaps">
|
<div className="confirm flex column gaps">
|
||||||
<p>
|
<p>
|
||||||
Are you sure you want remove hotbar
|
Are you sure you want remove hotbar
|
||||||
{" "}
|
{" "}
|
||||||
<b>
|
<b>
|
||||||
{option.value.name}
|
{option.value.name.get()}
|
||||||
</b>
|
</b>
|
||||||
?
|
?
|
||||||
</p>
|
</p>
|
||||||
@ -56,10 +65,10 @@ const NonInjectedHotbarRemoveCommand = observer(({
|
|||||||
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||||
menuIsOpen={true}
|
menuIsOpen={true}
|
||||||
options={(
|
options={(
|
||||||
hotbarStore.hotbars
|
hotbars.get()
|
||||||
.map(hotbar => ({
|
.map(hotbar => ({
|
||||||
value: hotbar,
|
value: hotbar,
|
||||||
label: hotbarStore.getDisplayLabel(hotbar),
|
label: computeHotbarDisplayLabel(hotbar),
|
||||||
}))
|
}))
|
||||||
)}
|
)}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
@ -72,7 +81,9 @@ export const HotbarRemoveCommand = withInjectables<Dependencies>(NonInjectedHotb
|
|||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
...props,
|
...props,
|
||||||
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
|
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
|
||||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
|
||||||
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
||||||
|
computeHotbarDisplayLabel: di.inject(computeHotbarDisplayLabelInjectable),
|
||||||
|
hotbars: di.inject(hotbarsInjectable),
|
||||||
|
removeHotbar: di.inject(removeHotbarInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,36 +6,46 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Select } from "../select";
|
import { Select } from "../select";
|
||||||
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
|
||||||
import type { InputValidator } from "../input";
|
import type { InputValidator } from "../input";
|
||||||
import { Input } from "../input";
|
import { Input } from "../input";
|
||||||
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 uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable";
|
import uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable";
|
||||||
import type { HotbarStore } from "../../../common/hotbars/store";
|
import type { IComputedValue } from "mobx";
|
||||||
|
import { action } from "mobx";
|
||||||
|
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
|
||||||
|
import type { GetHotbarById } from "../../../features/hotbar/storage/common/get-by-id.injectable";
|
||||||
|
import getHotbarByIdInjectable from "../../../features/hotbar/storage/common/get-by-id.injectable";
|
||||||
|
import hotbarsInjectable from "../../../features/hotbar/storage/common/hotbars.injectable";
|
||||||
|
import type { ComputeHotbarDisplayLabel } from "../../../features/hotbar/storage/common/compute-display-label.injectable";
|
||||||
|
import computeHotbarDisplayLabelInjectable from "../../../features/hotbar/storage/common/compute-display-label.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
closeCommandOverlay: () => void;
|
closeCommandOverlay: () => void;
|
||||||
hotbarStore: HotbarStore;
|
getHotbarById: GetHotbarById;
|
||||||
|
computeHotbarDisplayLabel: ComputeHotbarDisplayLabel;
|
||||||
uniqueHotbarName: InputValidator<false>;
|
uniqueHotbarName: InputValidator<false>;
|
||||||
|
hotbars: IComputedValue<Hotbar[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedHotbarRenameCommand = observer(({
|
const NonInjectedHotbarRenameCommand = observer(({
|
||||||
closeCommandOverlay,
|
closeCommandOverlay,
|
||||||
hotbarStore,
|
getHotbarById,
|
||||||
|
computeHotbarDisplayLabel,
|
||||||
uniqueHotbarName,
|
uniqueHotbarName,
|
||||||
|
hotbars,
|
||||||
}: Dependencies) => {
|
}: Dependencies) => {
|
||||||
const [hotbarId, setHotbarId] = useState("");
|
const [hotbarId, setHotbarId] = useState("");
|
||||||
const [hotbarName, setHotbarName] = useState("");
|
const [hotbarName, setHotbarName] = useState("");
|
||||||
|
|
||||||
const onSubmit = (name: string) => {
|
const onSubmit = action((name: string) => {
|
||||||
if (!name.trim()) {
|
if (!name.trim()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
hotbarStore.setHotbarName(hotbarId, name);
|
getHotbarById(hotbarId)?.name.set(name);
|
||||||
closeCommandOverlay();
|
closeCommandOverlay();
|
||||||
};
|
});
|
||||||
|
|
||||||
if (hotbarId) {
|
if (hotbarId) {
|
||||||
return (
|
return (
|
||||||
@ -65,16 +75,16 @@ const NonInjectedHotbarRenameCommand = observer(({
|
|||||||
onChange={(option) => {
|
onChange={(option) => {
|
||||||
if (option) {
|
if (option) {
|
||||||
setHotbarId(option.value.id);
|
setHotbarId(option.value.id);
|
||||||
setHotbarName(option.value.name);
|
setHotbarName(option.value.name.get());
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||||
menuIsOpen={true}
|
menuIsOpen={true}
|
||||||
options={(
|
options={(
|
||||||
hotbarStore.hotbars
|
hotbars.get()
|
||||||
.map(hotbar => ({
|
.map(hotbar => ({
|
||||||
value: hotbar,
|
value: hotbar,
|
||||||
label: hotbarStore.getDisplayLabel(hotbar),
|
label: computeHotbarDisplayLabel(hotbar),
|
||||||
}))
|
}))
|
||||||
)}
|
)}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
@ -86,9 +96,11 @@ const NonInjectedHotbarRenameCommand = observer(({
|
|||||||
|
|
||||||
export const HotbarRenameCommand = withInjectables<Dependencies>(NonInjectedHotbarRenameCommand, {
|
export const HotbarRenameCommand = withInjectables<Dependencies>(NonInjectedHotbarRenameCommand, {
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
|
|
||||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
|
||||||
uniqueHotbarName: di.inject(uniqueHotbarNameInjectable),
|
|
||||||
...props,
|
...props,
|
||||||
|
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
|
||||||
|
uniqueHotbarName: di.inject(uniqueHotbarNameInjectable),
|
||||||
|
getHotbarById: di.inject(getHotbarByIdInjectable),
|
||||||
|
hotbars: di.inject(hotbarsInjectable),
|
||||||
|
computeHotbarDisplayLabel: di.inject(computeHotbarDisplayLabelInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,28 +7,40 @@ import styles from "./hotbar-selector.module.scss";
|
|||||||
import React, { useRef, useState } from "react";
|
import React, { useRef, useState } from "react";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
|
||||||
import { HotbarSwitchCommand } from "./hotbar-switch-command";
|
import { HotbarSwitchCommand } from "./hotbar-switch-command";
|
||||||
import { Tooltip, TooltipPosition } from "../tooltip";
|
import { Tooltip, TooltipPosition } from "../tooltip";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { Hotbar } from "../../../common/hotbars/types";
|
|
||||||
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 { cssNames } from "@k8slens/utilities";
|
import { cssNames } from "@k8slens/utilities";
|
||||||
import type { HotbarStore } from "../../../common/hotbars/store";
|
import type { IComputedValue } from "mobx";
|
||||||
|
import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable";
|
||||||
|
import type { SwitchToPreviousHotbar } from "../../../features/hotbar/storage/common/switch-to-previous.injectable";
|
||||||
|
import type { SwitchToNextHotbar } from "../../../features/hotbar/storage/common/switch-to-next.injectable";
|
||||||
|
import switchToNextHotbarInjectable from "../../../features/hotbar/storage/common/switch-to-next.injectable";
|
||||||
|
import switchToPreviousHotbarInjectable from "../../../features/hotbar/storage/common/switch-to-previous.injectable";
|
||||||
|
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
|
||||||
|
import type { ComputeDisplayIndex } from "../../../features/hotbar/storage/common/compute-display-index.injectable";
|
||||||
|
import computeDisplayIndexInjectable from "../../../features/hotbar/storage/common/compute-display-index.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
hotbarStore: HotbarStore;
|
activeHotbar: IComputedValue<Hotbar | undefined>;
|
||||||
openCommandOverlay: (component: React.ReactElement) => void;
|
openCommandOverlay: (component: React.ReactElement) => void;
|
||||||
|
switchToPreviousHotbar: SwitchToPreviousHotbar;
|
||||||
|
switchToNextHotbar: SwitchToNextHotbar;
|
||||||
|
computeDisplayIndex: ComputeDisplayIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HotbarSelectorProps extends Partial<Dependencies> {
|
const NonInjectedHotbarSelector = observer(({
|
||||||
hotbar: Hotbar;
|
activeHotbar,
|
||||||
}
|
openCommandOverlay,
|
||||||
|
switchToNextHotbar,
|
||||||
const NonInjectedHotbarSelector = observer(({ hotbar, hotbarStore, openCommandOverlay }: HotbarSelectorProps & Dependencies) => {
|
switchToPreviousHotbar,
|
||||||
|
computeDisplayIndex,
|
||||||
|
}: Dependencies) => {
|
||||||
const [tooltipVisible, setTooltipVisible] = useState(false);
|
const [tooltipVisible, setTooltipVisible] = useState(false);
|
||||||
const tooltipTimeout = useRef<number>();
|
const tooltipTimeout = useRef<number>();
|
||||||
|
const hotbar = activeHotbar.get();
|
||||||
|
|
||||||
function clearTimer() {
|
function clearTimer() {
|
||||||
clearTimeout(tooltipTimeout.current);
|
clearTimeout(tooltipTimeout.current);
|
||||||
@ -42,12 +54,12 @@ const NonInjectedHotbarSelector = observer(({ hotbar, hotbarStore, openCommandOv
|
|||||||
|
|
||||||
function onPrevClick() {
|
function onPrevClick() {
|
||||||
onTooltipShow();
|
onTooltipShow();
|
||||||
hotbarStore.switchToPrevious();
|
switchToPreviousHotbar();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onNextClick() {
|
function onNextClick() {
|
||||||
onTooltipShow();
|
onTooltipShow();
|
||||||
hotbarStore.switchToNext();
|
switchToNextHotbar();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseEvent(event: React.MouseEvent) {
|
function onMouseEvent(event: React.MouseEvent) {
|
||||||
@ -65,7 +77,7 @@ const NonInjectedHotbarSelector = observer(({ hotbar, hotbarStore, openCommandOv
|
|||||||
<Badge
|
<Badge
|
||||||
id="hotbarIndex"
|
id="hotbarIndex"
|
||||||
small
|
small
|
||||||
label={hotbarStore.getDisplayIndex(hotbarStore.getActive())}
|
label={hotbar ? computeDisplayIndex(hotbar.id) : "??"}
|
||||||
onClick={() => openCommandOverlay(<HotbarSwitchCommand />)}
|
onClick={() => openCommandOverlay(<HotbarSwitchCommand />)}
|
||||||
className={styles.Badge}
|
className={styles.Badge}
|
||||||
onMouseEnter={onMouseEvent}
|
onMouseEnter={onMouseEvent}
|
||||||
@ -76,7 +88,7 @@ const NonInjectedHotbarSelector = observer(({ hotbar, hotbarStore, openCommandOv
|
|||||||
targetId="hotbarIndex"
|
targetId="hotbarIndex"
|
||||||
preferredPositions={[TooltipPosition.TOP, TooltipPosition.TOP_LEFT]}
|
preferredPositions={[TooltipPosition.TOP, TooltipPosition.TOP_LEFT]}
|
||||||
>
|
>
|
||||||
{hotbar.name}
|
{hotbar?.name}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Icon
|
<Icon
|
||||||
@ -88,10 +100,13 @@ const NonInjectedHotbarSelector = observer(({ hotbar, hotbarStore, openCommandOv
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const HotbarSelector = withInjectables<Dependencies, HotbarSelectorProps>(NonInjectedHotbarSelector, {
|
export const HotbarSelector = withInjectables<Dependencies>(NonInjectedHotbarSelector, {
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
|
||||||
openCommandOverlay: di.inject(commandOverlayInjectable).open,
|
|
||||||
...props,
|
...props,
|
||||||
|
openCommandOverlay: di.inject(commandOverlayInjectable).open,
|
||||||
|
activeHotbar: di.inject(activeHotbarInjectable),
|
||||||
|
switchToNextHotbar: di.inject(switchToNextHotbarInjectable),
|
||||||
|
switchToPreviousHotbar: di.inject(switchToPreviousHotbarInjectable),
|
||||||
|
computeDisplayIndex: di.inject(computeDisplayIndexInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,21 +6,28 @@
|
|||||||
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 from "../../../common/hotbars/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/hotbars/store";
|
import type { SetAsActiveHotbar } from "../../../features/hotbar/storage/common/set-as-active.injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
|
||||||
|
import type { ComputeHotbarDisplayLabel } from "../../../features/hotbar/storage/common/compute-display-label.injectable";
|
||||||
|
import computeHotbarDisplayLabelInjectable from "../../../features/hotbar/storage/common/compute-display-label.injectable";
|
||||||
|
import hotbarsInjectable from "../../../features/hotbar/storage/common/hotbars.injectable";
|
||||||
|
import setAsActiveHotbarInjectable from "../../../features/hotbar/storage/common/set-as-active.injectable";
|
||||||
|
|
||||||
const hotbarAddAction = Symbol("hotbar-add");
|
const hotbarAddAction = Symbol("hotbar-add");
|
||||||
const hotbarRemoveAction = Symbol("hotbar-remove");
|
const hotbarRemoveAction = Symbol("hotbar-remove");
|
||||||
const hotbarRenameAction = Symbol("hotbar-rename");
|
const hotbarRenameAction = Symbol("hotbar-rename");
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
hotbarStore: HotbarStore;
|
setAsActiveHotbar: SetAsActiveHotbar;
|
||||||
|
computeHotbarDisplayLabel: ComputeHotbarDisplayLabel;
|
||||||
|
hotbars: IComputedValue<Hotbar[]>;
|
||||||
commandOverlay: CommandOverlay;
|
commandOverlay: CommandOverlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +36,9 @@ function ignoreIf<T>(check: boolean, menuItems: T[]): T[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedHotbarSwitchCommand = observer(({
|
const NonInjectedHotbarSwitchCommand = observer(({
|
||||||
hotbarStore,
|
setAsActiveHotbar,
|
||||||
|
computeHotbarDisplayLabel,
|
||||||
|
hotbars,
|
||||||
commandOverlay,
|
commandOverlay,
|
||||||
}: Dependencies) => (
|
}: Dependencies) => (
|
||||||
<Select
|
<Select
|
||||||
@ -50,22 +59,22 @@ const NonInjectedHotbarSwitchCommand = observer(({
|
|||||||
return commandOverlay.open(<HotbarRenameCommand />);
|
return commandOverlay.open(<HotbarRenameCommand />);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hotbarStore.setActiveHotbar(option.value);
|
setAsActiveHotbar(option.value);
|
||||||
commandOverlay.close();
|
commandOverlay.close();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||||
menuIsOpen={true}
|
menuIsOpen={true}
|
||||||
options={[
|
options={[
|
||||||
...hotbarStore.hotbars.map(hotbar => ({
|
...hotbars.get().map(hotbar => ({
|
||||||
value: hotbar,
|
value: hotbar,
|
||||||
label: hotbarStore.getDisplayLabel(hotbar),
|
label: computeHotbarDisplayLabel(hotbar),
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
value: hotbarAddAction,
|
value: hotbarAddAction,
|
||||||
label: "Add hotbar ...",
|
label: "Add hotbar ...",
|
||||||
},
|
},
|
||||||
...ignoreIf(hotbarStore.hotbars.length > 1, [
|
...ignoreIf(hotbars.get().length > 1, [
|
||||||
{
|
{
|
||||||
value: hotbarRemoveAction,
|
value: hotbarRemoveAction,
|
||||||
label: "Remove hotbar ...",
|
label: "Remove hotbar ...",
|
||||||
@ -85,8 +94,10 @@ const NonInjectedHotbarSwitchCommand = observer(({
|
|||||||
|
|
||||||
export const HotbarSwitchCommand = withInjectables<Dependencies>(NonInjectedHotbarSwitchCommand, {
|
export const HotbarSwitchCommand = withInjectables<Dependencies>(NonInjectedHotbarSwitchCommand, {
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
|
||||||
commandOverlay: di.inject(commandOverlayInjectable),
|
|
||||||
...props,
|
...props,
|
||||||
|
commandOverlay: di.inject(commandOverlayInjectable),
|
||||||
|
computeHotbarDisplayLabel: di.inject(computeHotbarDisplayLabelInjectable),
|
||||||
|
hotbars: di.inject(hotbarsInjectable),
|
||||||
|
setAsActiveHotbar: di.inject(setAsActiveHotbarInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,19 +4,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
|
import findHotbarByNameInjectable from "../../../../features/hotbar/storage/common/find-by-name.injectable";
|
||||||
import { inputValidator } from "../input_validators";
|
import { inputValidator } from "../input_validators";
|
||||||
|
|
||||||
const uniqueHotbarNameInjectable = getInjectable({
|
const uniqueHotbarNameInjectable = getInjectable({
|
||||||
id: "unique-hotbar-name",
|
id: "unique-hotbar-name",
|
||||||
|
|
||||||
instantiate: di => {
|
instantiate: di => {
|
||||||
const store = di.inject(hotbarStoreInjectable);
|
const findHotbarByName = di.inject(findHotbarByNameInjectable);
|
||||||
|
|
||||||
return inputValidator({
|
return inputValidator({
|
||||||
condition: ({ required }) => required,
|
condition: ({ required }) => required,
|
||||||
message: () => "Hotbar with this name already exists",
|
message: () => "Hotbar with this name already exists",
|
||||||
validate: value => !store.findByName(value),
|
validate: value => !findHotbarByName(value),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<SidebarCluster/> renders w/o errors 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="SidebarCluster"
|
||||||
|
data-testid="sidebar-cluster-dropdown"
|
||||||
|
id="cluster-test-uid"
|
||||||
|
role="menubar"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded avatar"
|
||||||
|
style="width: 40px; height: 40px; background: rgb(96, 130, 10);"
|
||||||
|
>
|
||||||
|
tc
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="clusterName"
|
||||||
|
id="tooltip-cluster-test-uid"
|
||||||
|
>
|
||||||
|
test-cluster
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
class="Icon dropdown material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_drop_down"
|
||||||
|
>
|
||||||
|
arrow_drop_down
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@ -5,14 +5,19 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import "@testing-library/jest-dom/extend-expect";
|
import "@testing-library/jest-dom/extend-expect";
|
||||||
|
import type { RenderResult } from "@testing-library/react";
|
||||||
import { 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";
|
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||||
import type { DiRender } from "../../test-utils/renderFor";
|
|
||||||
import { renderFor } from "../../test-utils/renderFor";
|
import { renderFor } from "../../test-utils/renderFor";
|
||||||
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
|
|
||||||
import type { HotbarStore } from "../../../../common/hotbars/store";
|
describe("<SidebarCluster/>", () => {
|
||||||
|
let result: RenderResult;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const di = getDiForUnitTesting();
|
||||||
|
const render = renderFor(di);
|
||||||
|
|
||||||
const clusterEntity = new KubernetesCluster({
|
const clusterEntity = new KubernetesCluster({
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -30,31 +35,17 @@ const clusterEntity = new KubernetesCluster({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("<SidebarCluster/>", () => {
|
result = render(<SidebarCluster clusterEntity={clusterEntity}/>);
|
||||||
let render: DiRender;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const di = getDiForUnitTesting();
|
|
||||||
|
|
||||||
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}/>);
|
expect(result.container).toMatchSnapshot();
|
||||||
|
|
||||||
expect(container).toBeInstanceOf(HTMLElement);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders cluster avatar and name", () => {
|
it("renders cluster avatar and name", () => {
|
||||||
const { getByText, getAllByText } = render(<SidebarCluster clusterEntity={clusterEntity}/>);
|
expect(result.getByText("tc")).toBeInTheDocument();
|
||||||
|
|
||||||
expect(getByText("tc")).toBeInTheDocument();
|
const v = result.getAllByText("test-cluster");
|
||||||
|
|
||||||
const v = getAllByText("test-cluster");
|
|
||||||
|
|
||||||
expect(v.length).toBeGreaterThan(0);
|
expect(v.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
@ -64,11 +55,8 @@ describe("<SidebarCluster/>", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("renders cluster menu", () => {
|
it("renders cluster menu", () => {
|
||||||
const { getByTestId, getByText } = render(<SidebarCluster clusterEntity={clusterEntity}/>);
|
fireEvent.click(result.getByTestId("sidebar-cluster-dropdown"));
|
||||||
const link = getByTestId("sidebar-cluster-dropdown");
|
expect(result.getByText("Add to Hotbar")).toBeInTheDocument();
|
||||||
|
|
||||||
fireEvent.click(link);
|
|
||||||
expect(getByText("Add to Hotbar")).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -14,8 +14,6 @@ import { Icon } from "../icon";
|
|||||||
import { Menu, MenuItem } from "../menu";
|
import { Menu, MenuItem } from "../menu";
|
||||||
import { Tooltip } from "../tooltip";
|
import { Tooltip } from "../tooltip";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
|
|
||||||
import type { HotbarStore } from "../../../common/hotbars/store";
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { VisitEntityContextMenu } from "../../../common/catalog/visit-entity-context-menu.injectable";
|
import type { VisitEntityContextMenu } from "../../../common/catalog/visit-entity-context-menu.injectable";
|
||||||
import visitEntityContextMenuInjectable from "../../../common/catalog/visit-entity-context-menu.injectable";
|
import visitEntityContextMenuInjectable from "../../../common/catalog/visit-entity-context-menu.injectable";
|
||||||
@ -23,6 +21,8 @@ import type { Navigate } from "../../navigation/navigate.injectable";
|
|||||||
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
|
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
|
||||||
import navigateInjectable from "../../navigation/navigate.injectable";
|
import navigateInjectable from "../../navigation/navigate.injectable";
|
||||||
import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable";
|
import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable";
|
||||||
|
import type { ActiveHotbarModel } from "../../../features/hotbar/storage/common/toggling.injectable";
|
||||||
|
import activeHotbarInjectable from "../../../features/hotbar/storage/common/toggling.injectable";
|
||||||
|
|
||||||
export interface SidebarClusterProps {
|
export interface SidebarClusterProps {
|
||||||
clusterEntity: CatalogEntity | null | undefined;
|
clusterEntity: CatalogEntity | null | undefined;
|
||||||
@ -31,16 +31,16 @@ export interface SidebarClusterProps {
|
|||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
navigate: Navigate;
|
navigate: Navigate;
|
||||||
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
|
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
|
||||||
hotbarStore: HotbarStore;
|
|
||||||
visitEntityContextMenu: VisitEntityContextMenu;
|
visitEntityContextMenu: VisitEntityContextMenu;
|
||||||
|
entityInActiveHotbar: ActiveHotbarModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedSidebarCluster = observer(({
|
const NonInjectedSidebarCluster = observer(({
|
||||||
clusterEntity,
|
clusterEntity,
|
||||||
hotbarStore,
|
|
||||||
visitEntityContextMenu: onContextMenuOpen,
|
visitEntityContextMenu: onContextMenuOpen,
|
||||||
navigate,
|
navigate,
|
||||||
normalizeMenuItem,
|
normalizeMenuItem,
|
||||||
|
entityInActiveHotbar,
|
||||||
}: Dependencies & SidebarClusterProps) => {
|
}: Dependencies & SidebarClusterProps) => {
|
||||||
const [menuItems] = useState(observable.array<CatalogEntityContextMenu>());
|
const [menuItems] = useState(observable.array<CatalogEntityContextMenu>());
|
||||||
const [opened, setOpened] = useState(false );
|
const [opened, setOpened] = useState(false );
|
||||||
@ -61,13 +61,10 @@ const NonInjectedSidebarCluster = observer(({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onMenuOpen = () => {
|
const onMenuOpen = () => {
|
||||||
const isAddedToActive = hotbarStore.isAddedToActive(clusterEntity);
|
const title = entityInActiveHotbar.hasEntity(clusterEntity.getId())
|
||||||
const title = isAddedToActive
|
|
||||||
? "Remove from Hotbar"
|
? "Remove from Hotbar"
|
||||||
: "Add to Hotbar";
|
: "Add to Hotbar";
|
||||||
const onClick = isAddedToActive
|
const onClick = () => entityInActiveHotbar.toggleEntity(clusterEntity);
|
||||||
? () => hotbarStore.removeFromHotbar(clusterEntity.getId())
|
|
||||||
: () => hotbarStore.addToHotbar(clusterEntity);
|
|
||||||
|
|
||||||
menuItems.replace([{ title, onClick }]);
|
menuItems.replace([{ title, onClick }]);
|
||||||
onContextMenuOpen(clusterEntity, {
|
onContextMenuOpen(clusterEntity, {
|
||||||
@ -148,9 +145,9 @@ const NonInjectedSidebarCluster = observer(({
|
|||||||
export const SidebarCluster = withInjectables<Dependencies, SidebarClusterProps>(NonInjectedSidebarCluster, {
|
export const SidebarCluster = withInjectables<Dependencies, SidebarClusterProps>(NonInjectedSidebarCluster, {
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
...props,
|
...props,
|
||||||
hotbarStore: di.inject(hotbarStoreInjectable),
|
|
||||||
visitEntityContextMenu: di.inject(visitEntityContextMenuInjectable),
|
visitEntityContextMenu: di.inject(visitEntityContextMenuInjectable),
|
||||||
navigate: di.inject(navigateInjectable),
|
navigate: di.inject(navigateInjectable),
|
||||||
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
|
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
|
||||||
|
entityInActiveHotbar: di.inject(activeHotbarInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { defaultHotbarCells } from "../../common/hotbars/types";
|
import { defaultHotbarCells } from "../../features/hotbar/storage/common/types";
|
||||||
import { clusterListNamespaceForbiddenChannel } from "../../common/ipc/cluster";
|
import { clusterListNamespaceForbiddenChannel } from "../../common/ipc/cluster";
|
||||||
import { hotbarTooManyItemsChannel } from "../../common/ipc/hotbar";
|
import { hotbarTooManyItemsChannel } from "../../common/ipc/hotbar";
|
||||||
import showErrorNotificationInjectable from "../components/notifications/show-error-notification.injectable";
|
import showErrorNotificationInjectable from "../components/notifications/show-error-notification.injectable";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user