mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Show active item in hotbar & allow to pin/unpin (#2790)
* show active item in hotbar & allow to pin it Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * cleanup Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix styles Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
parent
d08d5a24d7
commit
752f49821a
@ -20,7 +20,6 @@
|
||||
*/
|
||||
|
||||
import mockFs from "mock-fs";
|
||||
import { CatalogEntityItem } from "../../renderer/components/+catalog/catalog-entity.store";
|
||||
import { ClusterStore } from "../cluster-store";
|
||||
import { HotbarStore } from "../hotbar-store";
|
||||
|
||||
@ -159,10 +158,9 @@ describe("HotbarStore", () => {
|
||||
|
||||
it("adds items", () => {
|
||||
const hotbarStore = HotbarStore.createInstance();
|
||||
const entity = new CatalogEntityItem(testCluster);
|
||||
|
||||
hotbarStore.load();
|
||||
hotbarStore.addToHotbar(entity);
|
||||
hotbarStore.addToHotbar(testCluster);
|
||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||
|
||||
expect(items.length).toEqual(1);
|
||||
@ -170,10 +168,9 @@ describe("HotbarStore", () => {
|
||||
|
||||
it("removes items", () => {
|
||||
const hotbarStore = HotbarStore.createInstance();
|
||||
const entity = new CatalogEntityItem(testCluster);
|
||||
|
||||
hotbarStore.load();
|
||||
hotbarStore.addToHotbar(entity);
|
||||
hotbarStore.addToHotbar(testCluster);
|
||||
hotbarStore.removeFromHotbar("test");
|
||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||
|
||||
@ -182,10 +179,9 @@ describe("HotbarStore", () => {
|
||||
|
||||
it("does nothing if removing with invalid uid", () => {
|
||||
const hotbarStore = HotbarStore.createInstance();
|
||||
const entity = new CatalogEntityItem(testCluster);
|
||||
|
||||
hotbarStore.load();
|
||||
hotbarStore.addToHotbar(entity);
|
||||
hotbarStore.addToHotbar(testCluster);
|
||||
hotbarStore.removeFromHotbar("invalid uid");
|
||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||
|
||||
@ -194,14 +190,11 @@ describe("HotbarStore", () => {
|
||||
|
||||
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);
|
||||
hotbarStore.addToHotbar(testCluster);
|
||||
hotbarStore.addToHotbar(minikubeCluster);
|
||||
hotbarStore.addToHotbar(awsCluster);
|
||||
|
||||
expect(hotbarStore.getActive().items[5]).toBeNull();
|
||||
|
||||
@ -213,14 +206,11 @@ describe("HotbarStore", () => {
|
||||
|
||||
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);
|
||||
hotbarStore.addToHotbar(testCluster);
|
||||
hotbarStore.addToHotbar(minikubeCluster);
|
||||
hotbarStore.addToHotbar(awsCluster);
|
||||
|
||||
// aws -> test
|
||||
hotbarStore.restackItems(2, 0);
|
||||
@ -232,14 +222,11 @@ describe("HotbarStore", () => {
|
||||
|
||||
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);
|
||||
hotbarStore.addToHotbar(testCluster);
|
||||
hotbarStore.addToHotbar(minikubeCluster);
|
||||
hotbarStore.addToHotbar(awsCluster);
|
||||
|
||||
// test -> aws
|
||||
hotbarStore.restackItems(0, 2);
|
||||
@ -251,10 +238,9 @@ describe("HotbarStore", () => {
|
||||
|
||||
it("does nothing when item moved to same cell", () => {
|
||||
const hotbarStore = HotbarStore.createInstance();
|
||||
const test = new CatalogEntityItem(testCluster);
|
||||
|
||||
hotbarStore.load();
|
||||
hotbarStore.addToHotbar(test);
|
||||
hotbarStore.addToHotbar(testCluster);
|
||||
hotbarStore.restackItems(0, 0);
|
||||
|
||||
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("test");
|
||||
@ -262,15 +248,12 @@ describe("HotbarStore", () => {
|
||||
|
||||
it("new items takes first 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(aws);
|
||||
hotbarStore.addToHotbar(testCluster);
|
||||
hotbarStore.addToHotbar(awsCluster);
|
||||
hotbarStore.restackItems(0, 3);
|
||||
hotbarStore.addToHotbar(minikube);
|
||||
hotbarStore.addToHotbar(minikubeCluster);
|
||||
|
||||
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("minikube");
|
||||
});
|
||||
@ -282,10 +265,9 @@ describe("HotbarStore", () => {
|
||||
console.error = jest.fn();
|
||||
|
||||
const hotbarStore = HotbarStore.createInstance();
|
||||
const test = new CatalogEntityItem(testCluster);
|
||||
|
||||
hotbarStore.load();
|
||||
hotbarStore.addToHotbar(test);
|
||||
hotbarStore.addToHotbar(testCluster);
|
||||
|
||||
expect(() => hotbarStore.restackItems(-5, 0)).toThrow();
|
||||
expect(() => hotbarStore.restackItems(2, -1)).toThrow();
|
||||
|
||||
@ -23,8 +23,8 @@ import { action, comparer, observable, toJS } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
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";
|
||||
|
||||
export interface HotbarItem {
|
||||
entity: {
|
||||
@ -151,15 +151,15 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||
}
|
||||
|
||||
@action
|
||||
addToHotbar(item: CatalogEntityItem, cellIndex = -1) {
|
||||
addToHotbar(item: CatalogEntity, cellIndex = -1) {
|
||||
const hotbar = this.getActive();
|
||||
const newItem = { entity: {
|
||||
uid: item.id,
|
||||
name: item.name,
|
||||
source: item.source
|
||||
uid: item.metadata.uid,
|
||||
name: item.metadata.name,
|
||||
source: item.metadata.source
|
||||
}};
|
||||
|
||||
if (hotbar.items.find(i => i?.entity.uid === item.id)) {
|
||||
if (hotbar.items.find(i => i?.entity.uid === item.metadata.uid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ export class Catalog extends React.Component {
|
||||
}
|
||||
|
||||
addToHotbar(item: CatalogEntityItem): void {
|
||||
HotbarStore.getInstance().addToHotbar(item);
|
||||
HotbarStore.getInstance().addToHotbar(item.entity);
|
||||
}
|
||||
|
||||
onDetails(item: CatalogEntityItem) {
|
||||
@ -137,7 +137,7 @@ export class Catalog extends React.Component {
|
||||
return (
|
||||
<MenuActions onOpen={() => item.onContextMenuOpen(this.contextMenu)}>
|
||||
<MenuItem key="add-to-hotbar" onClick={() => this.addToHotbar(item) }>
|
||||
<Icon material="add" small interactive={true} title="Add to hotbar"/> Add to Hotbar
|
||||
<Icon material="push_pin" small interactive={true} title="Pin to Hotbar"/> Pin to Hotbar
|
||||
</MenuItem>
|
||||
{
|
||||
menuItems.map((menuItem, index) => (
|
||||
|
||||
@ -18,26 +18,26 @@
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import "./hotbar-icon.scss";
|
||||
|
||||
import React, { DOMAttributes } from "react";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import randomColor from "randomcolor";
|
||||
|
||||
import { CatalogEntity, CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../../common/catalog";
|
||||
import { CatalogEntity, CatalogEntityContextMenuContext } from "../../../common/catalog";
|
||||
import { catalogCategoryRegistry } from "../../api/catalog-category-registry";
|
||||
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||
import { navigate } from "../../navigation";
|
||||
import { cssNames, IClassName } from "../../utils";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { Icon } from "../icon";
|
||||
import { HotbarIcon } from "./hotbar-icon";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
|
||||
interface Props extends DOMAttributes<HTMLElement> {
|
||||
entity: CatalogEntity;
|
||||
index: number;
|
||||
className?: IClassName;
|
||||
errorClass?: IClassName;
|
||||
add: (item: CatalogEntity, index: number) => void;
|
||||
remove: (uid: string) => void;
|
||||
}
|
||||
|
||||
@ -77,33 +77,18 @@ export class HotbarEntityIcon extends React.Component<Props> {
|
||||
return catalogEntityRegistry.activeEntity?.metadata?.uid == item.getId();
|
||||
}
|
||||
|
||||
onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
||||
if (menuItem.confirm) {
|
||||
ConfirmDialog.open({
|
||||
okButtonProps: {
|
||||
primary: false,
|
||||
accent: true,
|
||||
},
|
||||
ok: () => {
|
||||
menuItem.onClick();
|
||||
},
|
||||
message: menuItem.confirm.message
|
||||
});
|
||||
} else {
|
||||
menuItem.onClick();
|
||||
}
|
||||
}
|
||||
|
||||
generateAvatarStyle(entity: CatalogEntity): React.CSSProperties {
|
||||
return {
|
||||
"backgroundColor": randomColor({ seed: `${entity.metadata.name}-${entity.metadata.source}`, luminosity: "dark" })
|
||||
};
|
||||
isPersisted(entity: CatalogEntity) {
|
||||
return HotbarStore.getInstance().getActive().items.find((item) => item?.entity?.uid === entity.metadata.uid) !== undefined;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.contextMenu) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
entity, errorClass, remove,
|
||||
children, ...elemProps
|
||||
entity, errorClass, add, remove,
|
||||
index, children, ...elemProps
|
||||
} = this.props;
|
||||
const className = cssNames("HotbarEntityIcon", this.props.className, {
|
||||
interactive: true,
|
||||
@ -113,16 +98,31 @@ export class HotbarEntityIcon extends React.Component<Props> {
|
||||
const onOpen = async () => {
|
||||
await entity.onContextMenuOpen(this.contextMenu);
|
||||
};
|
||||
const isActive = this.isActive(entity);
|
||||
const isPersisted = this.isPersisted(entity);
|
||||
const menuItems = this.contextMenu?.menuItems.filter((menuItem) => !menuItem.onlyVisibleForSource || menuItem.onlyVisibleForSource === entity.metadata.source);
|
||||
|
||||
if (!isPersisted) {
|
||||
menuItems.unshift({
|
||||
title: "Pin to Hotbar",
|
||||
icon: "push_pin",
|
||||
onClick: () => add(entity, index)
|
||||
});
|
||||
} else {
|
||||
menuItems.unshift({
|
||||
title: "Unpin from Hotbar",
|
||||
icon: "push_pin",
|
||||
onClick: () => remove(entity.metadata.uid)
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<HotbarIcon
|
||||
uid={entity.getId()}
|
||||
title={entity.getName()}
|
||||
source={`${entity.metadata.source || "local"}`}
|
||||
uid={entity.metadata.uid}
|
||||
title={entity.metadata.name}
|
||||
source={entity.metadata.source}
|
||||
className={className}
|
||||
active={this.isActive(entity)}
|
||||
remove={remove}
|
||||
active={isActive}
|
||||
onMenuOpen={onOpen}
|
||||
menuItems={menuItems}
|
||||
{...elemProps}
|
||||
|
||||
@ -36,11 +36,6 @@
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px var(--textColorAccent);
|
||||
transition: all 0s 0.8s;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
@ -53,23 +48,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.active, &.interactive:hover {
|
||||
img {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:not(.active) {
|
||||
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px #ffffff30;
|
||||
}
|
||||
}
|
||||
|
||||
&.isDragging {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
> .led {
|
||||
div.MuiAvatar-root {
|
||||
&.active {
|
||||
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px var(--textColorAccent);
|
||||
transition: all 0s 0.8s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:not(.active) {
|
||||
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px #ffffff30;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.led {
|
||||
position: absolute;
|
||||
left: 3px;
|
||||
top: 3px;
|
||||
|
||||
@ -32,12 +32,12 @@ import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { Icon } from "../icon";
|
||||
import { Menu, MenuItem } from "../menu";
|
||||
import { MaterialTooltip } from "../+catalog/material-tooltip/material-tooltip";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
interface Props extends DOMAttributes<HTMLElement> {
|
||||
uid: string;
|
||||
title: string;
|
||||
source: string;
|
||||
remove: (uid: string) => void;
|
||||
onMenuOpen?: () => void;
|
||||
className?: IClassName;
|
||||
active?: boolean;
|
||||
@ -84,8 +84,8 @@ function getNameParts(name: string): string[] {
|
||||
return name.split(/@+/);
|
||||
}
|
||||
|
||||
export function HotbarIcon(props: Props) {
|
||||
const { uid, title, className, source, active, remove, disabled, menuItems, onMenuOpen, children, ...rest } = props;
|
||||
export const HotbarIcon = observer(({menuItems = [], ...props}: Props) => {
|
||||
const { uid, title, active, className, source, disabled, onMenuOpen, children, ...rest } = props;
|
||||
const id = `hotbarIcon-${uid}`;
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
|
||||
@ -134,12 +134,6 @@ export function HotbarIcon(props: Props) {
|
||||
toggleMenu();
|
||||
}}
|
||||
close={() => toggleMenu()}>
|
||||
<MenuItem key="remove-from-hotbar" onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
remove(uid);
|
||||
}}>
|
||||
<Icon material="clear" small interactive={true} title="Remove from hotbar"/> Remove from Hotbar
|
||||
</MenuItem>
|
||||
{ menuItems.map((menuItem) => {
|
||||
return (
|
||||
<MenuItem key={menuItem.title} onClick={() => onMenuItemClick(menuItem) }>
|
||||
@ -150,8 +144,4 @@ export function HotbarIcon(props: Props) {
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
HotbarIcon.defaultProps = {
|
||||
menuItems: []
|
||||
};
|
||||
});
|
||||
|
||||
@ -28,11 +28,12 @@ import { HotbarEntityIcon } from "./hotbar-entity-icon";
|
||||
import { cssNames, IClassName } from "../../utils";
|
||||
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||
import { defaultHotbarCells, HotbarItem, HotbarStore } from "../../../common/hotbar-store";
|
||||
import { catalogEntityRunContext } from "../../api/catalog-entity";
|
||||
import { CatalogEntity, catalogEntityRunContext } from "../../api/catalog-entity";
|
||||
import { DragDropContext, Draggable, Droppable, DropResult } from "react-beautiful-dnd";
|
||||
import { HotbarSelector } from "./hotbar-selector";
|
||||
import { HotbarCell } from "./hotbar-cell";
|
||||
import { HotbarIcon } from "./hotbar-icon";
|
||||
import { computed } from "mobx";
|
||||
|
||||
interface Props {
|
||||
className?: IClassName;
|
||||
@ -64,6 +65,10 @@ export class HotbarMenu extends React.Component<Props> {
|
||||
const from = parseInt(source.droppableId);
|
||||
const to = parseInt(destination.droppableId);
|
||||
|
||||
if (!this.hotbar.items[from]) { // Dropped non-persisted item
|
||||
this.hotbar.items[from] = this.items[from];
|
||||
}
|
||||
|
||||
HotbarStore.getInstance().restackItems(from, to);
|
||||
}
|
||||
|
||||
@ -73,14 +78,38 @@ export class HotbarMenu extends React.Component<Props> {
|
||||
hotbar.removeFromHotbar(uid);
|
||||
}
|
||||
|
||||
addItem(entity: CatalogEntity, index = -1) {
|
||||
const hotbar = HotbarStore.getInstance();
|
||||
|
||||
hotbar.addToHotbar(entity, index);
|
||||
}
|
||||
|
||||
getMoveAwayDirection(entityId: string, cellIndex: number) {
|
||||
const draggableItemIndex = this.hotbar.items.findIndex(item => item?.entity.uid == entityId);
|
||||
|
||||
return draggableItemIndex > cellIndex ? "animateDown" : "animateUp";
|
||||
}
|
||||
|
||||
@computed get items() {
|
||||
const items = this.hotbar.items;
|
||||
const activeEntity = catalogEntityRegistry.activeEntity;
|
||||
|
||||
if (!activeEntity) return items;
|
||||
|
||||
const emptyIndex = items.indexOf(null);
|
||||
|
||||
if (emptyIndex === -1) return items;
|
||||
if (items.find((item) => item?.entity?.uid === activeEntity.metadata.uid)) return items;
|
||||
|
||||
const modifiedItems = [...items];
|
||||
|
||||
modifiedItems.splice(emptyIndex, 1, { entity: { uid: activeEntity.metadata.uid }});
|
||||
|
||||
return modifiedItems;
|
||||
}
|
||||
|
||||
renderGrid() {
|
||||
return this.hotbar.items.map((item, index) => {
|
||||
return this.items.map((item, index) => {
|
||||
const entity = this.getEntity(item);
|
||||
|
||||
return (
|
||||
@ -116,17 +145,18 @@ export class HotbarMenu extends React.Component<Props> {
|
||||
{entity ? (
|
||||
<HotbarEntityIcon
|
||||
key={index}
|
||||
index={index}
|
||||
entity={entity}
|
||||
onClick={() => entity.onRun(catalogEntityRunContext)}
|
||||
className={cssNames({ isDragging: snapshot.isDragging })}
|
||||
remove={this.removeItem}
|
||||
add={this.addItem}
|
||||
/>
|
||||
) : (
|
||||
<HotbarIcon
|
||||
uid={item.entity.uid}
|
||||
title={item.entity.name}
|
||||
source={item.entity.source}
|
||||
remove={this.removeItem}
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
@ -151,7 +181,7 @@ export class HotbarMenu extends React.Component<Props> {
|
||||
return (
|
||||
<div className={cssNames("HotbarMenu flex column", className)}>
|
||||
<div className="HotbarItems flex column gaps">
|
||||
<DragDropContext onDragEnd={this.onDragEnd}>
|
||||
<DragDropContext onDragEnd={this.onDragEnd.bind(this)}>
|
||||
{this.renderGrid()}
|
||||
</DragDropContext>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user