1
0
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:
Jari Kolehmainen 2021-05-18 13:13:33 +03:00 committed by GitHub
parent d08d5a24d7
commit 752f49821a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 109 additions and 111 deletions

View File

@ -20,7 +20,6 @@
*/ */
import mockFs from "mock-fs"; import mockFs from "mock-fs";
import { CatalogEntityItem } from "../../renderer/components/+catalog/catalog-entity.store";
import { ClusterStore } from "../cluster-store"; import { ClusterStore } from "../cluster-store";
import { HotbarStore } from "../hotbar-store"; import { HotbarStore } from "../hotbar-store";
@ -159,10 +158,9 @@ describe("HotbarStore", () => {
it("adds items", () => { it("adds items", () => {
const hotbarStore = HotbarStore.createInstance(); const hotbarStore = HotbarStore.createInstance();
const entity = new CatalogEntityItem(testCluster);
hotbarStore.load(); hotbarStore.load();
hotbarStore.addToHotbar(entity); hotbarStore.addToHotbar(testCluster);
const items = hotbarStore.getActive().items.filter(Boolean); const items = hotbarStore.getActive().items.filter(Boolean);
expect(items.length).toEqual(1); expect(items.length).toEqual(1);
@ -170,10 +168,9 @@ describe("HotbarStore", () => {
it("removes items", () => { it("removes items", () => {
const hotbarStore = HotbarStore.createInstance(); const hotbarStore = HotbarStore.createInstance();
const entity = new CatalogEntityItem(testCluster);
hotbarStore.load(); hotbarStore.load();
hotbarStore.addToHotbar(entity); hotbarStore.addToHotbar(testCluster);
hotbarStore.removeFromHotbar("test"); hotbarStore.removeFromHotbar("test");
const items = hotbarStore.getActive().items.filter(Boolean); const items = hotbarStore.getActive().items.filter(Boolean);
@ -182,10 +179,9 @@ describe("HotbarStore", () => {
it("does nothing if removing with invalid uid", () => { it("does nothing if removing with invalid uid", () => {
const hotbarStore = HotbarStore.createInstance(); const hotbarStore = HotbarStore.createInstance();
const entity = new CatalogEntityItem(testCluster);
hotbarStore.load(); hotbarStore.load();
hotbarStore.addToHotbar(entity); hotbarStore.addToHotbar(testCluster);
hotbarStore.removeFromHotbar("invalid uid"); hotbarStore.removeFromHotbar("invalid uid");
const items = hotbarStore.getActive().items.filter(Boolean); const items = hotbarStore.getActive().items.filter(Boolean);
@ -194,14 +190,11 @@ describe("HotbarStore", () => {
it("moves item to empty cell", () => { it("moves item to empty cell", () => {
const hotbarStore = HotbarStore.createInstance(); const hotbarStore = HotbarStore.createInstance();
const test = new CatalogEntityItem(testCluster);
const minikube = new CatalogEntityItem(minikubeCluster);
const aws = new CatalogEntityItem(awsCluster);
hotbarStore.load(); hotbarStore.load();
hotbarStore.addToHotbar(test); hotbarStore.addToHotbar(testCluster);
hotbarStore.addToHotbar(minikube); hotbarStore.addToHotbar(minikubeCluster);
hotbarStore.addToHotbar(aws); hotbarStore.addToHotbar(awsCluster);
expect(hotbarStore.getActive().items[5]).toBeNull(); expect(hotbarStore.getActive().items[5]).toBeNull();
@ -213,14 +206,11 @@ describe("HotbarStore", () => {
it("moves items down", () => { it("moves items down", () => {
const hotbarStore = HotbarStore.createInstance(); const hotbarStore = HotbarStore.createInstance();
const test = new CatalogEntityItem(testCluster);
const minikube = new CatalogEntityItem(minikubeCluster);
const aws = new CatalogEntityItem(awsCluster);
hotbarStore.load(); hotbarStore.load();
hotbarStore.addToHotbar(test); hotbarStore.addToHotbar(testCluster);
hotbarStore.addToHotbar(minikube); hotbarStore.addToHotbar(minikubeCluster);
hotbarStore.addToHotbar(aws); hotbarStore.addToHotbar(awsCluster);
// aws -> test // aws -> test
hotbarStore.restackItems(2, 0); hotbarStore.restackItems(2, 0);
@ -232,14 +222,11 @@ describe("HotbarStore", () => {
it("moves items up", () => { it("moves items up", () => {
const hotbarStore = HotbarStore.createInstance(); const hotbarStore = HotbarStore.createInstance();
const test = new CatalogEntityItem(testCluster);
const minikube = new CatalogEntityItem(minikubeCluster);
const aws = new CatalogEntityItem(awsCluster);
hotbarStore.load(); hotbarStore.load();
hotbarStore.addToHotbar(test); hotbarStore.addToHotbar(testCluster);
hotbarStore.addToHotbar(minikube); hotbarStore.addToHotbar(minikubeCluster);
hotbarStore.addToHotbar(aws); hotbarStore.addToHotbar(awsCluster);
// test -> aws // test -> aws
hotbarStore.restackItems(0, 2); hotbarStore.restackItems(0, 2);
@ -251,10 +238,9 @@ describe("HotbarStore", () => {
it("does nothing when item moved to same cell", () => { it("does nothing when item moved to same cell", () => {
const hotbarStore = HotbarStore.createInstance(); const hotbarStore = HotbarStore.createInstance();
const test = new CatalogEntityItem(testCluster);
hotbarStore.load(); hotbarStore.load();
hotbarStore.addToHotbar(test); hotbarStore.addToHotbar(testCluster);
hotbarStore.restackItems(0, 0); hotbarStore.restackItems(0, 0);
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("test"); expect(hotbarStore.getActive().items[0].entity.uid).toEqual("test");
@ -262,15 +248,12 @@ describe("HotbarStore", () => {
it("new items takes first empty cell", () => { it("new items takes first empty cell", () => {
const hotbarStore = HotbarStore.createInstance(); const hotbarStore = HotbarStore.createInstance();
const test = new CatalogEntityItem(testCluster);
const minikube = new CatalogEntityItem(minikubeCluster);
const aws = new CatalogEntityItem(awsCluster);
hotbarStore.load(); hotbarStore.load();
hotbarStore.addToHotbar(test); hotbarStore.addToHotbar(testCluster);
hotbarStore.addToHotbar(aws); hotbarStore.addToHotbar(awsCluster);
hotbarStore.restackItems(0, 3); hotbarStore.restackItems(0, 3);
hotbarStore.addToHotbar(minikube); hotbarStore.addToHotbar(minikubeCluster);
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("minikube"); expect(hotbarStore.getActive().items[0].entity.uid).toEqual("minikube");
}); });
@ -282,10 +265,9 @@ describe("HotbarStore", () => {
console.error = jest.fn(); console.error = jest.fn();
const hotbarStore = HotbarStore.createInstance(); const hotbarStore = HotbarStore.createInstance();
const test = new CatalogEntityItem(testCluster);
hotbarStore.load(); hotbarStore.load();
hotbarStore.addToHotbar(test); hotbarStore.addToHotbar(testCluster);
expect(() => hotbarStore.restackItems(-5, 0)).toThrow(); expect(() => hotbarStore.restackItems(-5, 0)).toThrow();
expect(() => hotbarStore.restackItems(2, -1)).toThrow(); expect(() => hotbarStore.restackItems(2, -1)).toThrow();

View File

@ -23,8 +23,8 @@ import { action, comparer, observable, toJS } from "mobx";
import { BaseStore } from "./base-store"; import { BaseStore } from "./base-store";
import migrations from "../migrations/hotbar-store"; import migrations from "../migrations/hotbar-store";
import * as uuid from "uuid"; import * as uuid from "uuid";
import { CatalogEntityItem } from "../renderer/components/+catalog/catalog-entity.store";
import isNull from "lodash/isNull"; import isNull from "lodash/isNull";
import { CatalogEntity } from "./catalog";
export interface HotbarItem { export interface HotbarItem {
entity: { entity: {
@ -151,15 +151,15 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
} }
@action @action
addToHotbar(item: CatalogEntityItem, cellIndex = -1) { addToHotbar(item: CatalogEntity, cellIndex = -1) {
const hotbar = this.getActive(); const hotbar = this.getActive();
const newItem = { entity: { const newItem = { entity: {
uid: item.id, uid: item.metadata.uid,
name: item.name, name: item.metadata.name,
source: item.source 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; return;
} }

View File

@ -68,7 +68,7 @@ export class Catalog extends React.Component {
} }
addToHotbar(item: CatalogEntityItem): void { addToHotbar(item: CatalogEntityItem): void {
HotbarStore.getInstance().addToHotbar(item); HotbarStore.getInstance().addToHotbar(item.entity);
} }
onDetails(item: CatalogEntityItem) { onDetails(item: CatalogEntityItem) {
@ -137,7 +137,7 @@ export class Catalog extends React.Component {
return ( return (
<MenuActions onOpen={() => item.onContextMenuOpen(this.contextMenu)}> <MenuActions onOpen={() => item.onContextMenuOpen(this.contextMenu)}>
<MenuItem key="add-to-hotbar" onClick={() => this.addToHotbar(item) }> <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> </MenuItem>
{ {
menuItems.map((menuItem, index) => ( menuItems.map((menuItem, index) => (

View File

@ -18,26 +18,26 @@
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * 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. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import "./hotbar-icon.scss";
import React, { DOMAttributes } from "react"; import React, { DOMAttributes } from "react";
import { observable } from "mobx"; import { observable } from "mobx";
import { observer } from "mobx-react"; 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 { catalogCategoryRegistry } from "../../api/catalog-category-registry";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
import { navigate } from "../../navigation"; import { navigate } from "../../navigation";
import { cssNames, IClassName } from "../../utils"; import { cssNames, IClassName } from "../../utils";
import { ConfirmDialog } from "../confirm-dialog";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { HotbarIcon } from "./hotbar-icon"; import { HotbarIcon } from "./hotbar-icon";
import { HotbarStore } from "../../../common/hotbar-store";
interface Props extends DOMAttributes<HTMLElement> { interface Props extends DOMAttributes<HTMLElement> {
entity: CatalogEntity; entity: CatalogEntity;
index: number;
className?: IClassName; className?: IClassName;
errorClass?: IClassName; errorClass?: IClassName;
add: (item: CatalogEntity, index: number) => void;
remove: (uid: string) => void; remove: (uid: string) => void;
} }
@ -77,33 +77,18 @@ export class HotbarEntityIcon extends React.Component<Props> {
return catalogEntityRegistry.activeEntity?.metadata?.uid == item.getId(); return catalogEntityRegistry.activeEntity?.metadata?.uid == item.getId();
} }
onMenuItemClick(menuItem: CatalogEntityContextMenu) { isPersisted(entity: CatalogEntity) {
if (menuItem.confirm) { return HotbarStore.getInstance().getActive().items.find((item) => item?.entity?.uid === entity.metadata.uid) !== undefined;
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" })
};
} }
render() { render() {
if (!this.contextMenu) {
return null;
}
const { const {
entity, errorClass, remove, entity, errorClass, add, remove,
children, ...elemProps index, children, ...elemProps
} = this.props; } = this.props;
const className = cssNames("HotbarEntityIcon", this.props.className, { const className = cssNames("HotbarEntityIcon", this.props.className, {
interactive: true, interactive: true,
@ -113,16 +98,31 @@ export class HotbarEntityIcon extends React.Component<Props> {
const onOpen = async () => { const onOpen = async () => {
await entity.onContextMenuOpen(this.contextMenu); 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); 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 ( return (
<HotbarIcon <HotbarIcon
uid={entity.getId()} uid={entity.metadata.uid}
title={entity.getName()} title={entity.metadata.name}
source={`${entity.metadata.source || "local"}`} source={entity.metadata.source}
className={className} className={className}
active={this.isActive(entity)} active={isActive}
remove={remove}
onMenuOpen={onOpen} onMenuOpen={onOpen}
menuItems={menuItems} menuItems={menuItems}
{...elemProps} {...elemProps}

View File

@ -36,11 +36,6 @@
border-radius: 6px; border-radius: 6px;
} }
&.active {
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px var(--textColorAccent);
transition: all 0s 0.8s;
}
&.disabled { &.disabled {
opacity: 0.4; opacity: 0.4;
cursor: default; cursor: default;
@ -53,10 +48,14 @@
} }
} }
&.active, &.interactive:hover { &.isDragging {
img { box-shadow: none;
opacity: 1;
} }
div.MuiAvatar-root {
&.active {
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px var(--textColorAccent);
transition: all 0s 0.8s;
} }
&:hover { &:hover {
@ -64,12 +63,9 @@
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px #ffffff30; box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px #ffffff30;
} }
} }
&.isDragging {
box-shadow: none;
} }
> .led { .led {
position: absolute; position: absolute;
left: 3px; left: 3px;
top: 3px; top: 3px;

View File

@ -32,12 +32,12 @@ import { ConfirmDialog } from "../confirm-dialog";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { Menu, MenuItem } from "../menu"; import { Menu, MenuItem } from "../menu";
import { MaterialTooltip } from "../+catalog/material-tooltip/material-tooltip"; import { MaterialTooltip } from "../+catalog/material-tooltip/material-tooltip";
import { observer } from "mobx-react";
interface Props extends DOMAttributes<HTMLElement> { interface Props extends DOMAttributes<HTMLElement> {
uid: string; uid: string;
title: string; title: string;
source: string; source: string;
remove: (uid: string) => void;
onMenuOpen?: () => void; onMenuOpen?: () => void;
className?: IClassName; className?: IClassName;
active?: boolean; active?: boolean;
@ -84,8 +84,8 @@ function getNameParts(name: string): string[] {
return name.split(/@+/); return name.split(/@+/);
} }
export function HotbarIcon(props: Props) { export const HotbarIcon = observer(({menuItems = [], ...props}: Props) => {
const { uid, title, className, source, active, remove, disabled, menuItems, onMenuOpen, children, ...rest } = props; const { uid, title, active, className, source, disabled, onMenuOpen, children, ...rest } = props;
const id = `hotbarIcon-${uid}`; const id = `hotbarIcon-${uid}`;
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
@ -134,12 +134,6 @@ export function HotbarIcon(props: Props) {
toggleMenu(); toggleMenu();
}} }}
close={() => 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) => { { menuItems.map((menuItem) => {
return ( return (
<MenuItem key={menuItem.title} onClick={() => onMenuItemClick(menuItem) }> <MenuItem key={menuItem.title} onClick={() => onMenuItemClick(menuItem) }>
@ -150,8 +144,4 @@ export function HotbarIcon(props: Props) {
</Menu> </Menu>
</div> </div>
); );
} });
HotbarIcon.defaultProps = {
menuItems: []
};

View File

@ -28,11 +28,12 @@ import { HotbarEntityIcon } from "./hotbar-entity-icon";
import { cssNames, IClassName } from "../../utils"; import { cssNames, IClassName } from "../../utils";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
import { defaultHotbarCells, HotbarItem, HotbarStore } from "../../../common/hotbar-store"; 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 { DragDropContext, Draggable, Droppable, DropResult } from "react-beautiful-dnd";
import { HotbarSelector } from "./hotbar-selector"; import { HotbarSelector } from "./hotbar-selector";
import { HotbarCell } from "./hotbar-cell"; import { HotbarCell } from "./hotbar-cell";
import { HotbarIcon } from "./hotbar-icon"; import { HotbarIcon } from "./hotbar-icon";
import { computed } from "mobx";
interface Props { interface Props {
className?: IClassName; className?: IClassName;
@ -64,6 +65,10 @@ export class HotbarMenu extends React.Component<Props> {
const from = parseInt(source.droppableId); const from = parseInt(source.droppableId);
const to = parseInt(destination.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); HotbarStore.getInstance().restackItems(from, to);
} }
@ -73,14 +78,38 @@ export class HotbarMenu extends React.Component<Props> {
hotbar.removeFromHotbar(uid); hotbar.removeFromHotbar(uid);
} }
addItem(entity: CatalogEntity, index = -1) {
const hotbar = HotbarStore.getInstance();
hotbar.addToHotbar(entity, index);
}
getMoveAwayDirection(entityId: string, cellIndex: number) { getMoveAwayDirection(entityId: string, cellIndex: number) {
const draggableItemIndex = this.hotbar.items.findIndex(item => item?.entity.uid == entityId); const draggableItemIndex = this.hotbar.items.findIndex(item => item?.entity.uid == entityId);
return draggableItemIndex > cellIndex ? "animateDown" : "animateUp"; 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() { renderGrid() {
return this.hotbar.items.map((item, index) => { return this.items.map((item, index) => {
const entity = this.getEntity(item); const entity = this.getEntity(item);
return ( return (
@ -116,17 +145,18 @@ export class HotbarMenu extends React.Component<Props> {
{entity ? ( {entity ? (
<HotbarEntityIcon <HotbarEntityIcon
key={index} key={index}
index={index}
entity={entity} entity={entity}
onClick={() => entity.onRun(catalogEntityRunContext)} onClick={() => entity.onRun(catalogEntityRunContext)}
className={cssNames({ isDragging: snapshot.isDragging })} className={cssNames({ isDragging: snapshot.isDragging })}
remove={this.removeItem} remove={this.removeItem}
add={this.addItem}
/> />
) : ( ) : (
<HotbarIcon <HotbarIcon
uid={item.entity.uid} uid={item.entity.uid}
title={item.entity.name} title={item.entity.name}
source={item.entity.source} source={item.entity.source}
remove={this.removeItem}
disabled disabled
/> />
)} )}
@ -151,7 +181,7 @@ export class HotbarMenu extends React.Component<Props> {
return ( return (
<div className={cssNames("HotbarMenu flex column", className)}> <div className={cssNames("HotbarMenu flex column", className)}>
<div className="HotbarItems flex column gaps"> <div className="HotbarItems flex column gaps">
<DragDropContext onDragEnd={this.onDragEnd}> <DragDropContext onDragEnd={this.onDragEnd.bind(this)}>
{this.renderGrid()} {this.renderGrid()}
</DragDropContext> </DragDropContext>
</div> </div>