mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix: highlight sidebar's active section (#2366)
* - fix: highlight active sidebar section / link - fix: caching crd definitions to prevent sidebar's scroll jumps - refactored & simplified `Sidebar` and `SidebarItem` Signed-off-by: Roman <ixrock@gmail.com> * responding to comments Signed-off-by: Roman <ixrock@gmail.com> * adding @jsdoc comments to SidebarItem Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
ee4d434d35
commit
4303700bb2
@ -2,7 +2,7 @@ import React from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Redirect, Route, Switch } from "react-router";
|
import { Redirect, Route, Switch } from "react-router";
|
||||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||||
import { crdResourcesRoute, crdRoute, crdURL, crdDefinitionsRoute } from "./crd.route";
|
import { crdDefinitionsRoute, crdResourcesRoute, crdURL } from "./crd.route";
|
||||||
import { CrdList } from "./crd-list";
|
import { CrdList } from "./crd-list";
|
||||||
import { CrdResources } from "./crd-resources";
|
import { CrdResources } from "./crd-resources";
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ export class CustomResources extends React.Component {
|
|||||||
title: "Definitions",
|
title: "Definitions",
|
||||||
component: CustomResources,
|
component: CustomResources,
|
||||||
url: crdURL(),
|
url: crdURL(),
|
||||||
routePath: crdRoute.path.toString(),
|
routePath: String(crdDefinitionsRoute.path),
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,19 +6,27 @@ import { cssNames, prevDefault } from "../../utils";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { TabLayoutRoute } from "./tab-layout";
|
|
||||||
import { sidebarStorage } from "./sidebar-storage";
|
import { sidebarStorage } from "./sidebar-storage";
|
||||||
import { isActiveRoute } from "../../navigation";
|
import { isActiveRoute } from "../../navigation";
|
||||||
|
|
||||||
interface SidebarItemProps {
|
interface SidebarItemProps {
|
||||||
id: string; // Used to save nav item collapse/expand state in local storage
|
/**
|
||||||
|
* Unique id, used in storage and integration tests
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
text: React.ReactNode | string;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
text: React.ReactNode;
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
isHidden?: boolean;
|
isHidden?: boolean;
|
||||||
|
/**
|
||||||
|
* Forces this item to be also show as active or not.
|
||||||
|
*
|
||||||
|
* Default: dynamically checks the location against the `url` props to determine if
|
||||||
|
* this item should be shown as active
|
||||||
|
*/
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
subMenus?: TabLayoutRoute[];
|
subMenus?: React.ReactNode | React.ComponentType<SidebarItemProps>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -26,22 +34,30 @@ export class SidebarItem extends React.Component<SidebarItemProps> {
|
|||||||
static displayName = "SidebarItem";
|
static displayName = "SidebarItem";
|
||||||
|
|
||||||
get id(): string {
|
get id(): string {
|
||||||
return this.props.id; // unique id, used in storage and integration tests
|
return this.props.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
get compact(): boolean {
|
@computed get compact(): boolean {
|
||||||
return Boolean(sidebarStorage.get().compact);
|
return Boolean(sidebarStorage.get().compact);
|
||||||
}
|
}
|
||||||
|
|
||||||
get expanded(): boolean {
|
@computed get expanded(): boolean {
|
||||||
return Boolean(sidebarStorage.get().expanded[this.id]);
|
return Boolean(sidebarStorage.get().expanded[this.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get isExpandable(): boolean {
|
@computed get isActive(): boolean {
|
||||||
const { subMenus, children } = this.props;
|
return this.props.isActive ?? isActiveRoute({
|
||||||
const hasContent = subMenus?.length > 0 || children;
|
path: this.props.url,
|
||||||
|
exact: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Boolean(hasContent && !this.compact) /*not available in compact-mode*/;
|
@computed get isExpandable(): boolean {
|
||||||
|
if (this.compact) {
|
||||||
|
return false; // not available currently
|
||||||
|
}
|
||||||
|
|
||||||
|
return Boolean(this.props.subMenus || this.props.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleExpand = () => {
|
toggleExpand = () => {
|
||||||
@ -51,11 +67,11 @@ export class SidebarItem extends React.Component<SidebarItemProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isHidden, isActive, subMenus = [], icon, text, children, url, className } = this.props;
|
const { isHidden, icon, text, children, url, className, subMenus } = this.props;
|
||||||
|
|
||||||
if (isHidden) return null;
|
if (isHidden) return null;
|
||||||
|
|
||||||
const { id, compact, expanded, isExpandable, toggleExpand } = this;
|
const { isActive, id, compact, expanded, isExpandable, toggleExpand } = this;
|
||||||
const classNames = cssNames(SidebarItem.displayName, className, {
|
const classNames = cssNames(SidebarItem.displayName, className, {
|
||||||
compact,
|
compact,
|
||||||
});
|
});
|
||||||
@ -76,19 +92,7 @@ export class SidebarItem extends React.Component<SidebarItemProps> {
|
|||||||
</NavLink>
|
</NavLink>
|
||||||
{isExpandable && expanded && (
|
{isExpandable && expanded && (
|
||||||
<ul className={cssNames("sub-menu", { active: isActive })}>
|
<ul className={cssNames("sub-menu", { active: isActive })}>
|
||||||
{subMenus.map(({ title, routePath, url = routePath }) => {
|
{subMenus}
|
||||||
const subItemId = `${id}${routePath}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SidebarItem
|
|
||||||
key={subItemId}
|
|
||||||
id={subItemId}
|
|
||||||
url={url}
|
|
||||||
text={title}
|
|
||||||
isActive={isActiveRoute({ path: url, exact: true })}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{children}
|
{children}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import "./sidebar.scss";
|
import "./sidebar.scss";
|
||||||
|
import type { TabLayoutRoute } from "./tab-layout";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { TabLayoutRoute } from "./tab-layout";
|
import { computed } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
@ -22,7 +23,7 @@ import { UserManagement } from "../+user-management";
|
|||||||
import { Storage } from "../+storage";
|
import { Storage } from "../+storage";
|
||||||
import { Network } from "../+network";
|
import { Network } from "../+network";
|
||||||
import { crdStore } from "../+custom-resources/crd.store";
|
import { crdStore } from "../+custom-resources/crd.store";
|
||||||
import { CrdList, crdResourcesRoute, crdRoute, crdURL } from "../+custom-resources";
|
import { crdRoute, crdURL } from "../+custom-resources";
|
||||||
import { CustomResources } from "../+custom-resources/custom-resources";
|
import { CustomResources } from "../+custom-resources/custom-resources";
|
||||||
import { isActiveRoute } from "../../navigation";
|
import { isActiveRoute } from "../../navigation";
|
||||||
import { isAllowedResource } from "../../../common/rbac";
|
import { isAllowedResource } from "../../../common/rbac";
|
||||||
@ -44,29 +45,50 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
crdStore.reloadAll();
|
crdStore.reloadAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCustomResources() {
|
@computed get crdSubMenus(): React.ReactNode {
|
||||||
if (crdStore.isLoading) {
|
if (!crdStore.isLoaded && crdStore.isLoading) {
|
||||||
return <Spinner centerHorizontal/>;
|
return <Spinner centerHorizontal/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(crdStore.groups).map(([group, crds]) => {
|
return Object.entries(crdStore.groups).map(([group, crds]) => {
|
||||||
const submenus: TabLayoutRoute[] = crds.map((crd) => {
|
const crdGroupSubMenu: React.ReactNode = crds.map((crd) => {
|
||||||
return {
|
return (
|
||||||
title: crd.getResourceKind(),
|
<SidebarItem
|
||||||
component: CrdList,
|
key={crd.getResourceApiBase()}
|
||||||
url: crd.getResourceUrl(),
|
id={`crd-resource:${crd.getResourceApiBase()}`}
|
||||||
routePath: String(crdResourcesRoute.path),
|
url={crd.getResourceUrl()}
|
||||||
};
|
text={crd.getResourceTitle()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
key={group}
|
key={group}
|
||||||
|
text={group}
|
||||||
id={`crd-group:${group}`}
|
id={`crd-group:${group}`}
|
||||||
url={crdURL({ query: { groups: group } })}
|
url={crdURL({ query: { groups: group } })}
|
||||||
subMenus={submenus}
|
subMenus={crdGroupSubMenu}
|
||||||
text={group}
|
/>
|
||||||
isActive={false}
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTreeFromTabRoutes(tabRoutes: TabLayoutRoute[] = []): React.ReactNode {
|
||||||
|
if (!tabRoutes.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tabRoutes.map(({ title, routePath, url = routePath, exact = true }) => {
|
||||||
|
const subMenuItemId = `tab-route-item-${url}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarItem
|
||||||
|
key={subMenuItemId}
|
||||||
|
id={subMenuItemId}
|
||||||
|
url={url}
|
||||||
|
text={title}
|
||||||
|
isActive={isActiveRoute({ path: routePath, exact })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -122,10 +144,10 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
key={id}
|
key={id}
|
||||||
id={id}
|
id={id}
|
||||||
url={pageUrl}
|
url={pageUrl}
|
||||||
|
isActive={isActive}
|
||||||
text={menuItem.title}
|
text={menuItem.title}
|
||||||
icon={<menuItem.components.Icon/>}
|
icon={<menuItem.components.Icon/>}
|
||||||
isActive={isActive}
|
subMenus={this.renderTreeFromTabRoutes(tabRoutes)}
|
||||||
subMenus={tabRoutes}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -172,7 +194,7 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
isActive={isActiveRoute(workloadsRoute)}
|
isActive={isActiveRoute(workloadsRoute)}
|
||||||
isHidden={Workloads.tabRoutes.length == 0}
|
isHidden={Workloads.tabRoutes.length == 0}
|
||||||
url={workloadsURL({ query })}
|
url={workloadsURL({ query })}
|
||||||
subMenus={Workloads.tabRoutes}
|
subMenus={this.renderTreeFromTabRoutes(Workloads.tabRoutes)}
|
||||||
text="Workloads"
|
text="Workloads"
|
||||||
icon={<Icon svg="workloads"/>}
|
icon={<Icon svg="workloads"/>}
|
||||||
/>
|
/>
|
||||||
@ -181,7 +203,7 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
isActive={isActiveRoute(configRoute)}
|
isActive={isActiveRoute(configRoute)}
|
||||||
isHidden={Config.tabRoutes.length == 0}
|
isHidden={Config.tabRoutes.length == 0}
|
||||||
url={configURL({ query })}
|
url={configURL({ query })}
|
||||||
subMenus={Config.tabRoutes}
|
subMenus={this.renderTreeFromTabRoutes(Config.tabRoutes)}
|
||||||
text="Configuration"
|
text="Configuration"
|
||||||
icon={<Icon material="list"/>}
|
icon={<Icon material="list"/>}
|
||||||
/>
|
/>
|
||||||
@ -190,7 +212,7 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
isActive={isActiveRoute(networkRoute)}
|
isActive={isActiveRoute(networkRoute)}
|
||||||
isHidden={Network.tabRoutes.length == 0}
|
isHidden={Network.tabRoutes.length == 0}
|
||||||
url={networkURL({ query })}
|
url={networkURL({ query })}
|
||||||
subMenus={Network.tabRoutes}
|
subMenus={this.renderTreeFromTabRoutes(Network.tabRoutes)}
|
||||||
text="Network"
|
text="Network"
|
||||||
icon={<Icon material="device_hub"/>}
|
icon={<Icon material="device_hub"/>}
|
||||||
/>
|
/>
|
||||||
@ -199,7 +221,7 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
isActive={isActiveRoute(storageRoute)}
|
isActive={isActiveRoute(storageRoute)}
|
||||||
isHidden={Storage.tabRoutes.length == 0}
|
isHidden={Storage.tabRoutes.length == 0}
|
||||||
url={storageURL({ query })}
|
url={storageURL({ query })}
|
||||||
subMenus={Storage.tabRoutes}
|
subMenus={this.renderTreeFromTabRoutes(Storage.tabRoutes)}
|
||||||
icon={<Icon svg="storage"/>}
|
icon={<Icon svg="storage"/>}
|
||||||
text="Storage"
|
text="Storage"
|
||||||
/>
|
/>
|
||||||
@ -223,7 +245,7 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
id="apps"
|
id="apps"
|
||||||
isActive={isActiveRoute(appsRoute)}
|
isActive={isActiveRoute(appsRoute)}
|
||||||
url={appsURL({ query })}
|
url={appsURL({ query })}
|
||||||
subMenus={Apps.tabRoutes}
|
subMenus={this.renderTreeFromTabRoutes(Apps.tabRoutes)}
|
||||||
icon={<Icon material="apps"/>}
|
icon={<Icon material="apps"/>}
|
||||||
text="Apps"
|
text="Apps"
|
||||||
/>
|
/>
|
||||||
@ -231,20 +253,20 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
id="users"
|
id="users"
|
||||||
isActive={isActiveRoute(usersManagementRoute)}
|
isActive={isActiveRoute(usersManagementRoute)}
|
||||||
url={usersManagementURL({ query })}
|
url={usersManagementURL({ query })}
|
||||||
subMenus={UserManagement.tabRoutes}
|
subMenus={this.renderTreeFromTabRoutes(UserManagement.tabRoutes)}
|
||||||
icon={<Icon material="security"/>}
|
icon={<Icon material="security"/>}
|
||||||
text="Access Control"
|
text="Access Control"
|
||||||
/>
|
/>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
id="custom-resources"
|
id="custom-resources"
|
||||||
|
text="Custom Resources"
|
||||||
|
url={crdURL()}
|
||||||
isActive={isActiveRoute(crdRoute)}
|
isActive={isActiveRoute(crdRoute)}
|
||||||
isHidden={!isAllowedResource("customresourcedefinitions")}
|
isHidden={!isAllowedResource("customresourcedefinitions")}
|
||||||
url={crdURL()}
|
|
||||||
subMenus={CustomResources.tabRoutes}
|
|
||||||
icon={<Icon material="extension"/>}
|
icon={<Icon material="extension"/>}
|
||||||
text="Custom Resources"
|
|
||||||
>
|
>
|
||||||
{this.renderCustomResources()}
|
{this.renderTreeFromTabRoutes(CustomResources.tabRoutes)}
|
||||||
|
{this.crdSubMenus}
|
||||||
</SidebarItem>
|
</SidebarItem>
|
||||||
{this.renderRegisteredMenus()}
|
{this.renderRegisteredMenus()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user