1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Make legacy unit tests for hotbar green and more simple by using the new legacy helper

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
Iku-turso 2022-03-22 15:14:39 +02:00 committed by Janne Savolainen
parent b935dbca78
commit eb756f4da4
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
3 changed files with 656 additions and 572 deletions

View File

@ -6,14 +6,17 @@
import { anyObject } from "jest-mock-extended";
import mockFs from "mock-fs";
import logger from "../../main/logger";
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
import type {
CatalogEntity,
CatalogEntityData,
CatalogEntityKindData,
} from "../catalog";
import { HotbarStore } from "../hotbar-store";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import writeFileInjectable from "../fs/write-file.injectable";
import hotbarStoreInjectable from "../hotbar-store.injectable";
import catalogCatalogEntityInjectable
from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
jest.mock("../../main/catalog/catalog-entity-registry", () => ({
catalogEntityRegistry: {
@ -21,9 +24,11 @@ jest.mock("../../main/catalog/catalog-entity-registry", () => ({
getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "1dfa26e2ebab15780a3547e9c7fa785c",
name: "mycluster",
@ -31,12 +36,15 @@ jest.mock("../../main/catalog/catalog-entity-registry", () => ({
labels: {},
},
}),
getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "55b42c3c7ba3b04193416cda405269a5",
name: "my_shiny_cluster",
@ -44,12 +52,15 @@ jest.mock("../../main/catalog/catalog-entity-registry", () => ({
labels: {},
},
}),
getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "catalog-entity",
name: "Catalog",
@ -61,7 +72,9 @@ jest.mock("../../main/catalog/catalog-entity-registry", () => ({
},
}));
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
function getMockCatalogEntity(
data: Partial<CatalogEntityData> & CatalogEntityKindData,
): CatalogEntity {
return {
getName: jest.fn(() => data.metadata?.name),
getId: jest.fn(() => data.metadata?.uid),
@ -116,275 +129,303 @@ const awsCluster = getMockCatalogEntity({
});
describe("HotbarStore", () => {
let di: DiContainer;
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(writeFileInjectable, () => () => undefined);
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
// @ts-ignore
di.permitSideEffects(hotbarStoreInjectable);
di.override(hotbarStoreInjectable, (di) =>
HotbarStore.createInstance({
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
}),
);
// @ts-ignore
di.permitSideEffects(getConfigurationFileModelInjectable);
mockFs({
"some-directory-for-user-data": {
"lens-hotbar-store.json": JSON.stringify({}),
},
});
await di.runSetups();
di.inject(hotbarStoreInjectable);
// @ts-ignore
di.permitSideEffects(appVersionInjectable);
});
afterEach(() => {
mockFs.restore();
HotbarStore.resetInstance();
});
describe("load", () => {
it("loads one hotbar by default", () => {
expect(HotbarStore.getInstance().hotbars.length).toEqual(1);
describe("given no migrations", () => {
beforeEach(async () => {
await di.runSetups();
});
describe("load", () => {
it("loads one hotbar by default", () => {
expect(HotbarStore.getInstance().hotbars.length).toEqual(1);
});
});
describe("add", () => {
it("adds a hotbar", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.add({ name: "hottest" });
expect(hotbarStore.hotbars.length).toEqual(2);
});
});
describe("hotbar items", () => {
it("initially creates 12 empty cells", () => {
const hotbarStore = HotbarStore.getInstance();
expect(hotbarStore.getActive().items.length).toEqual(12);
});
it("initially adds catalog entity as first item", () => {
const hotbarStore = HotbarStore.getInstance();
expect(hotbarStore.getActive().items[0].entity.name).toEqual("Catalog");
});
it("adds items", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.addToHotbar(testCluster);
const items = hotbarStore.getActive().items.filter(Boolean);
expect(items.length).toEqual(2);
});
it("removes items", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.addToHotbar(testCluster);
hotbarStore.removeFromHotbar("test");
hotbarStore.removeFromHotbar("catalog-entity");
const items = hotbarStore.getActive().items.filter(Boolean);
expect(items).toStrictEqual([]);
});
it("does nothing if removing with invalid uid", () => {
const hotbarStore = HotbarStore.getInstance();
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", () => {
const hotbarStore = HotbarStore.getInstance();
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("test");
});
it("moves items down", () => {
const hotbarStore = HotbarStore.getInstance();
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([
"aws",
"catalog-entity",
"test",
"minikube",
]);
});
it("moves items up", () => {
const hotbarStore = HotbarStore.getInstance();
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",
"minikube",
"aws",
"test",
]);
});
it("logs an error if cellIndex is out of bounds", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.add({ name: "hottest", id: "hottest" });
hotbarStore.setActiveHotbar("hottest");
const { error } = logger;
const mocked = jest.fn();
logger.error = mocked;
hotbarStore.addToHotbar(testCluster, -1);
expect(mocked).toBeCalledWith(
"[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range",
anyObject(),
);
hotbarStore.addToHotbar(testCluster, 12);
expect(mocked).toBeCalledWith(
"[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range",
anyObject(),
);
hotbarStore.addToHotbar(testCluster, 13);
expect(mocked).toBeCalledWith(
"[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range",
anyObject(),
);
logger.error = error;
});
it("throws an error if getId is invalid or returns not a string", () => {
const hotbarStore = HotbarStore.getInstance();
expect(() => hotbarStore.addToHotbar({} as any)).toThrowError(
TypeError,
);
expect(() =>
hotbarStore.addToHotbar({ getId: () => true } as any),
).toThrowError(TypeError);
});
it("throws an error if getName is invalid or returns not a string", () => {
const hotbarStore = HotbarStore.getInstance();
expect(() =>
hotbarStore.addToHotbar({ getId: () => "" } as any),
).toThrowError(TypeError);
expect(() =>
hotbarStore.addToHotbar({ getId: () => "", getName: () => 4 } as any),
).toThrowError(TypeError);
});
it("does nothing when item moved to same cell", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.addToHotbar(testCluster);
hotbarStore.restackItems(1, 1);
expect(hotbarStore.getActive().items[1].entity.uid).toEqual("test");
});
it("new items takes first empty cell", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.addToHotbar(testCluster);
hotbarStore.addToHotbar(awsCluster);
hotbarStore.restackItems(0, 3);
hotbarStore.addToHotbar(minikubeCluster);
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("minikube");
});
it("throws if invalid arguments provided", () => {
// Prevent writing to stderr during this render.
const { error, warn } = console;
console.error = jest.fn();
console.warn = jest.fn();
const hotbarStore = HotbarStore.getInstance();
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();
// Restore writing to stderr.
console.error = error;
console.warn = warn;
});
it("checks if entity already pinned to hotbar", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.addToHotbar(testCluster);
expect(hotbarStore.isAddedToActive(testCluster)).toBeTruthy();
expect(hotbarStore.isAddedToActive(awsCluster)).toBeFalsy();
});
});
});
describe("add", () => {
it("adds a hotbar", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.add({ name: "hottest" });
expect(hotbarStore.hotbars.length).toEqual(2);
});
});
describe("hotbar items", () => {
it("initially creates 12 empty cells", () => {
const hotbarStore = HotbarStore.getInstance();
expect(hotbarStore.getActive().items.length).toEqual(12);
});
it("initially adds catalog entity as first item", () => {
const hotbarStore = HotbarStore.getInstance();
expect(hotbarStore.getActive().items[0].entity.name).toEqual("Catalog");
});
it("adds items", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.addToHotbar(testCluster);
const items = hotbarStore.getActive().items.filter(Boolean);
expect(items.length).toEqual(2);
});
it("removes items", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.addToHotbar(testCluster);
hotbarStore.removeFromHotbar("test");
hotbarStore.removeFromHotbar("catalog-entity");
const items = hotbarStore.getActive().items.filter(Boolean);
expect(items).toStrictEqual([]);
});
it("does nothing if removing with invalid uid", () => {
const hotbarStore = HotbarStore.getInstance();
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", () => {
const hotbarStore = HotbarStore.getInstance();
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("test");
});
it("moves items down", () => {
const hotbarStore = HotbarStore.getInstance();
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(["aws", "catalog-entity", "test", "minikube"]);
});
it("moves items up", () => {
const hotbarStore = HotbarStore.getInstance();
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", "minikube", "aws", "test"]);
});
it("logs an error if cellIndex is out of bounds", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.add({ name: "hottest", id: "hottest" });
hotbarStore.setActiveHotbar("hottest");
const { error } = logger;
const mocked = jest.fn();
logger.error = mocked;
hotbarStore.addToHotbar(testCluster, -1);
expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
hotbarStore.addToHotbar(testCluster, 12);
expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
hotbarStore.addToHotbar(testCluster, 13);
expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
logger.error = error;
});
it("throws an error if getId is invalid or returns not a string", () => {
const hotbarStore = HotbarStore.getInstance();
expect(() => hotbarStore.addToHotbar({} as any)).toThrowError(TypeError);
expect(() => hotbarStore.addToHotbar({ getId: () => true } as any)).toThrowError(TypeError);
});
it("throws an error if getName is invalid or returns not a string", () => {
const hotbarStore = HotbarStore.getInstance();
expect(() => hotbarStore.addToHotbar({ getId: () => "" } as any)).toThrowError(TypeError);
expect(() => hotbarStore.addToHotbar({ getId: () => "", getName: () => 4 } as any)).toThrowError(TypeError);
});
it("does nothing when item moved to same cell", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.addToHotbar(testCluster);
hotbarStore.restackItems(1, 1);
expect(hotbarStore.getActive().items[1].entity.uid).toEqual("test");
});
it("new items takes first empty cell", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.addToHotbar(testCluster);
hotbarStore.addToHotbar(awsCluster);
hotbarStore.restackItems(0, 3);
hotbarStore.addToHotbar(minikubeCluster);
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("minikube");
});
it("throws if invalid arguments provided", () => {
// Prevent writing to stderr during this render.
const { error, warn } = console;
console.error = jest.fn();
console.warn = jest.fn();
const hotbarStore = HotbarStore.getInstance();
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();
// Restore writing to stderr.
console.error = error;
console.warn = warn;
});
it("checks if entity already pinned to hotbar", () => {
const hotbarStore = HotbarStore.getInstance();
hotbarStore.addToHotbar(testCluster);
expect(hotbarStore.isAddedToActive(testCluster)).toBeTruthy();
expect(hotbarStore.isAddedToActive(awsCluster)).toBeFalsy();
});
});
describe("pre beta-5 migrations", () => {
beforeEach(() => {
const mockOpts = {
"some-directory-for-user-data": {
describe("given pre beta-5 configurations", () => {
beforeEach(async () => {
const configurationToBeMigrated = {
"some-electron-app-path-for-user-data": {
"lens-hotbar-store.json": JSON.stringify({
__internal__: {
migrations: {
version: "5.0.0-beta.3",
},
},
"hotbars": [
hotbars: [
{
"id": "3caac17f-aec2-4723-9694-ad204465d935",
"name": "myhotbar",
"items": [
id: "3caac17f-aec2-4723-9694-ad204465d935",
name: "myhotbar",
items: [
{
"entity": {
"uid": "1dfa26e2ebab15780a3547e9c7fa785c",
entity: {
uid: "1dfa26e2ebab15780a3547e9c7fa785c",
},
},
{
"entity": {
"uid": "55b42c3c7ba3b04193416cda405269a5",
entity: {
uid: "55b42c3c7ba3b04193416cda405269a5",
},
},
{
"entity": {
"uid": "176fd331968660832f62283219d7eb6e",
entity: {
uid: "176fd331968660832f62283219d7eb6e",
},
},
{
"entity": {
"uid": "61c4fb45528840ebad1badc25da41d14",
"name": "user1-context",
"source": "local",
entity: {
uid: "61c4fb45528840ebad1badc25da41d14",
name: "user1-context",
source: "local",
},
},
{
"entity": {
"uid": "27d6f99fe9e7548a6e306760bfe19969",
"name": "foo2",
"source": "local",
entity: {
uid: "27d6f99fe9e7548a6e306760bfe19969",
name: "foo2",
source: "local",
},
},
null,
{
"entity": {
"uid": "c0b20040646849bb4dcf773e43a0bf27",
"name": "multinode-demo",
"source": "local",
entity: {
uid: "c0b20040646849bb4dcf773e43a0bf27",
name: "multinode-demo",
source: "local",
},
},
null,
@ -399,15 +440,15 @@ describe("HotbarStore", () => {
},
};
mockFs(mockOpts);
});
mockFs(configurationToBeMigrated);
afterEach(() => {
mockFs.restore();
await di.runSetups();
});
it("allows to retrieve a hotbar", () => {
const hotbar = HotbarStore.getInstance().getById("3caac17f-aec2-4723-9694-ad204465d935");
const hotbar = HotbarStore.getInstance().getById(
"3caac17f-aec2-4723-9694-ad204465d935",
);
expect(hotbar.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
});

View File

@ -2,16 +2,379 @@
* 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 { HotbarStore } from "./hotbar-store";
import catalogCatalogEntityInjectable from "./catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import { action, comparer, observable, makeObservable, computed } from "mobx";
import { BaseStore } from "./base-store";
import migrations from "../migrations/hotbar-store";
import { toJS } from "./utils";
import { CatalogEntity } from "./catalog";
import logger from "../main/logger";
import { broadcastMessage } from "./ipc";
import {
defaultHotbarCells,
getEmptyHotbar,
Hotbar,
CreateHotbarData,
CreateHotbarOptions,
} from "./hotbar-types";
import { hotbarTooManyItemsChannel } from "./ipc/hotbar";
import type { GeneralEntity } from "./catalog-entities";
export interface HotbarStoreModel {
hotbars: Hotbar[];
activeHotbarId: string;
}
interface Dependencies {
catalogCatalogEntity: GeneralEntity;
}
export class HotbarStore extends BaseStore<HotbarStoreModel> {
readonly displayName = "HotbarStore";
@observable hotbars: Hotbar[] = [];
@observable private _activeHotbarId: string;
constructor(private dependencies: Dependencies) {
super({
configName: "lens-hotbar-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
syncOptions: {
equals: comparer.structural,
},
migrations,
});
makeObservable(this);
this.load();
}
@computed get activeHotbarId() {
return this._activeHotbarId;
}
/**
* If `hotbar` points to a known hotbar, make it active. Otherwise, ignore
* @param hotbar The hotbar instance, or the index, or its ID
*/
setActiveHotbar(hotbar: Hotbar | number | string) {
if (typeof hotbar === "number") {
if (hotbar >= 0 && hotbar < this.hotbars.length) {
this._activeHotbarId = this.hotbars[hotbar].id;
}
} else if (typeof hotbar === "string") {
if (this.getById(hotbar)) {
this._activeHotbarId = hotbar;
}
} else {
if (this.hotbars.indexOf(hotbar) >= 0) {
this._activeHotbarId = hotbar.id;
}
}
}
private hotbarIndexById(id: string) {
return this.hotbars.findIndex((hotbar) => hotbar.id === id);
}
private hotbarIndex(hotbar: Hotbar) {
return this.hotbars.indexOf(hotbar);
}
@computed get activeHotbarIndex() {
return this.hotbarIndexById(this.activeHotbarId);
}
@action
protected fromStore(data: Partial<HotbarStoreModel> = {}) {
if (!data.hotbars || !data.hotbars.length) {
const hotbar = getEmptyHotbar("Default");
const {
metadata: { uid, name, source },
} = this.dependencies.catalogCatalogEntity;
const initialItem = { entity: { uid, name, source }};
hotbar.items[0] = initialItem;
this.hotbars = [hotbar];
} else {
this.hotbars = data.hotbars;
}
this.hotbars.forEach(ensureExactHotbarItemLength);
if (data.activeHotbarId) {
this.setActiveHotbar(data.activeHotbarId);
}
if (!this.activeHotbarId) {
this.setActiveHotbar(0);
}
}
toJSON(): HotbarStoreModel {
const model: HotbarStoreModel = {
hotbars: this.hotbars,
activeHotbarId: this.activeHotbarId,
};
return toJS(model);
}
getActive() {
return this.getById(this.activeHotbarId);
}
getByName(name: string) {
return this.hotbars.find((hotbar) => hotbar.name === name);
}
getById(id: string) {
return this.hotbars.find((hotbar) => hotbar.id === id);
}
add = action(
(
data: CreateHotbarData,
{ setActive = false }: CreateHotbarOptions = {},
) => {
const hotbar = getEmptyHotbar(data.name, data.id);
this.hotbars.push(hotbar);
if (setActive) {
this._activeHotbarId = hotbar.id;
}
},
);
setHotbarName = action((id: string, name: string) => {
const index = this.hotbars.findIndex((hotbar) => hotbar.id === id);
if (index < 0) {
return void console.warn(
`[HOTBAR-STORE]: cannot setHotbarName: unknown id`,
{ id },
);
}
this.hotbars[index].name = name;
});
remove = action((hotbar: Hotbar) => {
if (this.hotbars.length <= 1) {
throw new Error("Cannot remove the last hotbar");
}
this.hotbars = this.hotbars.filter((h) => h !== hotbar);
if (this.activeHotbarId === hotbar.id) {
this.setActiveHotbar(0);
}
});
@action
addToHotbar(item: CatalogEntity, cellIndex?: number) {
const hotbar = this.getActive();
const uid = item.getId();
const name = item.getName();
if (typeof uid !== "string") {
throw new TypeError("CatalogEntity's ID must be a string");
}
if (typeof name !== "string") {
throw new TypeError("CatalogEntity's NAME must be a string");
}
if (this.isAddedToActive(item)) {
return;
}
const entity = {
uid,
name,
source: item.metadata.source,
};
const newItem = { entity };
if (cellIndex === undefined) {
// Add item to empty cell
const emptyCellIndex = hotbar.items.indexOf(null);
if (emptyCellIndex != -1) {
hotbar.items[emptyCellIndex] = newItem;
} else {
broadcastMessage(hotbarTooManyItemsChannel);
}
} else if (0 <= cellIndex && cellIndex < hotbar.items.length) {
hotbar.items[cellIndex] = newItem;
} else {
logger.error(
`[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range`,
{ entityId: uid, hotbarId: hotbar.id, cellIndex },
);
}
}
@action
removeFromHotbar(uid: string): void {
const hotbar = this.getActive();
const index = hotbar.items.findIndex((item) => item?.entity.uid === uid);
if (index < 0) {
return;
}
hotbar.items[index] = null;
}
/**
* Remove all hotbar items that reference the `uid`.
* @param uid The `EntityId` that each hotbar item refers to
* @returns A function that will (in an action) undo the removing of the hotbar items. This function will not complete if the hotbar has changed.
*/
@action
removeAllHotbarItems(uid: string) {
for (const hotbar of this.hotbars) {
const index = hotbar.items.findIndex((i) => i?.entity.uid === uid);
if (index >= 0) {
hotbar.items[index] = null;
}
}
}
findClosestEmptyIndex(from: number, direction = 1) {
let index = from;
while (this.getActive().items[index] != null) {
index += direction;
}
return index;
}
@action
restackItems(from: number, to: number): void {
const { items } = this.getActive();
const source = items[from];
const moveDown = from < to;
if (
from < 0 ||
to < 0 ||
from >= items.length ||
to >= items.length ||
isNaN(from) ||
isNaN(to)
) {
throw new Error("Invalid 'from' or 'to' arguments");
}
if (from == to) {
return;
}
items.splice(from, 1, null);
if (items[to] == null) {
items.splice(to, 1, source);
} else {
// Move cells up or down to closes empty cell
items.splice(this.findClosestEmptyIndex(to, moveDown ? -1 : 1), 1);
items.splice(to, 0, source);
}
}
switchToPrevious() {
const hotbarStore = HotbarStore.getInstance();
let index = hotbarStore.activeHotbarIndex - 1;
if (index < 0) {
index = hotbarStore.hotbars.length - 1;
}
hotbarStore.setActiveHotbar(index);
}
switchToNext() {
const hotbarStore = HotbarStore.getInstance();
let index = hotbarStore.activeHotbarIndex + 1;
if (index >= hotbarStore.hotbars.length) {
index = 0;
}
hotbarStore.setActiveHotbar(index);
}
/**
* Checks if entity already pinned to the active hotbar
*/
isAddedToActive(entity: CatalogEntity | null | undefined): boolean {
if (!entity) {
return false;
}
return (
this.getActive().items.findIndex(
(item) => item?.entity.uid === entity.getId(),
) >= 0
);
}
getDisplayLabel(hotbar: Hotbar): string {
return `${this.getDisplayIndex(hotbar)}: ${hotbar.name}`;
}
getDisplayIndex(hotbar: Hotbar): string {
const index = this.hotbarIndex(hotbar);
if (index < 0) {
return "??";
}
return `${index + 1}`;
}
}
/**
* This function ensures that there are always exactly `defaultHotbarCells`
* worth of items in the hotbar.
* @param hotbar The hotbar to modify
*/
function ensureExactHotbarItemLength(hotbar: Hotbar) {
// if there are not enough items
while (hotbar.items.length < defaultHotbarCells) {
hotbar.items.push(null);
}
// if for some reason the hotbar was overfilled before, remove as many entries
// as needed, but prefer empty slots and items at the end first.
while (hotbar.items.length > defaultHotbarCells) {
const lastNull = hotbar.items.lastIndexOf(null);
if (lastNull >= 0) {
hotbar.items.splice(lastNull, 1);
} else {
hotbar.items.length = defaultHotbarCells;
}
}
}
const hotbarStoreInjectable = getInjectable({
id: "hotbar-store",
instantiate: (di) => HotbarStore.createInstance({
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
}),
instantiate: (di) => {
HotbarStore.resetInstance();
const instance = HotbarStore.createInstance({
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
});
return instance;
},
causesSideEffects: true,
});

View File

@ -3,330 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { action, comparer, observable, makeObservable, computed } from "mobx";
import { BaseStore } from "./base-store";
import migrations from "../migrations/hotbar-store";
import { toJS } from "./utils";
import { CatalogEntity } from "./catalog";
import logger from "../main/logger";
import { broadcastMessage } from "./ipc";
import { defaultHotbarCells, getEmptyHotbar, Hotbar, CreateHotbarData, CreateHotbarOptions } from "./hotbar-types";
import { hotbarTooManyItemsChannel } from "./ipc/hotbar";
import type { GeneralEntity } from "./catalog-entities";
import hotbarStoreInjectable from "./hotbar-store.injectable";
export interface HotbarStoreModel {
hotbars: Hotbar[];
activeHotbarId: string;
}
import {
asLegacyGlobalSingletonForExtensionApi,
} from "../extensions/as-legacy-globals-for-extension-api/as-legacy-global-singleton-object-for-extension-api";
interface Dependencies {
catalogCatalogEntity: GeneralEntity;
}
export class HotbarStore extends BaseStore<HotbarStoreModel> {
readonly displayName = "HotbarStore";
@observable hotbars: Hotbar[] = [];
@observable private _activeHotbarId: string;
constructor(private dependencies: Dependencies) {
super({
configName: "lens-hotbar-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
syncOptions: {
equals: comparer.structural,
},
migrations,
});
makeObservable(this);
this.load();
}
@computed get activeHotbarId() {
return this._activeHotbarId;
}
/**
* If `hotbar` points to a known hotbar, make it active. Otherwise, ignore
* @param hotbar The hotbar instance, or the index, or its ID
*/
setActiveHotbar(hotbar: Hotbar | number | string) {
if (typeof hotbar === "number") {
if (hotbar >= 0 && hotbar < this.hotbars.length) {
this._activeHotbarId = this.hotbars[hotbar].id;
}
} else if (typeof hotbar === "string") {
if (this.getById(hotbar)) {
this._activeHotbarId = hotbar;
}
} else {
if (this.hotbars.indexOf(hotbar) >= 0) {
this._activeHotbarId = hotbar.id;
}
}
}
private hotbarIndexById(id: string) {
return this.hotbars.findIndex((hotbar) => hotbar.id === id);
}
private hotbarIndex(hotbar: Hotbar) {
return this.hotbars.indexOf(hotbar);
}
@computed get activeHotbarIndex() {
return this.hotbarIndexById(this.activeHotbarId);
}
@action
protected fromStore(data: Partial<HotbarStoreModel> = {}) {
if (!data.hotbars || !data.hotbars.length) {
const hotbar = getEmptyHotbar("Default");
const { metadata: { uid, name, source }} = this.dependencies.catalogCatalogEntity;
const initialItem = { entity: { uid, name, source }};
hotbar.items[0] = initialItem;
this.hotbars = [hotbar];
} else {
this.hotbars = data.hotbars;
}
this.hotbars.forEach(ensureExactHotbarItemLength);
if (data.activeHotbarId) {
this.setActiveHotbar(data.activeHotbarId);
}
if (!this.activeHotbarId) {
this.setActiveHotbar(0);
}
}
toJSON(): HotbarStoreModel {
const model: HotbarStoreModel = {
hotbars: this.hotbars,
activeHotbarId: this.activeHotbarId,
};
return toJS(model);
}
getActive() {
return this.getById(this.activeHotbarId);
}
getByName(name: string) {
return this.hotbars.find((hotbar) => hotbar.name === name);
}
getById(id: string) {
return this.hotbars.find((hotbar) => hotbar.id === id);
}
add = action((data: CreateHotbarData, { setActive = false }: CreateHotbarOptions = {}) => {
const hotbar = getEmptyHotbar(data.name, data.id);
this.hotbars.push(hotbar);
if (setActive) {
this._activeHotbarId = hotbar.id;
}
});
setHotbarName = action((id: string, name: string) => {
const index = this.hotbars.findIndex((hotbar) => hotbar.id === id);
if (index < 0) {
return void console.warn(`[HOTBAR-STORE]: cannot setHotbarName: unknown id`, { id });
}
this.hotbars[index].name = name;
});
remove = action((hotbar: Hotbar) => {
if (this.hotbars.length <= 1) {
throw new Error("Cannot remove the last hotbar");
}
this.hotbars = this.hotbars.filter((h) => h !== hotbar);
if (this.activeHotbarId === hotbar.id) {
this.setActiveHotbar(0);
}
});
@action
addToHotbar(item: CatalogEntity, cellIndex?: number) {
const hotbar = this.getActive();
const uid = item.getId();
const name = item.getName();
if (typeof uid !== "string") {
throw new TypeError("CatalogEntity's ID must be a string");
}
if (typeof name !== "string") {
throw new TypeError("CatalogEntity's NAME must be a string");
}
if (this.isAddedToActive(item)) {
return;
}
const entity = {
uid,
name,
source: item.metadata.source,
};
const newItem = { entity };
if (cellIndex === undefined) {
// Add item to empty cell
const emptyCellIndex = hotbar.items.indexOf(null);
if (emptyCellIndex != -1) {
hotbar.items[emptyCellIndex] = newItem;
} else {
broadcastMessage(hotbarTooManyItemsChannel);
}
} else if (0 <= cellIndex && cellIndex < hotbar.items.length) {
hotbar.items[cellIndex] = newItem;
} else {
logger.error(`[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range`, { entityId: uid, hotbarId: hotbar.id, cellIndex });
}
}
@action
removeFromHotbar(uid: string): void {
const hotbar = this.getActive();
const index = hotbar.items.findIndex(item => item?.entity.uid === uid);
if (index < 0) {
return;
}
hotbar.items[index] = null;
}
/**
* Remove all hotbar items that reference the `uid`.
* @param uid The `EntityId` that each hotbar item refers to
* @returns A function that will (in an action) undo the removing of the hotbar items. This function will not complete if the hotbar has changed.
*/
@action
removeAllHotbarItems(uid: string) {
for (const hotbar of this.hotbars) {
const index = hotbar.items.findIndex((i) => i?.entity.uid === uid);
if (index >= 0) {
hotbar.items[index] = null;
}
}
}
findClosestEmptyIndex(from: number, direction = 1) {
let index = from;
while(this.getActive().items[index] != null) {
index += direction;
}
return index;
}
@action
restackItems(from: number, to: number): void {
const { items } = this.getActive();
const source = items[from];
const moveDown = from < to;
if (from < 0 || to < 0 || from >= items.length || to >= items.length || isNaN(from) || isNaN(to)) {
throw new Error("Invalid 'from' or 'to' arguments");
}
if (from == to) {
return;
}
items.splice(from, 1, null);
if (items[to] == null) {
items.splice(to, 1, source);
} else {
// Move cells up or down to closes empty cell
items.splice(this.findClosestEmptyIndex(to, moveDown ? -1 : 1), 1);
items.splice(to, 0, source);
}
}
switchToPrevious() {
const hotbarStore = HotbarStore.getInstance();
let index = hotbarStore.activeHotbarIndex - 1;
if (index < 0) {
index = hotbarStore.hotbars.length - 1;
}
hotbarStore.setActiveHotbar(index);
}
switchToNext() {
const hotbarStore = HotbarStore.getInstance();
let index = hotbarStore.activeHotbarIndex + 1;
if (index >= hotbarStore.hotbars.length) {
index = 0;
}
hotbarStore.setActiveHotbar(index);
}
/**
* Checks if entity already pinned to the active hotbar
*/
isAddedToActive(entity: CatalogEntity | null | undefined): boolean {
if (!entity) {
return false;
}
return this.getActive().items.findIndex(item => item?.entity.uid === entity.getId()) >= 0;
}
getDisplayLabel(hotbar: Hotbar): string {
return `${this.getDisplayIndex(hotbar)}: ${hotbar.name}`;
}
getDisplayIndex(hotbar: Hotbar): string {
const index = this.hotbarIndex(hotbar);
if (index < 0) {
return "??";
}
return `${index + 1}`;
}
}
/**
* This function ensures that there are always exactly `defaultHotbarCells`
* worth of items in the hotbar.
* @param hotbar The hotbar to modify
*/
function ensureExactHotbarItemLength(hotbar: Hotbar) {
// if there are not enough items
while (hotbar.items.length < defaultHotbarCells) {
hotbar.items.push(null);
}
// if for some reason the hotbar was overfilled before, remove as many entries
// as needed, but prefer empty slots and items at the end first.
while (hotbar.items.length > defaultHotbarCells) {
const lastNull = hotbar.items.lastIndexOf(null);
if (lastNull >= 0) {
hotbar.items.splice(lastNull, 1);
} else {
hotbar.items.length = defaultHotbarCells;
}
}
}
export const HotbarStore = asLegacyGlobalSingletonForExtensionApi(hotbarStoreInjectable);