未验证 提交 6da5a64d 编写于 作者: P Peter Pan 提交者: GitHub

User interaction improvement (#949)

* feat: show more menu in navbar

* feat: able to drag and change run sidebar width
上级 22b159a1
...@@ -9,8 +9,11 @@ ...@@ -9,8 +9,11 @@
"graph": "Graphs", "graph": "Graphs",
"high-dimensional": "High Dimensional", "high-dimensional": "High Dimensional",
"histogram": "Histogram", "histogram": "Histogram",
"hyper-parameter": "Hyper Parameters",
"image": "Image", "image": "Image",
"inactive": "Inactive",
"loading": "Please wait while loading data", "loading": "Please wait while loading data",
"more": "More",
"next-page": "Next Page", "next-page": "Next Page",
"pr-curve": "PR Curve", "pr-curve": "PR Curve",
"previous-page": "Prev Page", "previous-page": "Prev Page",
......
...@@ -9,8 +9,11 @@ ...@@ -9,8 +9,11 @@
"graph": "网络结构", "graph": "网络结构",
"high-dimensional": "高维数据映射", "high-dimensional": "高维数据映射",
"histogram": "直方图", "histogram": "直方图",
"hyper-parameter": "超参可视化",
"image": "图像", "image": "图像",
"inactive": "待使用",
"loading": "数据载入中,请稍等", "loading": "数据载入中,请稍等",
"more": "更多",
"next-page": "下一页", "next-page": "下一页",
"pr-curve": "PR曲线", "pr-curve": "PR曲线",
"previous-page": "上一页", "previous-page": "上一页",
......
...@@ -14,8 +14,8 @@ ...@@ -14,8 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import React, {FunctionComponent} from 'react'; import React, {FunctionComponent, useCallback, useLayoutEffect, useRef, useState} from 'react';
import {WithStyled, asideWidth, rem, size, transitionProps} from '~/utils/style'; import {WithStyled, asideWidth, rem, transitionProps} from '~/utils/style';
import styled from 'styled-components'; import styled from 'styled-components';
...@@ -30,11 +30,16 @@ export const AsideSection = styled.section` ...@@ -30,11 +30,16 @@ export const AsideSection = styled.section`
} }
`; `;
const Wrapper = styled.div<{width?: string | number}>` const Wrapper = styled.div.attrs<{width: string | number}>(({width}) => ({
${props => size('100%', props.width == null ? asideWidth : props.width)} style: {
width: 'number' === typeof width ? `${width}px` : width
}
}))<{width: string | number}>`
height: 100%;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative;
> .aside-top { > .aside-top {
flex: auto; flex: auto;
...@@ -55,16 +60,107 @@ const Wrapper = styled.div<{width?: string | number}>` ...@@ -55,16 +60,107 @@ const Wrapper = styled.div<{width?: string | number}>`
box-shadow: 0 -${rem(5)} ${rem(16)} 0 rgba(0, 0, 0, 0.03); box-shadow: 0 -${rem(5)} ${rem(16)} 0 rgba(0, 0, 0, 0.03);
padding: ${rem(20)}; padding: ${rem(20)};
} }
> .aside-resize-bar-left,
> .aside-resize-bar-right {
position: absolute;
width: ${rem(8)};
height: 100%;
top: 0;
cursor: col-resize;
user-select: none;
&.aside-resize-bar-left {
left: 0;
}
&.aside-resize-bar-right {
right: 0;
}
}
`; `;
type AsideProps = { type AsideProps = {
width?: string | number; width?: string | number;
bottom?: React.ReactNode; bottom?: React.ReactNode;
resizable?: 'left' | 'right';
minWidth?: number;
maxWidth?: number;
onResized?: (width: number) => unknown;
}; };
const Aside: FunctionComponent<AsideProps & WithStyled> = ({width, bottom, className, children}) => { const Aside: FunctionComponent<AsideProps & WithStyled> = ({
width,
bottom,
resizable,
minWidth,
maxWidth,
onResized,
className,
children
}) => {
const [sideWidth, setSideWidth] = useState<NonNullable<typeof width>>(width ?? asideWidth);
const ref = useRef<HTMLDivElement>(null);
const resizing = useRef<boolean>(false);
const range = useRef({
min: minWidth ?? null,
max: maxWidth ?? null
});
useLayoutEffect(() => {
range.current.min = minWidth ?? null;
}, [minWidth]);
useLayoutEffect(() => {
range.current.max = maxWidth ?? null;
}, [maxWidth]);
useLayoutEffect(() => {
if (range.current.min == null && ref.current) {
const {width} = ref.current.getBoundingClientRect();
range.current.min = width;
}
}, []);
const mousedown = useCallback(() => {
resizing.current = true;
}, []);
const mousemove = useCallback(
(event: MouseEvent) => {
if (ref.current && resizing.current) {
const clientX = event.clientX;
const {left, right} = ref.current.getBoundingClientRect();
let w = 0;
if (resizable === 'left') {
w = Math.max(range.current.min ?? 0, right - clientX);
} else if (resizable === 'right') {
w = Math.max(range.current.min ?? 0, clientX - left);
}
w = Math.min(range.current.max ?? document.body.clientWidth / 2, w);
setSideWidth(w);
}
},
[resizable]
);
const mouseup = useCallback(() => {
resizing.current = false;
if (ref.current) {
onResized?.(ref.current.getBoundingClientRect().width);
}
}, [onResized]);
useLayoutEffect(() => {
document.addEventListener('mousemove', mousemove);
return () => document.removeEventListener('mousemove', mousemove);
}, [mousemove]);
useLayoutEffect(() => {
document.addEventListener('mouseup', mouseup);
return () => document.removeEventListener('mouseup', mouseup);
}, [mouseup]);
return ( return (
<Wrapper width={width} className={className}> <Wrapper width={sideWidth} className={className} ref={ref}>
{resizable ? <div className={`aside-resize-bar-${resizable}`} onMouseDown={mousedown}></div> : null}
<div className="aside-top">{children}</div> <div className="aside-top">{children}</div>
{bottom && <div className="aside-bottom">{bottom}</div>} {bottom && <div className="aside-bottom">{bottom}</div>}
</Wrapper> </Wrapper>
......
...@@ -14,9 +14,11 @@ ...@@ -14,9 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
// cspell:words cimode
import {Link, LinkProps, useLocation} from 'react-router-dom'; import {Link, LinkProps, useLocation} from 'react-router-dom';
import React, {FunctionComponent, useCallback, useEffect, useMemo, useState} from 'react'; import React, {FunctionComponent, useCallback, useEffect, useMemo, useState} from 'react';
import {border, borderRadius, rem, size, transitionProps} from '~/utils/style'; import {border, borderRadius, rem, size, transitionProps, triangle} from '~/utils/style';
import Icon from '~/components/Icon'; import Icon from '~/components/Icon';
import Language from '~/components/Language'; import Language from '~/components/Language';
...@@ -28,17 +30,40 @@ import {getApiToken} from '~/utils/fetch'; ...@@ -28,17 +30,40 @@ import {getApiToken} from '~/utils/fetch';
import logo from '~/assets/images/logo.svg'; import logo from '~/assets/images/logo.svg';
import queryString from 'query-string'; import queryString from 'query-string';
import styled from 'styled-components'; import styled from 'styled-components';
import useNavItems from '~/hooks/useNavItems'; import useAvailableComponents from '~/hooks/useAvailableComponents';
import {useTranslation} from 'react-i18next'; import {useTranslation} from 'react-i18next';
const BASE_URI: string = import.meta.env.SNOWPACK_PUBLIC_BASE_URI; const BASE_URI: string = import.meta.env.SNOWPACK_PUBLIC_BASE_URI;
const PUBLIC_PATH: string = import.meta.env.SNOWPACK_PUBLIC_PATH; const PUBLIC_PATH: string = import.meta.env.SNOWPACK_PUBLIC_PATH;
const API_TOKEN_KEY: string = import.meta.env.SNOWPACK_PUBLIC_API_TOKEN_KEY; const API_TOKEN_KEY: string = import.meta.env.SNOWPACK_PUBLIC_API_TOKEN_KEY;
interface NavbarItemProps extends Route { const MAX_ITEM_COUNT_IN_NAVBAR = 5;
const flatten = <T extends {children?: T[]}>(routes: T[]) => {
const result: Omit<T, 'children'>[] = [];
routes.forEach(route => {
if (route.children) {
result.push(...flatten(route.children));
} else {
result.push(route);
}
});
return result;
};
interface NavbarItemProps {
active: boolean;
path?: Route['path'];
showDropdownIcon?: boolean;
}
interface NavbarItemType {
id: string;
cid?: string; cid?: string;
name: string;
active: boolean; active: boolean;
children?: ({active: boolean} & NonNullable<Route['children']>[number])[]; path?: Route['path'];
children?: NavbarItemType[];
} }
function appendApiToken(url: string) { function appendApiToken(url: string) {
...@@ -129,10 +154,27 @@ const NavItem = styled.div<{active?: boolean}>` ...@@ -129,10 +154,27 @@ const NavItem = styled.div<{active?: boolean}>`
${props => border('bottom', rem(3), 'solid', props.active ? 'var(--navbar-highlight-color)' : 'transparent')} ${props => border('bottom', rem(3), 'solid', props.active ? 'var(--navbar-highlight-color)' : 'transparent')}
${transitionProps('border-bottom')} ${transitionProps('border-bottom')}
text-transform: uppercase; text-transform: uppercase;
&.dropdown-icon {
&::after {
content: '';
display: inline-block;
width: 0;
height: 0;
margin-left: 0.5rem;
vertical-align: middle;
${triangle({
pointingDirection: 'bottom',
width: rem(8),
height: rem(5),
foregroundColor: 'currentColor'
})}
}
}
} }
`; `;
const SubNav = styled.div` const SubNavWrapper = styled.div`
overflow: hidden; overflow: hidden;
border-radius: ${borderRadius}; border-radius: ${borderRadius};
`; `;
...@@ -156,38 +198,65 @@ const NavItemChild = styled.div<{active?: boolean}>` ...@@ -156,38 +198,65 @@ const NavItemChild = styled.div<{active?: boolean}>`
} }
`; `;
const NavbarLink: FunctionComponent<{to?: string} & Omit<LinkProps, 'to'>> = ({to, children, ...props}) => { const NavbarLink: FunctionComponent<{to?: string} & Omit<LinkProps, 'to'>> = ({to, children, ...props}) => (
return ( <Link to={to ? appendApiToken(to) : ''} {...props}>
<Link to={to ? appendApiToken(to) : ''} {...props}> {children}
{children} </Link>
</Link> );
);
}; // FIXME: why we need to add children type here... that's weird...
const NavbarItem = React.forwardRef<HTMLDivElement, NavbarItemProps & {children?: React.ReactNode}>(
const NavbarItem = React.forwardRef<HTMLDivElement, NavbarItemProps>(({id, cid, path, active}, ref) => { ({path, active, showDropdownIcon, children}, ref) => {
const {t} = useTranslation('common'); if (path) {
return (
const name = useMemo(() => (cid ? `${t(id)} - ${t(cid)}` : t(id)), [t, id, cid]); <NavItem active={active} ref={ref}>
<NavbarLink to={path} className="nav-link">
<span className={`nav-text ${showDropdownIcon ? 'dropdown-icon' : ''}`}>{children}</span>
</NavbarLink>
</NavItem>
);
}
if (path) {
return ( return (
<NavItem active={active} ref={ref}> <NavItem active={active} ref={ref}>
<NavbarLink to={path} className="nav-link"> <span className={`nav-text ${showDropdownIcon ? 'dropdown-icon' : ''}`}>{children}</span>
<span className="nav-text">{name}</span>
</NavbarLink>
</NavItem> </NavItem>
); );
} }
);
return (
<NavItem active={active} ref={ref}>
<span className="nav-text">{name}</span>
</NavItem>
);
});
NavbarItem.displayName = 'NavbarItem'; NavbarItem.displayName = 'NavbarItem';
const SubNav: FunctionComponent<{
menu: Omit<NavbarItemType, 'children' | 'cid'>[];
active?: boolean;
path?: string;
showDropdownIcon?: boolean;
}> = ({menu, active, path, showDropdownIcon, children}) => (
<Tippy
placement="bottom-start"
animation="shift-away-subtle"
interactive
arrow={false}
offset={[0, 0]}
hideOnClick={false}
role="menu"
content={
<SubNavWrapper>
{menu.map(item => (
<NavItemChild active={item.active} key={item.id}>
<NavbarLink to={item.path}>{item.name}</NavbarLink>
</NavItemChild>
))}
</SubNavWrapper>
}
>
<NavbarItem active={active || false} path={path} showDropdownIcon={showDropdownIcon}>
{children}
</NavbarItem>
</Tippy>
);
const Navbar: FunctionComponent = () => { const Navbar: FunctionComponent = () => {
const {t, i18n} = useTranslation('common'); const {t, i18n} = useTranslation('common');
const {pathname} = useLocation(); const {pathname} = useLocation();
...@@ -202,11 +271,32 @@ const Navbar: FunctionComponent = () => { ...@@ -202,11 +271,32 @@ const Navbar: FunctionComponent = () => {
const currentPath = useMemo(() => pathname.replace(BASE_URI, ''), [pathname]); const currentPath = useMemo(() => pathname.replace(BASE_URI, ''), [pathname]);
const [navItems] = useNavItems(); const [components, inactiveComponents] = useAvailableComponents();
const [items, setItems] = useState<NavbarItemProps[]>([]);
const componentsInNavbar = useMemo(() => components.slice(0, MAX_ITEM_COUNT_IN_NAVBAR), [components]);
const flattenMoreComponents = useMemo(() => flatten(components.slice(MAX_ITEM_COUNT_IN_NAVBAR)), [components]);
const flattenInactiveComponents = useMemo(() => flatten(inactiveComponents), [inactiveComponents]);
const componentsInMoreMenu = useMemo(
() =>
flattenMoreComponents.map(item => ({
...item,
active: currentPath === item.path
})),
[currentPath, flattenMoreComponents]
);
const componentsInInactiveMenu = useMemo(
() =>
flattenInactiveComponents.map(item => ({
...item,
active: currentPath === item.path
})),
[currentPath, flattenInactiveComponents]
);
const [navItemsInNavbar, setNavItemsInNavbar] = useState<NavbarItemType[]>([]);
useEffect(() => { useEffect(() => {
setItems(oldItems => setNavItemsInNavbar(oldItems =>
navItems.map(item => { componentsInNavbar.map(item => {
const children = item.children?.map(child => ({ const children = item.children?.map(child => ({
...child, ...child,
active: child.path === currentPath active: child.path === currentPath
...@@ -217,6 +307,7 @@ const Navbar: FunctionComponent = () => { ...@@ -217,6 +307,7 @@ const Navbar: FunctionComponent = () => {
return { return {
...item, ...item,
cid: child.id, cid: child.id,
name: child.name,
path: currentPath, path: currentPath,
active: true, active: true,
children children
...@@ -227,6 +318,7 @@ const Navbar: FunctionComponent = () => { ...@@ -227,6 +318,7 @@ const Navbar: FunctionComponent = () => {
return { return {
...item, ...item,
...oldItem, ...oldItem,
name: item.children?.find(c => c.id === oldItem.cid)?.name ?? item.name,
active: false, active: false,
children children
}; };
...@@ -240,7 +332,7 @@ const Navbar: FunctionComponent = () => { ...@@ -240,7 +332,7 @@ const Navbar: FunctionComponent = () => {
}; };
}) })
); );
}, [navItems, currentPath]); }, [componentsInNavbar, currentPath]);
return ( return (
<Nav> <Nav>
...@@ -249,36 +341,35 @@ const Navbar: FunctionComponent = () => { ...@@ -249,36 +341,35 @@ const Navbar: FunctionComponent = () => {
<img alt="PaddlePaddle" src={PUBLIC_PATH + logo} /> <img alt="PaddlePaddle" src={PUBLIC_PATH + logo} />
<span>VisualDL</span> <span>VisualDL</span>
</Logo> </Logo>
{items.map(item => { {navItemsInNavbar.map(item => {
if (item.children) { if (item.children) {
return ( return (
<Tippy <SubNav
placement="bottom-start" menu={item.children}
animation="shift-away-subtle" active={item.active}
interactive path={item.path}
arrow={false}
offset={[0, 0]}
hideOnClick={false}
role="menu"
content={
<SubNav>
{item.children.map(child => (
<NavItemChild active={child.active} key={child.id}>
<NavbarLink to={child.path}>
{t(item.id)} - {t(child.id)}
</NavbarLink>
</NavItemChild>
))}
</SubNav>
}
key={item.active ? `${item.id}-activated` : item.id} key={item.active ? `${item.id}-activated` : item.id}
> >
<NavbarItem {...item} /> {item.name}
</Tippy> </SubNav>
); );
} }
return <NavbarItem {...item} key={item.id} />; return (
<NavbarItem active={item.active} path={item.path} key={item.id}>
{item.name}
</NavbarItem>
);
})} })}
{componentsInMoreMenu.length ? (
<SubNav menu={componentsInMoreMenu} showDropdownIcon>
{t('common:more')}
</SubNav>
) : null}
{componentsInInactiveMenu.length ? (
<SubNav menu={componentsInInactiveMenu} showDropdownIcon>
{t('common:inactive')}
</SubNav>
) : null}
</div> </div>
<div className="right"> <div className="right">
<Tippy <Tippy
...@@ -290,9 +381,9 @@ const Navbar: FunctionComponent = () => { ...@@ -290,9 +381,9 @@ const Navbar: FunctionComponent = () => {
hideOnClick={false} hideOnClick={false}
role="menu" role="menu"
content={ content={
<SubNav> <SubNavWrapper>
<ThemeToggle /> <ThemeToggle />
</SubNav> </SubNavWrapper>
} }
> >
<NavItem className="nav-item"> <NavItem className="nav-item">
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
import Aside, {AsideSection} from '~/components/Aside'; import Aside, {AsideSection} from '~/components/Aside';
import React, {FunctionComponent, useCallback, useMemo, useState} from 'react'; import React, {FunctionComponent, useCallback, useMemo, useState} from 'react';
import {ellipsis, em, rem, size} from '~/utils/style'; import {asideWidth, ellipsis, em, rem, size} from '~/utils/style';
import Checkbox from '~/components/Checkbox'; import Checkbox from '~/components/Checkbox';
import Field from '~/components/Field'; import Field from '~/components/Field';
...@@ -26,8 +26,11 @@ import RunningToggle from '~/components/RunningToggle'; ...@@ -26,8 +26,11 @@ import RunningToggle from '~/components/RunningToggle';
import SearchInput from '~/components/SearchInput'; import SearchInput from '~/components/SearchInput';
import styled from 'styled-components'; import styled from 'styled-components';
import uniqBy from 'lodash/uniqBy'; import uniqBy from 'lodash/uniqBy';
import useLocalStorage from '~/hooks/useLocalStorage';
import {useTranslation} from 'react-i18next'; import {useTranslation} from 'react-i18next';
const SIDE_WIDTH_STORAGE_KEY = 'run_aside_width';
const StyledAside = styled(Aside)` const StyledAside = styled(Aside)`
${AsideSection}.run-section { ${AsideSection}.run-section {
flex: auto; flex: auto;
...@@ -137,8 +140,16 @@ const RunAside: FunctionComponent<RunAsideProps> = ({ ...@@ -137,8 +140,16 @@ const RunAside: FunctionComponent<RunAsideProps> = ({
[running, onToggleRunning] [running, onToggleRunning]
); );
const [width, setWidth] = useLocalStorage(SIDE_WIDTH_STORAGE_KEY);
return ( return (
<StyledAside bottom={bottom}> <StyledAside
bottom={bottom}
resizable="left"
width={width == null ? asideWidth : +width}
minWidth={260}
onResized={width => setWidth(width + '')}
>
{children} {children}
<AsideSection className="run-section"> <AsideSection className="run-section">
<Field className="run-select" label={t('common:select-runs')}> <Field className="run-select" label={t('common:select-runs')}>
......
...@@ -14,11 +14,18 @@ ...@@ -14,11 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
import routes, {Pages, Route} from '~/routes'; import routes, {Pages} from '~/routes';
import {useCallback, useEffect, useState} from 'react'; import {useCallback, useEffect, useMemo, useState} from 'react';
import type {Route} from '~/routes';
import ee from '~/utils/event'; import ee from '~/utils/event';
import useRequest from '~/hooks/useRequest'; import useRequest from '~/hooks/useRequest';
import {useTranslation} from 'react-i18next';
interface RouteWithName extends Route {
name: string;
children?: Pick<RouteWithName, 'id' | 'path' | 'component' | 'name'>[];
}
export const navMap = { export const navMap = {
scalar: Pages.Scalar, scalar: Pages.Scalar,
...@@ -29,11 +36,15 @@ export const navMap = { ...@@ -29,11 +36,15 @@ export const navMap = {
graph: Pages.Graph, graph: Pages.Graph,
embeddings: Pages.HighDimensional, embeddings: Pages.HighDimensional,
pr_curve: Pages.PRCurve, pr_curve: Pages.PRCurve,
roc_curve: Pages.ROCCurve roc_curve: Pages.ROCCurve,
hyper_parameters: Pages.HyperParameter
} as const; } as const;
const useNavItems = () => { const useAvailableComponents = () => {
const [components, setComponents] = useState<Route[]>([]); const {t} = useTranslation('common');
const [components, setComponents] = useState<RouteWithName[]>([]);
const [inactiveComponents, setInactiveComponents] = useState<RouteWithName[]>([]);
const {data, loading, error, mutate} = useRequest<(keyof typeof navMap)[]>('/components', { const {data, loading, error, mutate} = useRequest<(keyof typeof navMap)[]>('/components', {
refreshInterval: components.length ? 61 * 1000 : 15 * 1000, refreshInterval: components.length ? 61 * 1000 : 15 * 1000,
...@@ -53,32 +64,55 @@ const useNavItems = () => { ...@@ -53,32 +64,55 @@ const useNavItems = () => {
}; };
}, [mutate]); }, [mutate]);
const filterPages = useCallback( const filterRoutes = useCallback(
(pages: Route[]) => { (pages: Route[], filter: (page: Route) => boolean) => {
const items: string[] = data?.map(item => navMap[item]) ?? []; const iterator = (pages: Route[], parent?: Route) => {
return pages.reduce<Route[]>((m, page) => { const parentName = parent ? t(parent.id) + ' - ' : '';
if (page.children) { return pages.reduce<RouteWithName[]>((m, page) => {
const children = filterPages(page.children); const name = parentName + t(page.id);
if (children.length) { if (page.children) {
const children = iterator(page.children, page);
if (children.length) {
m.push({
...page,
name,
children: children as RouteWithName['children']
});
}
} else if (page.visible !== false && filter(page)) {
m.push({ m.push({
...page, ...page,
children: children as Route['children'] name,
children: undefined
}); });
} }
} else if (page.visible !== false && items.includes(page.id)) { return m;
m.push(page); }, []);
} };
return m; return iterator(pages);
}, []);
}, },
[data] [t]
); );
const legalAvailableComponentIdArray: string[] = useMemo(() => data?.map(item => navMap[item]) ?? [], [data]);
const findAvailableComponents = useCallback(
(pages: Route[]) => filterRoutes(pages, page => legalAvailableComponentIdArray.includes(page.id)),
[filterRoutes, legalAvailableComponentIdArray]
);
const findInactiveComponents = useCallback(
(pages: Route[]) => filterRoutes(pages, page => !legalAvailableComponentIdArray.includes(page.id)),
[filterRoutes, legalAvailableComponentIdArray]
);
useEffect(() => {
setComponents(findAvailableComponents(routes));
}, [findAvailableComponents]);
useEffect(() => { useEffect(() => {
setComponents(filterPages(routes)); setInactiveComponents(findInactiveComponents(routes));
}, [filterPages]); }, [findInactiveComponents]);
return [components, loading, error] as const; return [components, inactiveComponents, loading, error] as const;
}; };
export default useNavItems; export default useAvailableComponents;
...@@ -21,7 +21,7 @@ import {useHistory, useLocation} from 'react-router-dom'; ...@@ -21,7 +21,7 @@ import {useHistory, useLocation} from 'react-router-dom';
import Error from '~/components/Error'; import Error from '~/components/Error';
import HashLoader from 'react-spinners/HashLoader'; import HashLoader from 'react-spinners/HashLoader';
import styled from 'styled-components'; import styled from 'styled-components';
import useNavItems from '~/hooks/useNavItems'; import useAvailableComponents from '~/hooks/useAvailableComponents';
import {useTranslation} from 'react-i18next'; import {useTranslation} from 'react-i18next';
const CenterWrapper = styled.div` const CenterWrapper = styled.div`
...@@ -40,7 +40,7 @@ const Loading = styled.div` ...@@ -40,7 +40,7 @@ const Loading = styled.div`
`; `;
const IndexPage: FunctionComponent = () => { const IndexPage: FunctionComponent = () => {
const [navItems, loading] = useNavItems(); const [components, , loading] = useAvailableComponents();
const history = useHistory(); const history = useHistory();
const {t} = useTranslation('common'); const {t} = useTranslation('common');
...@@ -48,18 +48,20 @@ const IndexPage: FunctionComponent = () => { ...@@ -48,18 +48,20 @@ const IndexPage: FunctionComponent = () => {
const location = useLocation(); const location = useLocation();
useEffect(() => { useEffect(() => {
if (navItems.length) { if (components.length) {
if (navItems[0].path) { if (components[0].path) {
history.replace(navItems[0].path + location.search); history.replace(components[0].path + location.search);
} else if (navItems[0].children?.length && navItems[0].children[0].path) { } else if (components[0].children?.length && components[0].children[0].path) {
history.replace(navItems[0].children[0].path + location.search); history.replace(components[0].children[0].path + location.search);
} }
} else {
// TODO: no component available, add a error tip
} }
}, [navItems, history, location.search]); }, [components, history, location.search]);
return ( return (
<CenterWrapper> <CenterWrapper>
{loading || navItems.length ? ( {loading || components.length ? (
<Loading> <Loading>
<HashLoader size="60px" color={primaryColor} /> <HashLoader size="60px" color={primaryColor} />
<span>{t('common:loading')}</span> <span>{t('common:loading')}</span>
......
...@@ -14,7 +14,9 @@ ...@@ -14,7 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
import React, {FunctionComponent, LazyExoticComponent} from 'react'; import type {FunctionComponent, LazyExoticComponent} from 'react';
import React from 'react';
export enum Pages { export enum Pages {
Scalar = 'scalar', Scalar = 'scalar',
...@@ -25,7 +27,8 @@ export enum Pages { ...@@ -25,7 +27,8 @@ export enum Pages {
Graph = 'graph', Graph = 'graph',
HighDimensional = 'high-dimensional', HighDimensional = 'high-dimensional',
PRCurve = 'pr-curve', PRCurve = 'pr-curve',
ROCCurve = 'roc-curve' ROCCurve = 'roc-curve',
HyperParameter = 'hyper-parameter'
} }
export interface Route { export interface Route {
......
...@@ -14,4 +14,5 @@ ...@@ -14,4 +14,5 @@
* limitations under the License. * limitations under the License.
*/ */
export default ['embeddings', 'scalar', 'image', 'audio', 'text', 'graph', 'histogram', 'pr_curve', 'roc_curve']; export default ['embeddings', 'scalar', 'image', 'text', 'graph', 'pr_curve', 'roc_curve'];
// export default ['embeddings', 'scalar', 'image', 'audio', 'text', 'graph', 'histogram', 'pr_curve', 'roc_curve'];
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册