Bring back support for custom cluster icons (#3066)

* revive custom cluster icons

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix borders

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen authored and GitHub committed 2021-06-16 07:12:10 +03:00
1 parent 42bb2a620a
commit 3abb3bdcce
10 files changed
+202 -45

No files matched your search

@@ -79,6 +79,7 @@ export class CatalogEntityDetails extends Component<Props> {
uid={entity.metadata.uid}
title={entity.metadata.name}
source={entity.metadata.source}
icon={entity.spec.iconData}
onClick={() => this.openEntity()}
size={128} />
<div className="IconHint">
+8 -8
View File
@@ -37,12 +37,12 @@ import { catalogCategoryRegistry } from "../../../common/catalog";
import { CatalogAddButton } from "./catalog-add-button";
import type { RouteComponentProps } from "react-router";
import { Notifications } from "../notifications";
import { Avatar } from "../avatar/avatar";
import { MainLayout } from "../layout/main-layout";
import { cssNames } from "../../utils";
import { makeCss } from "../../../common/utils/makeCss";
import { CatalogEntityDetails } from "./catalog-entity-details";
import type { CatalogViewRouteParam } from "../../../common/routes";
import { HotbarIcon } from "../hotbar/hotbar-icon";
enum sortBy {
name = "name",
@@ -192,13 +192,13 @@ export class Catalog extends React.Component<Props> {
renderIcon(item: CatalogEntityItem) {
return (
<Avatar
title={item.name}
colorHash={`${item.name}-${item.source}`}
width={24}
height={24}
className={css.catalogIcon}
/>
<HotbarIcon
uid={item.getId()}
title={item.getName()}
source={item.source}
icon={item.entity.spec.iconData}
onClick={() => this.onDetails(item)}
size={24} />
);
}
@@ -20,6 +20,7 @@
*/
import React from "react";
import type { KubernetesCluster } from "../../../common/catalog-entities";
import { ClusterStore } from "../../../common/cluster-store";
import type { EntitySettingViewProps } from "../../../extensions/registries";
import type { CatalogEntity } from "../../api/catalog-entity";
@@ -41,6 +42,9 @@ export function GeneralSettings({ entity }: EntitySettingViewProps) {
<section>
<components.ClusterNameSetting cluster={cluster} />
</section>
<section>
<components.ClusterIconSetting cluster={cluster} entity={entity as KubernetesCluster} />
</section>
<section>
<components.ClusterKubeconfig cluster={cluster} />
</section>
@@ -0,0 +1,110 @@
/**
* 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 from "react";
import type { Cluster } from "../../../../main/cluster";
//import { FilePicker, OverSizeLimitStyle } from "../../file-picker";
import { boundMethod } from "../../../utils";
import { Button } from "../../button";
import { observable } from "mobx";
import { observer } from "mobx-react";
import { SubTitle } from "../../layout/sub-title";
import { HotbarIcon } from "../../hotbar/hotbar-icon";
import type { KubernetesCluster } from "../../../../common/catalog-entities";
import { FilePicker, OverSizeLimitStyle } from "../../file-picker";
enum GeneralInputStatus {
CLEAN = "clean",
ERROR = "error",
}
interface Props {
cluster: Cluster;
entity: KubernetesCluster
}
@observer
export class ClusterIconSetting extends React.Component<Props> {
@observable status = GeneralInputStatus.CLEAN;
@observable errorText?: string;
@boundMethod
async onIconPick([file]: File[]) {
const { cluster } = this.props;
try {
if (file) {
const buf = Buffer.from(await file.arrayBuffer());
cluster.preferences.icon = `data:${file.type};base64,${buf.toString("base64")}`;
} else {
// this has to be done as a seperate branch (and not always) because `cluster`
// is observable and triggers an update loop.
cluster.preferences.icon = undefined;
}
} catch (e) {
this.errorText = e.toString();
this.status = GeneralInputStatus.ERROR;
}
}
getClearButton() {
if (this.props.cluster.preferences.icon) {
return <Button
label="Clear"
tooltip="Revert back to default icon"
onClick={() => this.onIconPick([])}
/>;
}
return null;
}
render() {
const { entity } = this.props;
const label = (
<>
<HotbarIcon
uid={entity.metadata.uid}
title={entity.metadata.name}
source={entity.metadata.source}
icon={entity.spec.iconData}
/>
<span style={{marginRight: "var(--unit)"}}>Browse for new icon...</span>
</>
);
return (
<>
<SubTitle title="Cluster Icon" />
<div className="file-loader">
<FilePicker
accept="image/*"
label={label}
onOverSizeLimit={OverSizeLimitStyle.FILTER}
handler={this.onIconPick}
/>
{this.getClearButton()}
</div>
</>
);
}
}
@@ -27,3 +27,4 @@ export * from "./cluster-name-setting";
export * from "./cluster-prometheus-setting";
export * from "./cluster-proxy-setting";
export * from "./cluster-show-metrics";
export * from "./cluster-icon-settings";
@@ -129,6 +129,7 @@ export class HotbarEntityIcon extends React.Component<Props> {
uid={entity.metadata.uid}
title={entity.metadata.name}
source={entity.metadata.source}
icon={entity.spec.iconData}
className={className}
active={isActive}
onMenuOpen={onOpen}
@@ -113,7 +113,17 @@
}
img {
width: var(--size);
height: var(--size);
padding: 3px;
border-radius: 6px;
&.active {
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px var(--textColorAccent);
}
&:hover {
&:not(.active) {
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px #ffffff50;
}
}
}
}
+23 -9
View File
@@ -35,6 +35,7 @@ export interface HotbarIconProps extends DOMAttributes<HTMLElement> {
uid: string;
title: string;
source: string;
icon?: string;
onMenuOpen?: () => void;
className?: IClassName;
active?: boolean;
@@ -61,7 +62,7 @@ function onMenuItemClick(menuItem: CatalogEntityContextMenu) {
}
export const HotbarIcon = observer(({menuItems = [], size = 40, ...props}: HotbarIconProps) => {
const { uid, title, active, className, source, disabled, onMenuOpen, children, ...rest } = props;
const { uid, title, icon, active, className, source, disabled, onMenuOpen, children, ...rest } = props;
const id = `hotbarIcon-${uid}`;
const [menuOpen, setMenuOpen] = useState(false);
@@ -69,18 +70,31 @@ export const HotbarIcon = observer(({menuItems = [], size = 40, ...props}: Hotba
setMenuOpen(!menuOpen);
};
const renderIcon = () => {
if (icon) {
return <img
{...rest}
src={icon}
className={active ? "active" : "default"}
width={size}
height={size} />;
} else {
return <Avatar
{...rest}
title={title}
colorHash={`${title}-${source}`}
className={active ? "active" : "default"}
width={size}
height={size}
/>;
}
};
return (
<div className={cssNames("HotbarIcon flex inline", className, { disabled })}>
<MaterialTooltip title={`${title || "unknown"} (${source || "unknown"})`} placement="right">
<div id={id}>
<Avatar
{...rest}
title={title}
colorHash={`${title}-${source}`}
className={active ? "active" : "default"}
width={size}
height={size}
/>
{renderIcon()}
{children}
</div>
</MaterialTooltip>