1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

feat: command bar pages and name resolving (#7397)

This commit is contained in:
David Leek 2024-06-14 11:22:55 +02:00 committed by GitHub
parent 25947c3075
commit 9b789ea5ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 229 additions and 41 deletions

View File

@ -21,6 +21,8 @@ import {
CommandResultGroup, CommandResultGroup,
type CommandResultGroupItem, type CommandResultGroupItem,
} from './RecentlyVisited/CommandResultGroup'; } from './RecentlyVisited/CommandResultGroup';
import { PageSuggestions } from './PageSuggestions';
import { useRoutes } from 'component/layout/MainLayout/NavigationSidebar/useRoutes';
import { useAsyncDebounce } from 'react-table'; import { useAsyncDebounce } from 'react-table';
import useProjects from 'hooks/api/getters/useProjects/useProjects'; import useProjects from 'hooks/api/getters/useProjects/useProjects';
@ -89,6 +91,23 @@ export const CommandBar = () => {
CommandResultGroupItem[] CommandResultGroupItem[]
>([]); >([]);
const { lastVisited } = useRecentlyVisited(); 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 = () => { const hideSuggestions = () => {
setShowSuggestions(false); setShowSuggestions(false);
}; };
@ -212,7 +231,11 @@ export const CommandBar = () => {
elseShow={ elseShow={
showSuggestions && ( showSuggestions && (
<CommandResultsPaper className='dropdown-outline'> <CommandResultsPaper className='dropdown-outline'>
<RecentlyVisited lastVisited={lastVisited} /> <RecentlyVisited
lastVisited={lastVisited}
routes={allRoutes}
/>
<PageSuggestions routes={allRoutes} />
</CommandResultsPaper> </CommandResultsPaper>
) )
} }

View 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>
</>
);
};

View File

@ -14,6 +14,8 @@ import {
} from 'component/layout/MainLayout/NavigationSidebar/IconRenderer'; } from 'component/layout/MainLayout/NavigationSidebar/IconRenderer';
import type { LastViewedPage } from 'hooks/useRecentlyVisited'; import type { LastViewedPage } from 'hooks/useRecentlyVisited';
import type { Theme } from '@mui/material/styles/createTheme'; 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) => ({ const listItemButtonStyle = (theme: Theme) => ({
borderRadius: theme.spacing(0.5), borderRadius: theme.spacing(0.5),
@ -37,55 +39,127 @@ const StyledListItemText = styled(ListItemText)(({ theme }) => ({
margin: 0, margin: 0,
})); }));
const toListItemData = (lastVisited: LastViewedPage[]) => { const toListItemButton = (
return lastVisited.map((item) => { item: LastViewedPage,
if (item.featureId) { routes: Record<string, { path: string; route: string; title: string }>,
return { index: number,
name: item.featureId, ) => {
path: `/projects/${item.projectId}/features/${item.featureId}`, const key = `recently-visited-${index}`;
icon: <Icon>{'flag'}</Icon>, if (item.featureId && item.projectId) {
}; return (
<RecentlyVisitedFeatureButton
key={key}
featureId={item.featureId}
projectId={item.projectId}
/>
);
} }
if (item.projectId) { if (item.projectId) {
return { return (
name: item.projectId, <RecentlyVisitedProjectButton
path: `/projects/${item.projectId}`, key={key}
icon: <StyledProjectIcon />, projectId={item.projectId}
}; />
);
} }
return { if (!item.pathName) return null;
name: item.featureId ?? item.projectId ?? item.pathName, const name = routes[item.pathName]?.title ?? item.pathName;
path: item.pathName || '/', return (
icon: <IconRenderer path={item.pathName ?? '/unknown'} />, <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 = ({ export const RecentlyVisited = ({
lastVisited, lastVisited,
}: { lastVisited: LastViewedPage[] }) => { routes,
const listItems = toListItemData(lastVisited); }: {
lastVisited: LastViewedPage[];
routes: Record<string, { path: string; route: string; title: string }>;
}) => {
const buttons = lastVisited.map((item, index) =>
toListItemButton(item, routes, index),
);
return ( return (
<> <>
<StyledTypography color='textSecondary'> <StyledTypography color='textSecondary'>
Recently visited Recently visited
</StyledTypography> </StyledTypography>
<List> <List>{buttons}</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>
</> </>
); );
}; };

View File

@ -12,6 +12,7 @@ import UsersIcon from '@mui/icons-material/GroupOutlined';
import ServiceAccountIcon from '@mui/icons-material/Computer'; import ServiceAccountIcon from '@mui/icons-material/Computer';
import GroupsIcon from '@mui/icons-material/GroupsOutlined'; import GroupsIcon from '@mui/icons-material/GroupsOutlined';
import RoleIcon from '@mui/icons-material/AdminPanelSettingsOutlined'; import RoleIcon from '@mui/icons-material/AdminPanelSettingsOutlined';
import SearchIcon from '@mui/icons-material/Search';
import ApiAccessIcon from '@mui/icons-material/KeyOutlined'; import ApiAccessIcon from '@mui/icons-material/KeyOutlined';
import SingleSignOnIcon from '@mui/icons-material/AssignmentOutlined'; import SingleSignOnIcon from '@mui/icons-material/AssignmentOutlined';
import NetworkIcon from '@mui/icons-material/HubOutlined'; import NetworkIcon from '@mui/icons-material/HubOutlined';
@ -32,6 +33,7 @@ import { styled } from '@mui/material';
// TODO: move to routes // TODO: move to routes
const icons: Record<string, typeof SvgIcon> = { const icons: Record<string, typeof SvgIcon> = {
'/search': SearchIcon,
'/applications': ApplicationsIcon, '/applications': ApplicationsIcon,
'/context': ContextFieldsIcon, '/context': ContextFieldsIcon,
'/feature-toggle-type': FlagTypesIcon, '/feature-toggle-type': FlagTypesIcon,