mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Use new EntityIcon for non-hotbar uses
- Allow executing entity's onRun from catalog Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
baef6944aa
commit
b56ce826b0
@ -28,7 +28,7 @@ import { action, makeObservable, observable, reaction, runInAction, when } from
|
||||
import { CatalogEntityItem, CatalogEntityStore } from "./catalog-entity.store";
|
||||
import { navigate } from "../../navigation";
|
||||
import { MenuItem, MenuActions } from "../menu";
|
||||
import type { CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
|
||||
import { CatalogEntityContextMenu, CatalogEntityContextMenuContext, catalogEntityRunContext } from "../../api/catalog-entity";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { catalogCategoryRegistry, CatalogEntity } from "../../../common/catalog";
|
||||
@ -41,7 +41,7 @@ import { makeCss } from "../../../common/utils/makeCss";
|
||||
import { CatalogEntityDetails } from "./catalog-entity-details";
|
||||
import { catalogURL, CatalogViewRouteParam } from "../../../common/routes";
|
||||
import { CatalogMenu } from "./catalog-menu";
|
||||
import { HotbarIcon } from "../hotbar/hotbar-icon";
|
||||
import { EntityIcon } from "../entity-icon";
|
||||
|
||||
export const previousActiveTab = createAppStorage("catalog-previous-active-tab", "");
|
||||
|
||||
@ -172,13 +172,14 @@ export class Catalog extends React.Component<Props> {
|
||||
|
||||
renderIcon(item: CatalogEntityItem<CatalogEntity>) {
|
||||
return (
|
||||
<HotbarIcon
|
||||
uid={`catalog-icon-${item.getId()}`}
|
||||
<EntityIcon
|
||||
title={item.getName()}
|
||||
source={item.source}
|
||||
src={item.entity.spec.icon?.src}
|
||||
material={item.entity.spec.icon?.material}
|
||||
background={item.entity.spec.icon?.background}
|
||||
onClick={() => item.onRun(catalogEntityRunContext)}
|
||||
hoverWidth="1.5px"
|
||||
size={24}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -25,7 +25,7 @@ import GraphemeSplitter from "grapheme-splitter";
|
||||
import { Avatar as MaterialAvatar, AvatarTypeMap } from "@material-ui/core";
|
||||
import { iter } from "../../utils";
|
||||
|
||||
interface Props extends DOMAttributes<HTMLElement>, Partial<AvatarTypeMap> {
|
||||
export interface AvatarProps extends DOMAttributes<HTMLElement>, Partial<AvatarTypeMap> {
|
||||
title: string;
|
||||
colorHash?: string;
|
||||
width?: number;
|
||||
@ -33,6 +33,7 @@ interface Props extends DOMAttributes<HTMLElement>, Partial<AvatarTypeMap> {
|
||||
src?: string;
|
||||
className?: string;
|
||||
background?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
function getNameParts(name: string): string[] {
|
||||
@ -69,8 +70,8 @@ function getIconString(title: string) {
|
||||
].filter(Boolean).join("");
|
||||
}
|
||||
|
||||
export function Avatar(props: Props) {
|
||||
const { title, width = 32, height = 32, colorHash, children, background, ...settings } = props;
|
||||
export function Avatar(props: AvatarProps) {
|
||||
const { title, width = 32, height = 32, colorHash, children, style, background, ...settings } = props;
|
||||
|
||||
const getBackgroundColor = () => {
|
||||
if (background) {
|
||||
@ -89,7 +90,8 @@ export function Avatar(props: Props) {
|
||||
backgroundColor: getBackgroundColor(),
|
||||
width,
|
||||
height,
|
||||
textTransform: "uppercase"
|
||||
textTransform: "uppercase",
|
||||
...style,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
32
src/renderer/components/entity-icon/entity-icon.scss
Normal file
32
src/renderer/components/entity-icon/entity-icon.scss
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.EntityIcon {
|
||||
&.active {
|
||||
box-shadow: 0 0 0px var(--hover-width) var(--clusterMenuBackground), 0 0 0px calc(2*var(--hover-width)) var(--textColorAccent);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:not(.active) {
|
||||
box-shadow: 0 0 0px var(--hover-width) var(--clusterMenuBackground), 0 0 0px calc(2*var(--hover-width)) #ffffff50;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/renderer/components/entity-icon/entity-icon.tsx
Normal file
56
src/renderer/components/entity-icon/entity-icon.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* 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 "./entity-icon.scss";
|
||||
|
||||
import React, { DOMAttributes } from "react";
|
||||
import { cssNames, IClassName } from "../../utils";
|
||||
import { Avatar } from "../avatar/avatar";
|
||||
import { Icon } from "../icon";
|
||||
|
||||
export interface EntityIconProps extends DOMAttributes<HTMLElement> {
|
||||
title: string;
|
||||
size?: number;
|
||||
source: string;
|
||||
src?: string;
|
||||
material?: string;
|
||||
background?: string;
|
||||
active?: boolean;
|
||||
className?: IClassName;
|
||||
hoverWidth?: string;
|
||||
}
|
||||
|
||||
export function EntityIcon({ active, size = 40, hoverWidth = "3px", material, className, ...props }: EntityIconProps) {
|
||||
return (
|
||||
<Avatar
|
||||
width={size}
|
||||
height={size}
|
||||
colorHash={`${props.title}-${props.source}`}
|
||||
className={cssNames("EntityIcon", className, active ? "active" : "default")}
|
||||
style={{
|
||||
"--hover-width": hoverWidth,
|
||||
} as React.CSSProperties}
|
||||
{...props}
|
||||
>
|
||||
{material && <Icon className="materialIcon" material={material} />}
|
||||
</Avatar>
|
||||
);
|
||||
}
|
||||
22
src/renderer/components/entity-icon/index.ts
Normal file
22
src/renderer/components/entity-icon/index.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from "./entity-icon";
|
||||
@ -31,6 +31,7 @@ import { cssNames, IClassName } from "../../utils";
|
||||
import { Icon } from "../icon";
|
||||
import { HotbarIcon } from "./hotbar-icon";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { catalogEntityRunContext } from "../../api/catalog-entity";
|
||||
|
||||
interface Props extends DOMAttributes<HTMLElement> {
|
||||
entity: CatalogEntity;
|
||||
@ -44,20 +45,16 @@ interface Props extends DOMAttributes<HTMLElement> {
|
||||
|
||||
@observer
|
||||
export class HotbarEntityIcon extends React.Component<Props> {
|
||||
@observable private contextMenu: CatalogEntityContextMenuContext;
|
||||
@observable private contextMenu: CatalogEntityContextMenuContext = {
|
||||
menuItems: [],
|
||||
navigate: (url: string) => navigate(url),
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.contextMenu = {
|
||||
menuItems: [],
|
||||
navigate: (url: string) => navigate(url),
|
||||
};
|
||||
}
|
||||
|
||||
get kindIcon() {
|
||||
const className = "badge";
|
||||
const category = catalogCategoryRegistry.getCategoryForEntity(this.props.entity);
|
||||
@ -92,20 +89,17 @@ export class HotbarEntityIcon extends React.Component<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.contextMenu) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
entity, errorClass, add, remove,
|
||||
index, children, ...elemProps
|
||||
index, children, className, ...elemProps
|
||||
} = this.props;
|
||||
const className = cssNames("HotbarEntityIcon", this.props.className, {
|
||||
const classNames = cssNames("HotbarEntityIcon", className, {
|
||||
interactive: true,
|
||||
active: this.isActive(entity),
|
||||
disabled: !entity
|
||||
});
|
||||
|
||||
console.log(entity);
|
||||
const isPersisted = this.isPersisted(entity);
|
||||
const onOpen = async () => {
|
||||
const menuItems: CatalogEntityContextMenu[] = [];
|
||||
@ -136,11 +130,12 @@ export class HotbarEntityIcon extends React.Component<Props> {
|
||||
src={entity.spec.icon?.src}
|
||||
material={entity.spec.icon?.material}
|
||||
background={entity.spec.icon?.background}
|
||||
className={className}
|
||||
className={classNames}
|
||||
active={isActive}
|
||||
onMenuOpen={onOpen}
|
||||
menuItems={this.contextMenu.menuItems}
|
||||
tooltip={`${entity.metadata.name} (${entity.metadata.source})`}
|
||||
onClick={() => entity.onRun(catalogEntityRunContext)}
|
||||
{...elemProps}
|
||||
>
|
||||
{ this.ledIcon }
|
||||
|
||||
@ -64,23 +64,6 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
div.MuiAvatar-root {
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
|
||||
&.active {
|
||||
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px var(--textColorAccent);
|
||||
}
|
||||
|
||||
&.interactive {
|
||||
&:hover {
|
||||
&:not(.active) {
|
||||
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px #ffffff50;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.led {
|
||||
position: absolute;
|
||||
left: 3px;
|
||||
|
||||
@ -28,9 +28,8 @@ import { cssNames, IClassName } from "../../utils";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { Menu, MenuItem } from "../menu";
|
||||
import { observer } from "mobx-react";
|
||||
import { Avatar } from "../avatar/avatar";
|
||||
import { Icon } from "../icon";
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { EntityIcon } from "../entity-icon";
|
||||
import { Tooltip } from "@material-ui/core";
|
||||
|
||||
export interface HotbarIconProps extends DOMAttributes<HTMLElement> {
|
||||
uid: string;
|
||||
@ -74,34 +73,27 @@ export const HotbarIcon = observer(({menuItems = [], size = 40, tooltip, ...prop
|
||||
setMenuOpen(!menuOpen);
|
||||
};
|
||||
|
||||
const renderIcon = () => {
|
||||
return (
|
||||
<Avatar
|
||||
{...rest}
|
||||
title={title}
|
||||
colorHash={`${title}-${source}`}
|
||||
className={cssNames(active ? "active" : "default", { interactive: !!onClick })}
|
||||
width={size}
|
||||
height={size}
|
||||
src={src}
|
||||
onClick={(event) => {
|
||||
if (!disabled) {
|
||||
onClick?.(event);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{material && <Icon className="materialIcon" material={material}/>}
|
||||
</Avatar>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cssNames("HotbarIcon flex", className, { disabled, contextMenuAvailable: menuItems.length > 0 })}>
|
||||
{tooltip && <Tooltip targetId={id}>{tooltip}</Tooltip>}
|
||||
<div id={id}>
|
||||
{renderIcon()}
|
||||
{children}
|
||||
</div>
|
||||
<div className={cssNames("HotbarIcon flex", className, { disabled })}>
|
||||
<Tooltip title={`${title || "unknown"} (${source || "unknown"})`} placement="right">
|
||||
<div id={id}>
|
||||
<EntityIcon
|
||||
title={title}
|
||||
size={size}
|
||||
active={active}
|
||||
src={src}
|
||||
source={source}
|
||||
material={material}
|
||||
onClick={event => {
|
||||
if (!disabled) {
|
||||
onClick?.(event);
|
||||
}
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
usePortal
|
||||
htmlFor={id}
|
||||
|
||||
@ -27,7 +27,7 @@ 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 type { CatalogEntity } from "../../api/catalog-entity";
|
||||
import { DragDropContext, Draggable, Droppable, DropResult } from "react-beautiful-dnd";
|
||||
import { HotbarSelector } from "./hotbar-selector";
|
||||
import { HotbarCell } from "./hotbar-cell";
|
||||
@ -126,51 +126,45 @@ export class HotbarMenu extends React.Component<Props> {
|
||||
>
|
||||
{item && (
|
||||
<Draggable draggableId={item.entity.uid} key={item.entity.uid} index={0} >
|
||||
{(provided, snapshot) => {
|
||||
const style = {
|
||||
zIndex: defaultHotbarCells - index,
|
||||
position: "absolute",
|
||||
...provided.draggableProps.style,
|
||||
} as React.CSSProperties;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.entity.uid}
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={style}
|
||||
>
|
||||
{entity ? (
|
||||
<HotbarEntityIcon
|
||||
key={index}
|
||||
index={index}
|
||||
entity={entity}
|
||||
onClick={() => entity.onRun(catalogEntityRunContext)}
|
||||
className={cssNames({ isDragging: snapshot.isDragging })}
|
||||
remove={this.removeItem}
|
||||
add={this.addItem}
|
||||
size={40}
|
||||
/>
|
||||
) : (
|
||||
<HotbarIcon
|
||||
uid={`hotbar-icon-${item.entity.uid}`}
|
||||
title={item.entity.name}
|
||||
source={item.entity.source}
|
||||
tooltip={`${item.entity.name} (${item.entity.source})`}
|
||||
menuItems={[
|
||||
{
|
||||
title: "Unpin from Hotbar",
|
||||
onClick: () => this.removeItem(item.entity.uid)
|
||||
}
|
||||
]}
|
||||
disabled
|
||||
size={40}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
key={item.entity.uid}
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={{
|
||||
zIndex: defaultHotbarCells - index,
|
||||
position: "absolute",
|
||||
...provided.draggableProps.style,
|
||||
}}
|
||||
>
|
||||
{entity ? (
|
||||
<HotbarEntityIcon
|
||||
key={index}
|
||||
index={index}
|
||||
entity={entity}
|
||||
className={cssNames({ isDragging: snapshot.isDragging })}
|
||||
remove={this.removeItem}
|
||||
add={this.addItem}
|
||||
size={40}
|
||||
/>
|
||||
) : (
|
||||
<HotbarIcon
|
||||
uid={item.entity.uid}
|
||||
title={item.entity.name}
|
||||
source={item.entity.source}
|
||||
menuItems={[
|
||||
{
|
||||
title: "Unpin from Hotbar",
|
||||
onClick: () => this.removeItem(item.entity.uid)
|
||||
}
|
||||
]}
|
||||
disabled
|
||||
size={40}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
)}
|
||||
{provided.placeholder}
|
||||
|
||||
@ -21,25 +21,24 @@
|
||||
|
||||
// Helper for combining css classes inside components
|
||||
|
||||
export type IClassName = string | string[] | IClassNameMap;
|
||||
export type IClassNameMap = {
|
||||
[className: string]: boolean | any;
|
||||
};
|
||||
export type IClassName = string | string[] | Record<string, any> | undefined | null;
|
||||
|
||||
export function cssNames(...args: IClassName[]): string {
|
||||
const map: IClassNameMap = {};
|
||||
const names: string[] = [];
|
||||
|
||||
args.forEach(className => {
|
||||
if (typeof className === "string" || Array.isArray(className)) {
|
||||
[].concat(className).forEach(name => map[name] = true);
|
||||
for (const arg of args) {
|
||||
if (typeof arg === "string") {
|
||||
names.push(arg.trim());
|
||||
} else if (Array.isArray(arg)) {
|
||||
names.push(...arg.map(name => name.trim()));
|
||||
} else if (arg && typeof arg === "object") {
|
||||
for (const [name, isActive] of Object.entries(arg)) {
|
||||
if (isActive) {
|
||||
names.push(name.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Object.assign(map, className);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Object.entries(map)
|
||||
.filter(([, isActive]) => !!isActive)
|
||||
.map(([className]) => className.trim())
|
||||
.join(" ");
|
||||
return names.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user