diff --git a/src/common/hotbar-store.ts b/src/common/hotbar-store.ts index 478279d9c0..ea2d7a4038 100644 --- a/src/common/hotbar-store.ts +++ b/src/common/hotbar-store.ts @@ -2,6 +2,9 @@ import { action, comparer, observable, toJS } from "mobx"; import { BaseStore } from "./base-store"; import migrations from "../migrations/hotbar-store"; import * as uuid from "uuid"; +import { CatalogEntity } from "./catalog-entity"; +import { CatalogEntityItem } from "../renderer/components/+catalog/catalog-entity.store"; +import isNull from "lodash/isNull"; export interface HotbarItem { entity: { @@ -63,7 +66,7 @@ export class HotbarStore extends BaseStore { this.hotbars = [{ id: uuid.v4(), name: "Default", - items: [] + items: [], }]; } else { this.hotbars = data.hotbars; @@ -115,6 +118,52 @@ export class HotbarStore extends BaseStore { } } + addToHotbar(item: CatalogEntityItem, cellIndex = -1) { + const hotbar = this.getActive(); + const newItem = { entity: { uid: item.id }}; + + if (hotbar.items.find(i => i?.entity.uid === item.id)) { + return; + } + + if (cellIndex == -1) { + // Add item to empty cell + const emptyCellIndex = hotbar.items.findIndex(isNull); + + if (emptyCellIndex != -1) { + hotbar.items[emptyCellIndex] = newItem; + } else { + // Add new item to the end of list + hotbar.items.push(newItem); + } + } else { + hotbar.items[cellIndex] = newItem; + } + } + + removeFromHotbar(item: CatalogEntity) { + const hotbar = this.getActive(); + const index = hotbar.items.findIndex((i) => i?.entity.uid === item.getId()); + + if (index == -1) { + return; + } + + hotbar.items[index] = null; + } + + addEmptyCell() { + const hotbar = this.getActive(); + + hotbar.items.push(null); + } + + removeEmptyCell(index: number) { + const hotbar = this.getActive(); + + hotbar.items.splice(index, 1); + } + switchToPrevious() { const hotbarStore = HotbarStore.getInstance(); let index = hotbarStore.activeHotbarIndex - 1; diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index 478a282698..806c103dd9 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -56,14 +56,8 @@ export class Catalog extends React.Component { }, 2_000); } - addToHotbar(item: CatalogEntityItem) { - const hotbar = HotbarStore.getInstance().getActive(); - - if (!hotbar) { - return; - } - - hotbar.items.push({ entity: { uid: item.id }}); + addToHotbar(item: CatalogEntityItem): void { + HotbarStore.getInstance().addToHotbar(item); } onDetails(item: CatalogEntityItem) { diff --git a/src/renderer/components/hotbar/hotbar-icon.scss b/src/renderer/components/hotbar/hotbar-icon.scss index bce7070ad1..587139cc15 100644 --- a/src/renderer/components/hotbar/hotbar-icon.scss +++ b/src/renderer/components/hotbar/hotbar-icon.scss @@ -2,9 +2,7 @@ .HotbarIcon { --size: 37px; - position: relative; - border-radius: 8px; - padding: 2px; + border-radius: 4px; user-select: none; cursor: pointer; @@ -23,8 +21,7 @@ } &.active { - margin-left: -3px; - border: 3px solid #fff; + box-shadow: 0 0 0 3px var(--clusterMenuBackground), 0px 0px 0 6px white; } &.active, &.interactive:hover { diff --git a/src/renderer/components/hotbar/hotbar-icon.tsx b/src/renderer/components/hotbar/hotbar-icon.tsx index cbf77fba44..87cb06bebf 100644 --- a/src/renderer/components/hotbar/hotbar-icon.tsx +++ b/src/renderer/components/hotbar/hotbar-icon.tsx @@ -59,14 +59,10 @@ export class HotbarIcon extends React.Component { this.menuOpen = !this.menuOpen; } - removeFromHotbar(item: CatalogEntity) { - const hotbar = HotbarStore.getInstance().getActive(); + remove(item: CatalogEntity) { + const hotbar = HotbarStore.getInstance(); - if (!hotbar) { - return; - } - - hotbar.items = hotbar.items.filter((i) => i.entity.uid !== item.metadata.uid); + hotbar.removeFromHotbar(item); } onMenuItemClick(menuItem: CatalogEntityContextMenu) { @@ -115,7 +111,7 @@ export class HotbarIcon extends React.Component { position={{right: true, bottom: true }} // FIXME: position does not work open={() => onOpen()} close={() => this.toggleMenu()}> - this.removeFromHotbar(entity) }> + this.remove(entity) }> Remove from Hotbar { this.contextMenu && menuItems.map((menuItem) => { diff --git a/src/renderer/components/hotbar/hotbar-menu.scss b/src/renderer/components/hotbar/hotbar-menu.scss index e8cf6efc1a..7ced9c4b8b 100644 --- a/src/renderer/components/hotbar/hotbar-menu.scss +++ b/src/renderer/components/hotbar/hotbar-menu.scss @@ -4,22 +4,30 @@ position: relative; text-align: center; background: $clusterMenuBackground; - border-right: 1px solid $clusterMenuBorderColor; - padding: $spacing 0; + padding: 28px 0; min-width: 75px; .is-mac &:before { content: ""; - height: 20px; // extra spacing for mac-os "traffic-light" buttons + height: 4px; // extra spacing for mac-os "traffic-light" buttons } - .items { - padding: 0 $spacing; // extra spacing for cluster-icon's badge - margin-bottom: $margin; - overflow: visible; + .HotbarItems { + --cellWidth: 40px; + --cellHeight: 56px; - &:empty { - display: none; + box-sizing: content-box; + margin: 0 auto; + height: 100%; + @include hidden-scrollbar; + + .HotbarCell { + width: var(--cellWidth); + height: 40px; + min-height: 40px; + margin: 8px; + background: var(--sidebarBackground); + border-radius: 4px; } } @@ -32,4 +40,24 @@ cursor: pointer; } } + + .AddCellButton { + width: 40px; + height: 40px; + min-height: 40px; + margin: 8px auto; + background-color: transparent; + color: var(--textColorDimmed); + border-radius: 4px; + transition: all 0.2s; + cursor: pointer; + + &:hover { + background-color: var(--sidebarBackground); + } + + .Icon { + --size: 24px; + } + } } diff --git a/src/renderer/components/hotbar/hotbar-menu.tsx b/src/renderer/components/hotbar/hotbar-menu.tsx index 197671b034..b19668cf25 100644 --- a/src/renderer/components/hotbar/hotbar-menu.tsx +++ b/src/renderer/components/hotbar/hotbar-menu.tsx @@ -1,17 +1,18 @@ import "./hotbar-menu.scss"; import "./hotbar.commands"; -import React from "react"; -import { observer } from "mobx-react"; +import React, { ReactNode } from "react"; +import { disposeOnUnmount, observer } from "mobx-react"; import { HotbarIcon } from "./hotbar-icon"; -import { cssNames, IClassName } from "../../utils"; +import { cssNames, cssVar, IClassName } from "../../utils"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; -import { HotbarStore } from "../../../common/hotbar-store"; +import { HotbarItem, HotbarStore } from "../../../common/hotbar-store"; import { catalogEntityRunContext } from "../../api/catalog-entity"; import { Icon } from "../icon"; import { Badge } from "../badge"; import { CommandOverlay } from "../command-palette"; import { HotbarSwitchCommand } from "./hotbar-switch-command"; +import { action, reaction } from "mobx"; interface Props { className?: IClassName; @@ -19,14 +20,24 @@ interface Props { @observer export class HotbarMenu extends React.Component { - get hotbarItems() { + componentDidMount() { + disposeOnUnmount(this, [ + reaction(() => this.hotbar, () => this.createInitialCells(), { fireImmediately: true }) + ]); + } + + get hotbar() { + return HotbarStore.getInstance().getActive(); + } + + getEntity(item: HotbarItem) { const hotbar = HotbarStore.getInstance().getActive(); if (!hotbar) { - return []; + return null; } - return hotbar.items.map((item) => catalogEntityRegistry.items.find((entity) => entity.metadata.uid === item.entity.uid)).filter(Boolean); + return item ? catalogEntityRegistry.items.find((entity) => entity.metadata.uid === item.entity.uid) : null; } previous() { @@ -41,24 +52,59 @@ export class HotbarMenu extends React.Component { CommandOverlay.open(); } + @action + createInitialCells() { + if (this.hotbar.items.length) { + return; + } + + const element = document.querySelector(".HotbarItems"); + const height = element.offsetHeight; + const cellHeight = cssVar(element).get("--cellHeight").toString(); + const cellsFit = Math.floor(height / parseInt(cellHeight)) - 1; + + this.hotbar.items = [...Array.from(Array(cellsFit).fill(null))]; + } + + renderGrid() { + if (!this.hotbar.items.length) return; + + return this.hotbar.items.map((item, index) => { + const entity = this.getEntity(item); + + return ( + + {entity && ( + entity.onRun(catalogEntityRunContext)} + /> + )} + + ); + }); + } + + renderAddCellButton() { + return ( + + ); + } + render() { const { className } = this.props; const hotbarIndex = HotbarStore.getInstance().activeHotbarIndex + 1; return (
-
- {this.hotbarItems.map((entity, index) => { - return ( - entity.onRun(catalogEntityRunContext)} - /> - ); - })} +
+ {this.renderGrid()} + {this.renderAddCellButton()}
this.previous()} /> @@ -71,3 +117,13 @@ export class HotbarMenu extends React.Component { ); } } + +interface HotbarCellProps { + children?: ReactNode; +} + +function HotbarCell(props: HotbarCellProps) { + return ( +
{props.children}
+ ); +}