From 4d70fffe171f9b8386763cc5e13e4dee94e15ff4 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Thu, 6 May 2021 11:52:10 +0300 Subject: [PATCH] Abstract out HotbarEntityIcon from HotbarIcon Signed-off-by: Alex Andreev --- .../components/hotbar/hotbar-entity-icon.tsx | 115 +++++++++ .../components/hotbar/hotbar-icon.tsx | 230 +++++++----------- .../components/hotbar/hotbar-menu.tsx | 76 +++--- 3 files changed, 237 insertions(+), 184 deletions(-) create mode 100644 src/renderer/components/hotbar/hotbar-entity-icon.tsx diff --git a/src/renderer/components/hotbar/hotbar-entity-icon.tsx b/src/renderer/components/hotbar/hotbar-entity-icon.tsx new file mode 100644 index 0000000000..52a93990ee --- /dev/null +++ b/src/renderer/components/hotbar/hotbar-entity-icon.tsx @@ -0,0 +1,115 @@ +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 { 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"; + +interface Props extends DOMAttributes { + entity: CatalogEntity; + className?: IClassName; + errorClass?: IClassName; + remove: (uid: string) => void; +} + +@observer +export class HotbarEntityIcon extends React.Component { + @observable.deep private contextMenu: CatalogEntityContextMenuContext; + + componentDidMount() { + this.contextMenu = { + menuItems: [], + navigate: (url: string) => navigate(url) + }; + } + + get kindIcon() { + const className = "badge"; + const category = catalogCategoryRegistry.getCategoryForEntity(this.props.entity); + + if (!category) { + return ; + } + + if (category.metadata.icon.includes("; + } else { + return ; + } + } + + get ledIcon() { + const className = cssNames("led", { online: this.props.entity.status.phase == "connected"}); // TODO: make it more generic + + return
; + } + + isActive(item: CatalogEntity) { + 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" }) + }; + } + + render() { + const { + entity, errorClass, remove, + children, ...elemProps + } = this.props; + const className = cssNames("HotbarEntityIcon", this.props.className, { + interactive: true, + active: this.isActive(entity), + disabled: !entity + }); + const onOpen = async () => { + await entity.onContextMenuOpen(this.contextMenu); + }; + const menuItems = this.contextMenu?.menuItems.filter((menuItem) => !menuItem.onlyVisibleForSource || menuItem.onlyVisibleForSource === entity.metadata.source); + + return ( + + { this.ledIcon } + { this.kindIcon } + + ); + } +} diff --git a/src/renderer/components/hotbar/hotbar-icon.tsx b/src/renderer/components/hotbar/hotbar-icon.tsx index 82be0ee88c..b858934634 100644 --- a/src/renderer/components/hotbar/hotbar-icon.tsx +++ b/src/renderer/components/hotbar/hotbar-icon.tsx @@ -1,27 +1,50 @@ import "./hotbar-icon.scss"; -import React, { DOMAttributes } from "react"; -import { observer } from "mobx-react"; -import { cssNames, IClassName, iter } from "../../utils"; -import { Tooltip } from "../tooltip"; +import React, { DOMAttributes, useState } from "react"; import { Avatar } from "@material-ui/core"; -import { CatalogEntity, CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../../common/catalog"; -import { Menu, MenuItem } from "../menu"; -import { Icon } from "../icon"; -import { computed, observable } from "mobx"; -import { navigate } from "../../navigation"; -import { HotbarStore } from "../../../common/hotbar-store"; -import { ConfirmDialog } from "../confirm-dialog"; import randomColor from "randomcolor"; -import { catalogCategoryRegistry } from "../../api/catalog-category-registry"; import GraphemeSplitter from "grapheme-splitter"; +import { CatalogEntityContextMenu } from "../../../common/catalog"; +import { cssNames, IClassName, iter } from "../../utils"; +import { ConfirmDialog } from "../confirm-dialog"; +import { Icon } from "../icon"; +import { Menu, MenuItem } from "../menu"; +import { MaterialTooltip } from "../+catalog/material-tooltip/material-tooltip"; + interface Props extends DOMAttributes { - entity: CatalogEntity; - index: number; + uid: string; + title: string; + source: string; + remove: (uid: string) => void; + onMenuOpen?: () => void; className?: IClassName; - errorClass?: IClassName; - isActive?: boolean; + active?: boolean; + menuItems?: CatalogEntityContextMenu[]; + disabled?: boolean; +} + +function generateAvatarStyle(seed: string): React.CSSProperties { + return { + "backgroundColor": randomColor({ seed, luminosity: "dark" }) + }; +} + +function onMenuItemClick(menuItem: CatalogEntityContextMenu) { + if (menuItem.confirm) { + ConfirmDialog.open({ + okButtonProps: { + primary: false, + accent: true, + }, + ok: () => { + menuItem.onClick(); + }, + message: menuItem.confirm.message + }); + } else { + menuItem.onClick(); + } } function getNameParts(name: string): string[] { @@ -40,20 +63,17 @@ function getNameParts(name: string): string[] { return name.split(/@+/); } -@observer -export class HotbarIcon extends React.Component { - @observable.deep private contextMenu: CatalogEntityContextMenuContext; - @observable menuOpen = false; +export function HotbarIcon(props: Props) { + const { uid, title, className, source, active, remove, disabled, menuItems, onMenuOpen, children, ...rest } = props; + const id = `hotbarIcon-${uid}`; + const [menuOpen, setMenuOpen] = useState(false); - componentDidMount() { - this.contextMenu = { - menuItems: [], - navigate: (url: string) => navigate(url) - }; - } + const toggleMenu = () => { + setMenuOpen(!menuOpen); + }; - @computed get iconString() { - const [rawFirst, rawSecond, rawThird] = getNameParts(this.props.entity.metadata.name); + const getIconString = () => { + const [rawFirst, rawSecond, rawThird] = getNameParts(title); const splitter = new GraphemeSplitter(); const first = splitter.iterateGraphemes(rawFirst); const second = rawSecond ? splitter.iterateGraphemes(rawSecond): first; @@ -64,114 +84,50 @@ export class HotbarIcon extends React.Component { ...iter.take(second, 1), ...iter.take(third, 1), ].filter(Boolean).join(""); - } + }; - get kindIcon() { - const className = "badge"; - const category = catalogCategoryRegistry.getCategoryForEntity(this.props.entity); - - if (!category) { - return ; - } - - if (category.metadata.icon.includes("; - } else { - return ; - } - } - - get ledIcon() { - const className = cssNames("led", { online: this.props.entity.status.phase == "connected"}); // TODO: make it more generic - - return
; - } - - toggleMenu() { - this.menuOpen = !this.menuOpen; - } - - remove(item: CatalogEntity) { - const hotbar = HotbarStore.getInstance(); - - hotbar.removeFromHotbar(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" }) - }; - } - - render() { - const { - entity, errorClass, isActive, - children, ...elemProps - } = this.props; - const entityIconId = `hotbar-icon-${this.props.index}`; - const className = cssNames("HotbarIcon flex inline", this.props.className, { - interactive: true, - active: isActive, - }); - const onOpen = async () => { - await entity.onContextMenuOpen(this.contextMenu); - this.toggleMenu(); - }; - const menuItems = this.contextMenu?.menuItems.filter((menuItem) => !menuItem.onlyVisibleForSource || menuItem.onlyVisibleForSource === entity.metadata.source); - - return ( -
- {entity.metadata.name} ({entity.metadata.source || "local"}) - - {this.iconString} - - { this.ledIcon } - { this.kindIcon } - onOpen()} - close={() => this.toggleMenu()}> - this.remove(entity) }> - Remove from Hotbar - - { this.contextMenu && menuItems.map((menuItem) => { - return ( - this.onMenuItemClick(menuItem) }> - {menuItem.title} - - ); - })} - - {children} -
- ); - } + return ( +
+ +
+ + {getIconString()} + + {children} +
+
+ { + onMenuOpen?.(); + toggleMenu(); + }} + close={() => toggleMenu()}> + remove(uid) }> + Remove from Hotbar + + { menuItems.map((menuItem) => { + return ( + onMenuItemClick(menuItem) }> + {menuItem.title} + + ); + })} + +
+ ); } + +HotbarIcon.defaultProps = { + menuItems: [] +}; diff --git a/src/renderer/components/hotbar/hotbar-menu.tsx b/src/renderer/components/hotbar/hotbar-menu.tsx index 29008f073c..e8dbed3561 100644 --- a/src/renderer/components/hotbar/hotbar-menu.tsx +++ b/src/renderer/components/hotbar/hotbar-menu.tsx @@ -1,15 +1,17 @@ import "./hotbar-menu.scss"; import "./hotbar.commands"; -import React, { HTMLAttributes, ReactNode, useState } from "react"; +import React from "react"; import { observer } from "mobx-react"; -import { HotbarIcon } from "./hotbar-icon"; +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 { CatalogEntity, catalogEntityRunContext } from "../../api/catalog-entity"; +import { 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"; interface Props { className?: IClassName; @@ -21,10 +23,6 @@ export class HotbarMenu extends React.Component { return HotbarStore.getInstance().getActive(); } - isActive(item: CatalogEntity) { - return catalogEntityRegistry.activeEntity?.metadata?.uid == item.getId(); - } - getEntity(item: HotbarItem) { const hotbar = HotbarStore.getInstance().getActive(); @@ -48,6 +46,12 @@ export class HotbarMenu extends React.Component { HotbarStore.getInstance().restackItems(from, to); } + removeItem(uid: string) { + const hotbar = HotbarStore.getInstance(); + + hotbar.removeFromHotbar(uid); + } + renderGrid() { return this.hotbar.items.map((item, index) => { const entity = this.getEntity(item); @@ -62,8 +66,8 @@ export class HotbarMenu extends React.Component { className={cssNames({ isDraggingOver: snapshot.isDraggingOver })} {...provided.droppableProps} > - {entity && ( - + {item && ( + {(provided, snapshot) => { const style = { zIndex: defaultHotbarCells - index, @@ -79,14 +83,22 @@ export class HotbarMenu extends React.Component { {...provided.dragHandleProps} style={style} > - entity.onRun(catalogEntityRunContext)} - className={cssNames({ isDragging: snapshot.isDragging })} - /> + {entity ? ( + entity?.onRun(catalogEntityRunContext)} + className={cssNames({ isDragging: snapshot.isDragging })} + remove={this.removeItem} + /> + ) : ( + + )}
); }} @@ -117,33 +129,3 @@ export class HotbarMenu extends React.Component { ); } } - -interface HotbarCellProps extends HTMLAttributes { - children?: ReactNode; - index: number; - innerRef?: React.LegacyRef; -} - -function HotbarCell({ innerRef, children, className, ...rest }: HotbarCellProps) { - const [animating, setAnimating] = useState(false); - const onAnimationEnd = () => { setAnimating(false); }; - const onClick = () => { - if (className.includes("isDraggingOver")) { - return; - } - - setAnimating(true); - }; - - return ( -
- {children} -
- ); -}