mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: command bar pages and name resolving (#7397)
This commit is contained in:
parent
25947c3075
commit
9b789ea5ef
@ -21,6 +21,8 @@ import {
|
||||
CommandResultGroup,
|
||||
type CommandResultGroupItem,
|
||||
} from './RecentlyVisited/CommandResultGroup';
|
||||
import { PageSuggestions } from './PageSuggestions';
|
||||
import { useRoutes } from 'component/layout/MainLayout/NavigationSidebar/useRoutes';
|
||||
import { useAsyncDebounce } from 'react-table';
|
||||
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||
|
||||
@ -89,6 +91,23 @@ export const CommandBar = () => {
|
||||
CommandResultGroupItem[]
|
||||
>([]);
|
||||
const { lastVisited } = useRecentlyVisited();
|
||||
const { routes } = useRoutes();
|
||||
const allRoutes: Record<
|
||||
string,
|
||||
{ path: string; route: string; title: string }
|
||||
> = {};
|
||||
for (const route of [
|
||||
...routes.mainNavRoutes,
|
||||
...routes.adminRoutes,
|
||||
...routes.mobileRoutes,
|
||||
]) {
|
||||
allRoutes[route.path] = {
|
||||
path: route.path,
|
||||
route: route.route,
|
||||
title: route.title,
|
||||
};
|
||||
}
|
||||
|
||||
const hideSuggestions = () => {
|
||||
setShowSuggestions(false);
|
||||
};
|
||||
@ -212,7 +231,11 @@ export const CommandBar = () => {
|
||||
elseShow={
|
||||
showSuggestions && (
|
||||
<CommandResultsPaper className='dropdown-outline'>
|
||||
<RecentlyVisited lastVisited={lastVisited} />
|
||||
<RecentlyVisited
|
||||
lastVisited={lastVisited}
|
||||
routes={allRoutes}
|
||||
/>
|
||||
<PageSuggestions routes={allRoutes} />
|
||||
</CommandResultsPaper>
|
||||
)
|
||||
}
|
||||
|
89
frontend/src/component/commandBar/PageSuggestions.tsx
Normal file
89
frontend/src/component/commandBar/PageSuggestions.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import {
|
||||
List,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
styled,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { IconRenderer } from 'component/layout/MainLayout/NavigationSidebar/IconRenderer';
|
||||
import type { Theme } from '@mui/material/styles/createTheme';
|
||||
|
||||
const listItemButtonStyle = (theme: Theme) => ({
|
||||
borderRadius: theme.spacing(0.5),
|
||||
borderLeft: `${theme.spacing(0.5)} solid transparent`,
|
||||
'&.Mui-selected': {
|
||||
borderLeft: `${theme.spacing(0.5)} solid ${theme.palette.primary.main}`,
|
||||
},
|
||||
});
|
||||
|
||||
const StyledTypography = styled(Typography)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.bodySize,
|
||||
padding: theme.spacing(0, 3),
|
||||
}));
|
||||
|
||||
const StyledListItemIcon = styled(ListItemIcon)(({ theme }) => ({
|
||||
minWidth: theme.spacing(4),
|
||||
margin: theme.spacing(0.25, 0),
|
||||
}));
|
||||
|
||||
const StyledListItemText = styled(ListItemText)(({ theme }) => ({
|
||||
margin: 0,
|
||||
}));
|
||||
|
||||
const toListItemData = (
|
||||
items: string[],
|
||||
routes: Record<string, { path: string; route: string; title: string }>,
|
||||
) => {
|
||||
return items.map((item) => {
|
||||
return {
|
||||
name: routes[item]?.title ?? item,
|
||||
path: item,
|
||||
icon: <IconRenderer path={item} />,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const pages = [
|
||||
'/search',
|
||||
'/integrations',
|
||||
'/environments',
|
||||
'/context',
|
||||
'/segments',
|
||||
'/tag-types',
|
||||
'/applications',
|
||||
'/strategies',
|
||||
];
|
||||
|
||||
export const PageSuggestions = ({
|
||||
routes,
|
||||
}: {
|
||||
routes: Record<string, { path: string; route: string; title: string }>;
|
||||
}) => {
|
||||
const filtered = pages.filter((page) => routes[page]);
|
||||
const pageItems = toListItemData(filtered, routes);
|
||||
return (
|
||||
<>
|
||||
<StyledTypography color='textSecondary'>Pages</StyledTypography>
|
||||
<List>
|
||||
{pageItems.map((item, index) => (
|
||||
<ListItemButton
|
||||
key={`recently-visited-${index}`}
|
||||
dense={true}
|
||||
component={Link}
|
||||
to={item.path}
|
||||
sx={listItemButtonStyle}
|
||||
>
|
||||
<StyledListItemIcon>{item.icon}</StyledListItemIcon>
|
||||
<StyledListItemText>
|
||||
<Typography color='textPrimary'>
|
||||
{item.name}
|
||||
</Typography>
|
||||
</StyledListItemText>
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
</>
|
||||
);
|
||||
};
|
@ -14,6 +14,8 @@ import {
|
||||
} from 'component/layout/MainLayout/NavigationSidebar/IconRenderer';
|
||||
import type { LastViewedPage } from 'hooks/useRecentlyVisited';
|
||||
import type { Theme } from '@mui/material/styles/createTheme';
|
||||
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
const listItemButtonStyle = (theme: Theme) => ({
|
||||
borderRadius: theme.spacing(0.5),
|
||||
@ -37,55 +39,127 @@ const StyledListItemText = styled(ListItemText)(({ theme }) => ({
|
||||
margin: 0,
|
||||
}));
|
||||
|
||||
const toListItemData = (lastVisited: LastViewedPage[]) => {
|
||||
return lastVisited.map((item) => {
|
||||
if (item.featureId) {
|
||||
return {
|
||||
name: item.featureId,
|
||||
path: `/projects/${item.projectId}/features/${item.featureId}`,
|
||||
icon: <Icon>{'flag'}</Icon>,
|
||||
};
|
||||
}
|
||||
if (item.projectId) {
|
||||
return {
|
||||
name: item.projectId,
|
||||
path: `/projects/${item.projectId}`,
|
||||
icon: <StyledProjectIcon />,
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: item.featureId ?? item.projectId ?? item.pathName,
|
||||
path: item.pathName || '/',
|
||||
icon: <IconRenderer path={item.pathName ?? '/unknown'} />,
|
||||
};
|
||||
});
|
||||
const toListItemButton = (
|
||||
item: LastViewedPage,
|
||||
routes: Record<string, { path: string; route: string; title: string }>,
|
||||
index: number,
|
||||
) => {
|
||||
const key = `recently-visited-${index}`;
|
||||
if (item.featureId && item.projectId) {
|
||||
return (
|
||||
<RecentlyVisitedFeatureButton
|
||||
key={key}
|
||||
featureId={item.featureId}
|
||||
projectId={item.projectId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (item.projectId) {
|
||||
return (
|
||||
<RecentlyVisitedProjectButton
|
||||
key={key}
|
||||
projectId={item.projectId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (!item.pathName) return null;
|
||||
const name = routes[item.pathName]?.title ?? item.pathName;
|
||||
return (
|
||||
<RecentlyVisitedPathButton key={key} path={item.pathName} name={name} />
|
||||
);
|
||||
};
|
||||
|
||||
const RecentlyVisitedFeatureButton = ({
|
||||
key,
|
||||
projectId,
|
||||
featureId,
|
||||
}: { key: string; projectId: string; featureId: string }) => {
|
||||
return (
|
||||
<ListItemButton
|
||||
key={key}
|
||||
dense={true}
|
||||
component={Link}
|
||||
to={`/projects/${projectId}/features/${featureId}`}
|
||||
sx={listItemButtonStyle}
|
||||
>
|
||||
<StyledListItemIcon>
|
||||
<Icon>{'flag'}</Icon>
|
||||
</StyledListItemIcon>
|
||||
<StyledListItemText>
|
||||
<Typography color='textPrimary'>{featureId}</Typography>
|
||||
</StyledListItemText>
|
||||
</ListItemButton>
|
||||
);
|
||||
};
|
||||
|
||||
const RecentlyVisitedPathButton = ({
|
||||
path,
|
||||
key,
|
||||
name,
|
||||
}: { path: string; key: string; name: string }) => {
|
||||
return (
|
||||
<ListItemButton
|
||||
key={key}
|
||||
dense={true}
|
||||
component={Link}
|
||||
to={path}
|
||||
sx={listItemButtonStyle}
|
||||
>
|
||||
<StyledListItemIcon>
|
||||
<ConditionallyRender
|
||||
condition={path === '/projects'}
|
||||
show={<StyledProjectIcon />}
|
||||
elseShow={<IconRenderer path={path} />}
|
||||
/>
|
||||
</StyledListItemIcon>
|
||||
<StyledListItemText>
|
||||
<Typography color='textPrimary'>{name}</Typography>
|
||||
</StyledListItemText>
|
||||
</ListItemButton>
|
||||
);
|
||||
};
|
||||
|
||||
const RecentlyVisitedProjectButton = ({
|
||||
projectId,
|
||||
key,
|
||||
}: { projectId: string; key: string }) => {
|
||||
const { project, loading } = useProjectOverview(projectId);
|
||||
const projectDeleted = !project.name && !loading;
|
||||
if (projectDeleted) return null;
|
||||
return (
|
||||
<ListItemButton
|
||||
key={key}
|
||||
dense={true}
|
||||
component={Link}
|
||||
to={`/projects/${projectId}`}
|
||||
sx={listItemButtonStyle}
|
||||
>
|
||||
<StyledListItemIcon>
|
||||
<StyledProjectIcon />
|
||||
</StyledListItemIcon>
|
||||
<StyledListItemText>
|
||||
<Typography color='textPrimary'>{project.name}</Typography>
|
||||
</StyledListItemText>
|
||||
</ListItemButton>
|
||||
);
|
||||
};
|
||||
|
||||
export const RecentlyVisited = ({
|
||||
lastVisited,
|
||||
}: { lastVisited: LastViewedPage[] }) => {
|
||||
const listItems = toListItemData(lastVisited);
|
||||
routes,
|
||||
}: {
|
||||
lastVisited: LastViewedPage[];
|
||||
routes: Record<string, { path: string; route: string; title: string }>;
|
||||
}) => {
|
||||
const buttons = lastVisited.map((item, index) =>
|
||||
toListItemButton(item, routes, index),
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<StyledTypography color='textSecondary'>
|
||||
Recently visited
|
||||
</StyledTypography>
|
||||
<List>
|
||||
{listItems.map((item, index) => (
|
||||
<ListItemButton
|
||||
key={`recently-visited-${index}`}
|
||||
dense={true}
|
||||
component={Link}
|
||||
to={item.path}
|
||||
sx={listItemButtonStyle}
|
||||
>
|
||||
<StyledListItemIcon>{item.icon}</StyledListItemIcon>
|
||||
<StyledListItemText>
|
||||
<Typography>{item.name}</Typography>
|
||||
</StyledListItemText>
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
<List>{buttons}</List>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -12,6 +12,7 @@ import UsersIcon from '@mui/icons-material/GroupOutlined';
|
||||
import ServiceAccountIcon from '@mui/icons-material/Computer';
|
||||
import GroupsIcon from '@mui/icons-material/GroupsOutlined';
|
||||
import RoleIcon from '@mui/icons-material/AdminPanelSettingsOutlined';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import ApiAccessIcon from '@mui/icons-material/KeyOutlined';
|
||||
import SingleSignOnIcon from '@mui/icons-material/AssignmentOutlined';
|
||||
import NetworkIcon from '@mui/icons-material/HubOutlined';
|
||||
@ -32,6 +33,7 @@ import { styled } from '@mui/material';
|
||||
|
||||
// TODO: move to routes
|
||||
const icons: Record<string, typeof SvgIcon> = {
|
||||
'/search': SearchIcon,
|
||||
'/applications': ApplicationsIcon,
|
||||
'/context': ContextFieldsIcon,
|
||||
'/feature-toggle-type': FlagTypesIcon,
|
||||
|
Loading…
Reference in New Issue
Block a user