Hotbar inner drag-n-drop (#2691)

* Configure ts to use react-jsx rule

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Moving HotbarSelector to separate component

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Initial drag-n-drop implementation

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Revert tsconfig and linter changes

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Reverting back active cell effect

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding drag-n-drop behavior

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fix drag-n-drop logic

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* White border on dragging over

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding test coverage

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fixing cell hover effect

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Increase PageLayout z-index

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Styling hotbar selector tooltip

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
Alex Andreev authored and GitHub committed 2021-05-04 07:49:14 +03:00
1 parent 83e63bf959
commit 1af12fe59e
9 files changed
+404 -96

No files matched your search

+197
View File
@@ -1,7 +1,71 @@
import mockFs from "mock-fs";
import { CatalogEntityItem } from "../../renderer/components/+catalog/catalog-entity.store";
import { ClusterStore } from "../cluster-store";
import { HotbarStore } from "../hotbar-store";
const testCluster = {
uid: "test",
name: "test",
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running"
},
spec: {},
getName: jest.fn(),
getId: jest.fn(),
onDetailsOpen: jest.fn(),
onContextMenuOpen: jest.fn(),
onSettingsOpen: jest.fn(),
metadata: {
uid: "test",
name: "test",
labels: {}
}
};
const minikubeCluster = {
uid: "minikube",
name: "minikube",
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running"
},
spec: {},
getName: jest.fn(),
getId: jest.fn(),
onDetailsOpen: jest.fn(),
onContextMenuOpen: jest.fn(),
onSettingsOpen: jest.fn(),
metadata: {
uid: "minikube",
name: "minikube",
labels: {}
}
};
const awsCluster = {
uid: "aws",
name: "aws",
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running"
},
spec: {},
getName: jest.fn(),
getId: jest.fn(),
onDetailsOpen: jest.fn(),
onContextMenuOpen: jest.fn(),
onSettingsOpen: jest.fn(),
metadata: {
uid: "aws",
name: "aws",
labels: {}
}
};
describe("HotbarStore", () => {
beforeEach(() => {
ClusterStore.resetInstance();
@@ -31,4 +95,137 @@ describe("HotbarStore", () => {
expect(hotbarStore.hotbars.length).toEqual(2);
});
});
describe("hotbar items", () => {
it("initially creates 12 empty cells", () => {
const hotbarStore = HotbarStore.createInstance();
hotbarStore.load();
expect(hotbarStore.getActive().items.length).toEqual(12);
});
it("adds items", () => {
const hotbarStore = HotbarStore.createInstance();
const entity = new CatalogEntityItem(testCluster);
hotbarStore.load();
hotbarStore.addToHotbar(entity);
const items = hotbarStore.getActive().items.filter(Boolean);
expect(items.length).toEqual(1);
});
it("removes items", () => {
const hotbarStore = HotbarStore.createInstance();
const entity = new CatalogEntityItem(testCluster);
hotbarStore.load();
hotbarStore.addToHotbar(entity);
hotbarStore.removeFromHotbar("test");
const items = hotbarStore.getActive().items.filter(Boolean);
expect(items.length).toEqual(0);
});
it("does nothing if removing with invalid uid", () => {
const hotbarStore = HotbarStore.createInstance();
const entity = new CatalogEntityItem(testCluster);
hotbarStore.load();
hotbarStore.addToHotbar(entity);
hotbarStore.removeFromHotbar("invalid uid");
const items = hotbarStore.getActive().items.filter(Boolean);
expect(items.length).toEqual(1);
});
it("moves item to empty cell", () => {
const hotbarStore = HotbarStore.createInstance();
const test = new CatalogEntityItem(testCluster);
const minikube = new CatalogEntityItem(minikubeCluster);
const aws = new CatalogEntityItem(awsCluster);
hotbarStore.load();
hotbarStore.addToHotbar(test);
hotbarStore.addToHotbar(minikube);
hotbarStore.addToHotbar(aws);
expect(hotbarStore.getActive().items[5]).toBeNull();
hotbarStore.restackItems(1, 5);
expect(hotbarStore.getActive().items[5]).toBeTruthy();
expect(hotbarStore.getActive().items[5].entity.uid).toEqual("minikube");
});
it("moves items down", () => {
const hotbarStore = HotbarStore.createInstance();
const test = new CatalogEntityItem(testCluster);
const minikube = new CatalogEntityItem(minikubeCluster);
const aws = new CatalogEntityItem(awsCluster);
hotbarStore.load();
hotbarStore.addToHotbar(test);
hotbarStore.addToHotbar(minikube);
hotbarStore.addToHotbar(aws);
// aws -> test
hotbarStore.restackItems(2, 0);
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
expect(items.slice(0, 4)).toEqual(["aws", "test", "minikube", null]);
});
it("moves items up", () => {
const hotbarStore = HotbarStore.createInstance();
const test = new CatalogEntityItem(testCluster);
const minikube = new CatalogEntityItem(minikubeCluster);
const aws = new CatalogEntityItem(awsCluster);
hotbarStore.load();
hotbarStore.addToHotbar(test);
hotbarStore.addToHotbar(minikube);
hotbarStore.addToHotbar(aws);
// test -> aws
hotbarStore.restackItems(0, 2);
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
expect(items.slice(0, 4)).toEqual(["minikube", "aws", "test", null]);
});
it("does nothing when item moved to same cell", () => {
const hotbarStore = HotbarStore.createInstance();
const test = new CatalogEntityItem(testCluster);
hotbarStore.load();
hotbarStore.addToHotbar(test);
hotbarStore.restackItems(0, 0);
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("test");
});
it("throws if invalid arguments provided", () => {
// Prevent writing to stderr during this render.
const err = console.error;
console.error = jest.fn();
const hotbarStore = HotbarStore.createInstance();
const test = new CatalogEntityItem(testCluster);
hotbarStore.load();
hotbarStore.addToHotbar(test);
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 = err;
});
});
});
+36 -3
View File
@@ -4,7 +4,6 @@ import migrations from "../migrations/hotbar-store";
import * as uuid from "uuid";
import { CatalogEntityItem } from "../renderer/components/+catalog/catalog-entity.store";
import isNull from "lodash/isNull";
import { CatalogEntity } from "./catalog/catalog-entity";
export interface HotbarItem {
entity: {
@@ -147,9 +146,9 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
}
}
removeFromHotbar(item: CatalogEntity) {
removeFromHotbar(uid: string) {
const hotbar = this.getActive();
const index = hotbar.items.findIndex((i) => i?.entity.uid === item.getId());
const index = hotbar.items.findIndex((i) => i?.entity.uid === uid);
if (index == -1) {
return;
@@ -158,6 +157,40 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
hotbar.items[index] = null;
}
findClosestEmptyIndex(from: number, direction = 1) {
let index = from;
while(this.getActive().items[index] != null) {
index += direction;
}
return index;
}
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;