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 { 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();

View File

@ -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;
}

View File

@ -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) => (

View File

@ -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}

View File

@ -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;

View File

@ -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: []
};
});

View File

@ -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>