mirror of
https://github.com/apache/superset.git
synced 2026-04-27 03:55:47 +00:00
326 lines
8.4 KiB
TypeScript
326 lines
8.4 KiB
TypeScript
/**
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
import React from 'react';
|
|
import { t, styled } from '@superset-ui/core';
|
|
import { Nav, Navbar, NavItem, MenuItem } from 'react-bootstrap';
|
|
import NavDropdown from 'src/components/NavDropdown';
|
|
import MenuObject, {
|
|
MenuObjectProps,
|
|
MenuObjectChildProps,
|
|
} from './MenuObject';
|
|
import NewMenu from './NewMenu';
|
|
import UserMenu from './UserMenu';
|
|
import LanguagePicker, { Languages } from './LanguagePicker';
|
|
|
|
interface BrandProps {
|
|
path: string;
|
|
icon: string;
|
|
alt: string;
|
|
width: string | number;
|
|
}
|
|
|
|
interface NavBarProps {
|
|
bug_report_url?: string;
|
|
version_string?: string;
|
|
version_sha?: string;
|
|
documentation_url?: string;
|
|
languages: Languages;
|
|
show_language_picker: boolean;
|
|
user_is_anonymous: boolean;
|
|
user_info_url: string;
|
|
user_login_url: string;
|
|
user_logout_url: string;
|
|
locale: string;
|
|
}
|
|
|
|
export interface MenuProps {
|
|
data: {
|
|
menu: MenuObjectProps[];
|
|
brand: BrandProps;
|
|
navbar_right: NavBarProps;
|
|
settings: MenuObjectProps[];
|
|
};
|
|
}
|
|
|
|
const StyledHeader = styled.header`
|
|
&:nth-last-of-type(2) nav {
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.caret {
|
|
display: none;
|
|
}
|
|
|
|
.navbar-inverse {
|
|
border: none;
|
|
}
|
|
|
|
.version-info {
|
|
padding: 5px 20px;
|
|
color: ${({ theme }) => theme.colors.grayscale.base};
|
|
font-size: ${({ theme }) => theme.typography.sizes.xs}px;
|
|
|
|
div {
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
.navbar-brand {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
}
|
|
|
|
.nav > li > a {
|
|
padding: ${({ theme }) => theme.gridUnit * 4}px;
|
|
}
|
|
|
|
.navbar-nav > li > a {
|
|
color: ${({ theme }) => theme.colors.grayscale.dark1};
|
|
border-bottom: none;
|
|
&:focus {
|
|
border-bottom: none;
|
|
}
|
|
&:after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: -3px;
|
|
left: 50%;
|
|
width: 0;
|
|
height: 3px;
|
|
background-color: ${({ theme }) => theme.colors.primary.base};
|
|
opacity: 0;
|
|
transform: translateX(-50%);
|
|
transition: all ${({ theme }) => theme.transitionTiming}s;
|
|
}
|
|
|
|
&:hover {
|
|
color: ${({ theme }) => theme.colors.grayscale.dark1};
|
|
border-bottom: none;
|
|
&:after {
|
|
opacity: 1;
|
|
width: 100%;
|
|
}
|
|
}
|
|
&:hover,
|
|
&:focus {
|
|
margin: 0;
|
|
}
|
|
}
|
|
|
|
.settings-divider {
|
|
margin-bottom: 8px;
|
|
border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
|
|
}
|
|
.navbar-right {
|
|
display: flex;
|
|
align-items: center;
|
|
.dropdown:first-of-type {
|
|
/* this is the "+ NEW" button. Sweep this up when it's replaced */
|
|
margin-right: ${({ theme }) => theme.gridUnit * 2}px;
|
|
}
|
|
}
|
|
`;
|
|
|
|
export function Menu({
|
|
data: { menu, brand, navbar_right: navbarRight, settings },
|
|
}: MenuProps) {
|
|
// Flatten settings
|
|
const flatSettings: any[] = [];
|
|
|
|
if (settings) {
|
|
settings.forEach((section: object, index: number) => {
|
|
const newSection: MenuObjectProps = {
|
|
...section,
|
|
index,
|
|
isHeader: true,
|
|
};
|
|
|
|
flatSettings.push(newSection);
|
|
|
|
// Filter out '-'
|
|
if (newSection.childs) {
|
|
newSection.childs.forEach((child: any) => {
|
|
if (child !== '-') {
|
|
flatSettings.push(child);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (index !== settings.length - 1) {
|
|
flatSettings.push('-');
|
|
}
|
|
});
|
|
}
|
|
|
|
return (
|
|
<StyledHeader className="top" id="main-menu">
|
|
<Navbar inverse fluid staticTop role="navigation">
|
|
<Navbar.Header>
|
|
<Navbar.Brand>
|
|
<a className="navbar-brand" href={brand.path}>
|
|
<img width={brand.width} src={brand.icon} alt={brand.alt} />
|
|
</a>
|
|
</Navbar.Brand>
|
|
<Navbar.Toggle />
|
|
</Navbar.Header>
|
|
<Nav>
|
|
{menu.map((item, index) => (
|
|
<MenuObject {...item} key={item.label} index={index + 1} />
|
|
))}
|
|
</Nav>
|
|
<Nav className="navbar-right">
|
|
{!navbarRight.user_is_anonymous && <NewMenu />}
|
|
{settings && settings.length && (
|
|
<NavDropdown id="settings-dropdown" title="Settings">
|
|
{flatSettings.map((section, index) => {
|
|
if (section === '-') {
|
|
return (
|
|
<MenuItem
|
|
key={`$${index}`}
|
|
divider
|
|
disabled
|
|
className="settings-divider"
|
|
/>
|
|
);
|
|
} else if (section.isHeader) {
|
|
return (
|
|
<MenuItem key={`${section.label}`} disabled>
|
|
{section.label}
|
|
</MenuItem>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<MenuItem
|
|
key={`${section.label}`}
|
|
href={section.url}
|
|
eventKey={index}
|
|
>
|
|
{section.label}
|
|
</MenuItem>
|
|
);
|
|
})}
|
|
</NavDropdown>
|
|
)}
|
|
{navbarRight.documentation_url && (
|
|
<NavItem
|
|
href={navbarRight.documentation_url}
|
|
target="_blank"
|
|
title="Documentation"
|
|
>
|
|
<i className="fa fa-question" />
|
|
|
|
</NavItem>
|
|
)}
|
|
{navbarRight.bug_report_url && (
|
|
<NavItem
|
|
href={navbarRight.bug_report_url}
|
|
target="_blank"
|
|
title="Report a Bug"
|
|
>
|
|
<i className="fa fa-bug" />
|
|
|
|
</NavItem>
|
|
)}
|
|
{navbarRight.show_language_picker && (
|
|
<LanguagePicker
|
|
locale={navbarRight.locale}
|
|
languages={navbarRight.languages}
|
|
/>
|
|
)}
|
|
{!navbarRight.user_is_anonymous && (
|
|
<UserMenu
|
|
userInfoUrl={navbarRight.user_info_url}
|
|
userLogoutUrl={navbarRight.user_logout_url}
|
|
versionString={navbarRight.version_string}
|
|
versionSha={navbarRight.version_sha}
|
|
/>
|
|
)}
|
|
{navbarRight.user_is_anonymous && (
|
|
<NavItem href={navbarRight.user_login_url}>
|
|
<i className="fa fa-fw fa-sign-in" />
|
|
{t('Login')}
|
|
</NavItem>
|
|
)}
|
|
</Nav>
|
|
</Navbar>
|
|
</StyledHeader>
|
|
);
|
|
}
|
|
|
|
// transform the menu data to reorganize components
|
|
export default function MenuWrapper({ data }: MenuProps) {
|
|
const newMenuData = {
|
|
...data,
|
|
};
|
|
// Menu items that should go into settings dropdown
|
|
const settingsMenus = {
|
|
Security: true,
|
|
Manage: true,
|
|
};
|
|
|
|
// Menu items that should be ignored
|
|
const ignore = {
|
|
'Import Dashboards': true,
|
|
};
|
|
|
|
// Cycle through menu.menu to build out cleanedMenu and settings
|
|
const cleanedMenu: MenuObjectProps[] = [];
|
|
const settings: MenuObjectProps[] = [];
|
|
|
|
newMenuData.menu.forEach((item: any) => {
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
const children: (MenuObjectProps | string)[] = [];
|
|
const newItem = {
|
|
...item,
|
|
};
|
|
|
|
// Filter childs
|
|
if (item.childs) {
|
|
item.childs.forEach((child: MenuObjectChildProps | string) => {
|
|
if (typeof child === 'string') {
|
|
children.push(child);
|
|
} else if (
|
|
(child as MenuObjectChildProps).label &&
|
|
!ignore.hasOwnProperty(child.label)
|
|
) {
|
|
children.push(child);
|
|
}
|
|
});
|
|
|
|
newItem.childs = children;
|
|
}
|
|
|
|
if (!settingsMenus.hasOwnProperty(item.name)) {
|
|
cleanedMenu.push(newItem);
|
|
} else {
|
|
settings.push(newItem);
|
|
}
|
|
});
|
|
|
|
newMenuData.menu = cleanedMenu;
|
|
newMenuData.settings = settings;
|
|
|
|
return <Menu data={newMenuData} />;
|
|
}
|