mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Add icon column to catalog list (#2852)
* add icon column to catalog list Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * Create generic Avatar component Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> Co-authored-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
parent
5372e3617e
commit
e033122baf
@ -69,7 +69,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.TableCell.icon {
|
||||
max-width: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.TableCell.source {
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.TableCell.status {
|
||||
max-width: 100px;
|
||||
&.connected {
|
||||
color: var(--colorSuccess);
|
||||
}
|
||||
@ -97,4 +108,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.catalogIcon {
|
||||
font-size: 10px;
|
||||
-webkit-font-smoothing: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ import { CatalogAddButton } from "./catalog-add-button";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import type { ICatalogViewRouteParam } from "./catalog.route";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Avatar } from "../avatar/avatar";
|
||||
|
||||
enum sortBy {
|
||||
name = "name",
|
||||
@ -174,6 +175,24 @@ export class Catalog extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
renderIcon(item: CatalogEntityItem) {
|
||||
const category = catalogCategoryRegistry.getCategoryForEntity(item.entity);
|
||||
|
||||
if (!category) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
title={item.name}
|
||||
colorHash={`${item.name}-${item.source}`}
|
||||
width={24}
|
||||
height={24}
|
||||
className="catalogIcon"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.catalogEntityStore) {
|
||||
return null;
|
||||
@ -202,12 +221,14 @@ export class Catalog extends React.Component<Props> {
|
||||
(entity: CatalogEntityItem) => entity.searchFields,
|
||||
]}
|
||||
renderTableHeader={[
|
||||
{ title: "", className: "icon" },
|
||||
{ title: "Name", className: "name", sortBy: sortBy.name },
|
||||
{ title: "Source", className: "source", sortBy: sortBy.source },
|
||||
{ title: "Labels", className: "labels" },
|
||||
{ title: "Status", className: "status", sortBy: sortBy.status },
|
||||
]}
|
||||
renderTableContents={(item: CatalogEntityItem) => [
|
||||
this.renderIcon(item),
|
||||
item.name,
|
||||
item.source,
|
||||
item.labels.map((label) => <Badge key={label} label={label} title={label} />),
|
||||
|
||||
92
src/renderer/components/avatar/avatar.tsx
Normal file
92
src/renderer/components/avatar/avatar.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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 React, { DOMAttributes } from "react";
|
||||
import randomColor from "randomcolor";
|
||||
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> {
|
||||
title: string;
|
||||
colorHash?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
src?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function getNameParts(name: string): string[] {
|
||||
const byWhitespace = name.split(/\s+/);
|
||||
|
||||
if (byWhitespace.length > 1) {
|
||||
return byWhitespace;
|
||||
}
|
||||
|
||||
const byDashes = name.split(/[-_]+/);
|
||||
|
||||
if (byDashes.length > 1) {
|
||||
return byDashes;
|
||||
}
|
||||
|
||||
return name.split(/@+/);
|
||||
}
|
||||
|
||||
function getIconString(title: string) {
|
||||
if (!title) {
|
||||
return "??";
|
||||
}
|
||||
|
||||
const [rawFirst, rawSecond, rawThird] = getNameParts(title);
|
||||
const splitter = new GraphemeSplitter();
|
||||
const first = splitter.iterateGraphemes(rawFirst);
|
||||
const second = rawSecond ? splitter.iterateGraphemes(rawSecond): first;
|
||||
const third = rawThird ? splitter.iterateGraphemes(rawThird) : iter.newEmpty();
|
||||
|
||||
return [
|
||||
...iter.take(first, 1),
|
||||
...iter.take(second, 1),
|
||||
...iter.take(third, 1),
|
||||
].filter(Boolean).join("");
|
||||
}
|
||||
|
||||
export function Avatar(props: Props) {
|
||||
const { title, src, width = 32, height = 32, colorHash, ...settings } = props;
|
||||
|
||||
const generateAvatarStyle = (): React.CSSProperties => {
|
||||
return {
|
||||
backgroundColor: randomColor({ seed: colorHash, luminosity: "dark" }),
|
||||
width,
|
||||
height,
|
||||
textTransform: "uppercase"
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<MaterialAvatar
|
||||
variant="rounded"
|
||||
style={generateAvatarStyle()}
|
||||
{...settings}
|
||||
>
|
||||
{getIconString(title)}
|
||||
</MaterialAvatar>
|
||||
);
|
||||
}
|
||||
@ -20,9 +20,18 @@
|
||||
*/
|
||||
|
||||
.HotbarMenu {
|
||||
.HotbarIcon {
|
||||
--size: 37px;
|
||||
|
||||
.HotbarIconMenu {
|
||||
left: 30px;
|
||||
min-width: 250px;
|
||||
|
||||
li.MenuItem {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.HotbarIcon {
|
||||
border-radius: 6px;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
@ -53,9 +62,11 @@
|
||||
}
|
||||
|
||||
div.MuiAvatar-root {
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
|
||||
&.active {
|
||||
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px var(--textColorAccent);
|
||||
transition: all 0s 0.8s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@ -106,13 +117,3 @@
|
||||
height: var(--size);
|
||||
}
|
||||
}
|
||||
|
||||
.HotbarIconMenu {
|
||||
left: 30px;
|
||||
min-width: 250px;
|
||||
|
||||
li.MenuItem {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,16 +22,14 @@
|
||||
import "./hotbar-icon.scss";
|
||||
|
||||
import React, { DOMAttributes, useState } from "react";
|
||||
import { Avatar } from "@material-ui/core";
|
||||
import randomColor from "randomcolor";
|
||||
import GraphemeSplitter from "grapheme-splitter";
|
||||
|
||||
import type { CatalogEntityContextMenu } from "../../../common/catalog";
|
||||
import { cssNames, IClassName, iter } from "../../utils";
|
||||
import { cssNames, IClassName } from "../../utils";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { Menu, MenuItem } from "../menu";
|
||||
import { MaterialTooltip } from "../+catalog/material-tooltip/material-tooltip";
|
||||
import { observer } from "mobx-react";
|
||||
import { Avatar } from "../avatar/avatar";
|
||||
|
||||
interface Props extends DOMAttributes<HTMLElement> {
|
||||
uid: string;
|
||||
@ -44,12 +42,6 @@ interface Props extends DOMAttributes<HTMLElement> {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
function generateAvatarStyle(seed: string): React.CSSProperties {
|
||||
return {
|
||||
"backgroundColor": randomColor({ seed, luminosity: "dark" })
|
||||
};
|
||||
}
|
||||
|
||||
function onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
||||
if (menuItem.confirm) {
|
||||
ConfirmDialog.open({
|
||||
@ -67,22 +59,6 @@ function onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
||||
}
|
||||
}
|
||||
|
||||
function getNameParts(name: string): string[] {
|
||||
const byWhitespace = name.split(/\s+/);
|
||||
|
||||
if (byWhitespace.length > 1) {
|
||||
return byWhitespace;
|
||||
}
|
||||
|
||||
const byDashes = name.split(/[-_]+/);
|
||||
|
||||
if (byDashes.length > 1) {
|
||||
return byDashes;
|
||||
}
|
||||
|
||||
return name.split(/@+/);
|
||||
}
|
||||
|
||||
export const HotbarIcon = observer(({menuItems = [], ...props}: Props) => {
|
||||
const { uid, title, active, className, source, disabled, onMenuOpen, children, ...rest } = props;
|
||||
const id = `hotbarIcon-${uid}`;
|
||||
@ -92,36 +68,18 @@ export const HotbarIcon = observer(({menuItems = [], ...props}: Props) => {
|
||||
setMenuOpen(!menuOpen);
|
||||
};
|
||||
|
||||
const getIconString = () => {
|
||||
if (!title) {
|
||||
return "??";
|
||||
}
|
||||
|
||||
const [rawFirst, rawSecond, rawThird] = getNameParts(title);
|
||||
const splitter = new GraphemeSplitter();
|
||||
const first = splitter.iterateGraphemes(rawFirst);
|
||||
const second = rawSecond ? splitter.iterateGraphemes(rawSecond): first;
|
||||
const third = rawThird ? splitter.iterateGraphemes(rawThird) : iter.newEmpty();
|
||||
|
||||
return [
|
||||
...iter.take(first, 1),
|
||||
...iter.take(second, 1),
|
||||
...iter.take(third, 1),
|
||||
].filter(Boolean).join("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cssNames("HotbarIcon flex inline", className, { disabled })}>
|
||||
<MaterialTooltip title={`${title || "unknown"} (${source || "unknown"})`} placement="right">
|
||||
<div id={id}>
|
||||
<Avatar
|
||||
{...rest}
|
||||
variant="square"
|
||||
title={title}
|
||||
colorHash={`${title}-${source}`}
|
||||
className={active ? "active" : "default"}
|
||||
style={generateAvatarStyle(`${title}-${source}`)}
|
||||
>
|
||||
{getIconString()}
|
||||
</Avatar>
|
||||
width={40}
|
||||
height={40}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
</MaterialTooltip>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user