mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Remove BaseStore<T> and replace with composition of dependencies (#7002)
* Rename ExtensionStore -> BaseExtensionStore - The name was too close to ExtensionsStore Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move ExtensionsStore to new format Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move ClusterStore to new format Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move UserStore to new format Signed-off-by: Sebastian Malton <sebastian@malton.name> * Cleanup types to remove multiple cast locations Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move HotbarStore to new format Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move WeblinkStore to new format Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move FileSystemProvisionerStore to new format Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Clean up impl and rename to better describe intent Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix remaining type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fully split apart the enabled extensions storage Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fully split apart the clusters storage Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fully split apart the hotbar storage Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fully split apart the weblinks storage Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fully split apart the user preferences storage Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crashing Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests and snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix integration test failures Signed-off-by: Sebastian Malton <sebastian@malton.name> * Improve typing to prevent errors in the future. Signed-off-by: Sebastian Malton <sebastian@malton.name> * Cleanup @k8slens/messaging and friends - To fix type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix lint issue Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix global override not being complete enough causing tests to fail Signed-off-by: Sebastian Malton <sebastian@malton.name> * Bump memory for unit tests on CI Signed-off-by: Sebastian Malton <sebastian@malton.name> * Attempt to fix memory issue on CI again Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fixup test because of new injectables Signed-off-by: Sebastian Malton <sebastian@malton.name> * Upgrade Jest Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix unit tests falling over Signed-off-by: Sebastian Malton <sebastian@malton.name> * Back out jest config change Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove console log Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshot Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests by matching equality instead of snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests by forcing specific snapshot style - Ubuntu CI seems to format arrays in snapshots differently than macOS locally Signed-off-by: Sebastian Malton <sebastian@malton.name> --------- Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
ba4a283af9
commit
49f0a1af9c
365 files changed
+6733
-3972
No files matched your search
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { MigrationDeclaration } from "../../../../common/persistent-storage/migrations.injectable";
|
||||
|
||||
export const hotbarStoreMigrationInjectionToken = getInjectionToken<MigrationDeclaration>({
|
||||
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;
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
|
||||
export interface HotbarItem {
|
||||
entity: {
|
||||
uid: string;
|
||||
name: string;
|
||||
source?: string;
|
||||
};
|
||||
params?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateHotbarData {
|
||||
id?: string;
|
||||
name: string;
|
||||
items?: (HotbarItem | null)[];
|
||||
}
|
||||
|
||||
export interface CreateHotbarOptions {
|
||||
setActive?: boolean;
|
||||
}
|
||||
|
||||
export const defaultHotbarCells = 12; // Number is chosen to easy hit any item with keyboard
|
||||
@@ -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;
|
||||
|
||||
@@ -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 * as uuid from "uuid";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token";
|
||||
import type { HotbarData } from "../common/hotbar";
|
||||
|
||||
const v500Alpha2HotbarStoreMigrationInjectable = getInjectable({
|
||||
id: "v5.0.0-alpha.2-hotbar-store-migration",
|
||||
instantiate: () => ({
|
||||
version: "5.0.0-alpha.2",
|
||||
run(store) {
|
||||
const rawHotbars = store.get("hotbars");
|
||||
const hotbars: HotbarData[] = Array.isArray(rawHotbars) ? rawHotbars : [];
|
||||
|
||||
store.set("hotbars", hotbars.map(({ id, ...rest }) => ({
|
||||
id: id || uuid.v4(),
|
||||
...rest,
|
||||
})));
|
||||
},
|
||||
}),
|
||||
injectionToken: hotbarStoreMigrationInjectionToken,
|
||||
});
|
||||
|
||||
export default v500Alpha2HotbarStoreMigrationInjectable;
|
||||
|
||||
@@ -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;
|
||||
|
||||
+6
-6
@@ -3,21 +3,21 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
|
||||
import { onLoadOfApplicationInjectionToken } from "@k8slens/application";
|
||||
import setupSyncingOfGeneralCatalogEntitiesInjectable from "../../../../main/start-main-application/runnables/setup-syncing-of-general-catalog-entities.injectable";
|
||||
import hotbarsPersistentStorageInjectable from "../common/storage.injectable";
|
||||
|
||||
const initHotbarStoreInjectable = getInjectable({
|
||||
id: "init-hotbar-store",
|
||||
const loadHotbarStorageInjectable = getInjectable({
|
||||
id: "load-hotbar-storage",
|
||||
instantiate: (di) => ({
|
||||
run: () => {
|
||||
const hotbarStore = di.inject(hotbarStoreInjectable);
|
||||
const storage = di.inject(hotbarsPersistentStorageInjectable);
|
||||
|
||||
hotbarStore.load();
|
||||
storage.loadAndStartSyncing();
|
||||
},
|
||||
runAfter: setupSyncingOfGeneralCatalogEntitiesInjectable,
|
||||
}),
|
||||
injectionToken: onLoadOfApplicationInjectionToken,
|
||||
});
|
||||
|
||||
export default initHotbarStoreInjectable;
|
||||
export default loadHotbarStorageInjectable;
|
||||
+7
-7
@@ -3,21 +3,21 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
|
||||
import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens";
|
||||
import initClusterStoreInjectable from "../../../cluster/store/renderer/init.injectable";
|
||||
import initClusterStoreInjectable from "../../../cluster/storage/renderer/init.injectable";
|
||||
import hotbarsPersistentStorageInjectable from "../common/storage.injectable";
|
||||
|
||||
const initHotbarStoreInjectable = getInjectable({
|
||||
id: "init-hotbar-store",
|
||||
const loadHotbarStorageInjectable = getInjectable({
|
||||
id: "load-hotbar-storage",
|
||||
instantiate: (di) => ({
|
||||
run: () => {
|
||||
const hotbarStore = di.inject(hotbarStoreInjectable);
|
||||
const storage = di.inject(hotbarsPersistentStorageInjectable);
|
||||
|
||||
hotbarStore.load();
|
||||
storage.loadAndStartSyncing();
|
||||
},
|
||||
runAfter: initClusterStoreInjectable,
|
||||
}),
|
||||
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",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user