Skip to content

Commit

Permalink
[PUI] Quick commands pallet (#6987)
Browse files Browse the repository at this point in the history
* add spotlight

* [PUI] Quick commands pallet
Fixes #5888

* add testing for new commands

* add text input testing

* only test backend if code changed

* add trans files

* fix testing text

* always push coverage

* add nav state to manage navigation state

* add navigation action and test

* make test faster

* fix typo

* use texts instead

* fix tests for linux

* use var to determine action key

* Revert "use texts instead"

This reverts commit 7771189.

* add wait for input

* split out keyboard based tests

* split ou test

* add upload

* revert assert change

* adjust reporting settings

* ignore error code

* fix reporter config

* add full info suit (+tests)

* make tests more accurate

* license modal fixes

* unify icons

* add custom actions registering
with removal on page refresh

* only upload report data if the tests failed

* Revert "add trans files"

This reverts commit 28d96e0.

* adjust url that iw waited for

* try an await and body locator for keypresses

* test registering addition actions

* extend testing for actions

* add doclink and test

* merge tests
  • Loading branch information
matmair authored Apr 11, 2024
1 parent ff8eeca commit cbbdb70
Show file tree
Hide file tree
Showing 16 changed files with 377 additions and 32 deletions.
1 change: 1 addition & 0 deletions src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@mantine/hooks": "<7",
"@mantine/modals": "<7",
"@mantine/notifications": "<7",
"@mantine/spotlight": "<7",
"@naisutech/react-tree": "^3.1.0",
"@sentry/react": "^7.109.0",
"@tabler/icons-react": "^3.1.0",
Expand Down
15 changes: 15 additions & 0 deletions src/frontend/src/components/buttons/SpotlightButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { t } from '@lingui/macro';
import { ActionIcon } from '@mantine/core';
import { spotlight } from '@mantine/spotlight';
import { IconCommand } from '@tabler/icons-react';

/**
* A button which opens the quick command modal
*/
export function SpotlightButton() {
return (
<ActionIcon onClick={() => spotlight.open()} title={t`Open spotlight`}>
<IconCommand />
</ActionIcon>
);
}
4 changes: 4 additions & 0 deletions src/frontend/src/components/items/MenuLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export interface MenuLinkItem {
docchildren?: React.ReactNode;
}

export type menuItemsCollection = {
[key: string]: MenuLinkItem;
};

function ConditionalDocTooltip({
item,
children
Expand Down
23 changes: 21 additions & 2 deletions src/frontend/src/components/nav/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import { ActionIcon, Container, Group, Indicator, Tabs } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { IconBell, IconSearch } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useMatch, useNavigate, useParams } from 'react-router-dom';

import { api } from '../../App';
import { navTabs as mainNavTabs } from '../../defaults/links';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { InvenTreeStyle } from '../../globalStyle';
import { apiUrl } from '../../states/ApiState';
import { useLocalState } from '../../states/LocalState';
import { ScanButton } from '../buttons/ScanButton';
import { SpotlightButton } from '../buttons/SpotlightButton';
import { MainMenu } from './MainMenu';
import { NavHoverMenu } from './NavHoverMenu';
import { NavigationDrawer } from './NavigationDrawer';
Expand All @@ -19,8 +21,12 @@ import { SearchDrawer } from './SearchDrawer';

export function Header() {
const { classes } = InvenTreeStyle();
const [setNavigationOpen, navigationOpen] = useLocalState((state) => [
state.setNavigationOpen,
state.navigationOpen
]);
const [navDrawerOpened, { open: openNavDrawer, close: closeNavDrawer }] =
useDisclosure(false);
useDisclosure(navigationOpen);
const [
searchDrawerOpened,
{ open: openSearchDrawer, close: closeSearchDrawer }
Expand Down Expand Up @@ -59,6 +65,18 @@ export function Header() {
refetchOnWindowFocus: false
});

// Sync Navigation Drawer state with zustand
useEffect(() => {
if (navigationOpen === navDrawerOpened) return;
setNavigationOpen(navDrawerOpened);
}, [navDrawerOpened]);

useEffect(() => {
if (navigationOpen === navDrawerOpened) return;
if (navigationOpen) openNavDrawer();
else closeNavDrawer();
}, [navigationOpen]);

return (
<div className={classes.layoutHeader}>
<SearchDrawer opened={searchDrawerOpened} onClose={closeSearchDrawer} />
Expand All @@ -80,6 +98,7 @@ export function Header() {
<ActionIcon onClick={openSearchDrawer}>
<IconSearch />
</ActionIcon>
<SpotlightButton />
<ScanButton />
<ActionIcon onClick={openNotificationDrawer}>
<Indicator
Expand Down
49 changes: 40 additions & 9 deletions src/frontend/src/components/nav/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { t } from '@lingui/macro';
import { Container, Flex, Space } from '@mantine/core';
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { SpotlightProvider } from '@mantine/spotlight';
import { IconSearch } from '@tabler/icons-react';
import { useEffect, useState } from 'react';
import { Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';

import { getActions } from '../../defaults/actions';
import { InvenTreeStyle } from '../../globalStyle';
import { useSessionState } from '../../states/SessionState';
import { Footer } from './Footer';
Expand All @@ -22,17 +27,43 @@ export const ProtectedRoute = ({ children }: { children: JSX.Element }) => {

export default function LayoutComponent() {
const { classes } = InvenTreeStyle();
const navigate = useNavigate();
const location = useLocation();

const defaultactions = getActions(navigate);
const [actions, setActions] = useState(defaultactions);
const [customActions, setCustomActions] = useState<boolean>(false);

function actionsAreChanging(change: []) {
if (change.length > defaultactions.length) setCustomActions(true);
setActions(change);
}
useEffect(() => {
if (customActions) {
setActions(defaultactions);
setCustomActions(false);
}
}, [location]);

return (
<ProtectedRoute>
<Flex direction="column" mih="100vh">
<Header />
<Container className={classes.layoutContent} size="100%">
<Outlet />
</Container>
<Space h="xl" />
<Footer />
</Flex>
<SpotlightProvider
actions={actions}
onActionsChange={actionsAreChanging}
searchIcon={<IconSearch size="1.2rem" />}
searchPlaceholder={t`Search...`}
shortcut={['mod + K', '/']}
nothingFoundMessage={t`Nothing found...`}
>
<Flex direction="column" mih="100vh">
<Header />
<Container className={classes.layoutContent} size="100%">
<Outlet />
</Container>
<Space h="xl" />
<Footer />
</Flex>
</SpotlightProvider>
</ProtectedRoute>
);
}
4 changes: 3 additions & 1 deletion src/frontend/src/components/nav/NavHoverMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { useLocalState } from '../../states/LocalState';
import { InvenTreeLogo } from '../items/InvenTreeLogo';
import { MenuLinks } from '../items/MenuLinks';

const onlyItems = Object.values(menuItems);

export function NavHoverMenu({
openDrawer: openDrawer
}: {
Expand Down Expand Up @@ -85,7 +87,7 @@ export function NavHoverMenu({
mx="-md"
color={theme.colorScheme === 'dark' ? 'dark.5' : 'gray.1'}
/>
<MenuLinks links={menuItems} highlighted={true} />
<MenuLinks links={onlyItems} highlighted={true} />
<div className={classes.headerDropdownFooter}>
<Group position="apart">
<div>
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/src/components/nav/NavigationDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { MenuLinkItem, MenuLinks } from '../items/MenuLinks';

// TODO @matmair #1: implement plugin loading and menu item generation see #5269
const plugins: MenuLinkItem[] = [];
const onlyItems = Object.values(menuItems);

export function NavigationDrawer({
opened,
Expand Down Expand Up @@ -60,7 +61,7 @@ function DrawerContent() {
<Container className={classes.layoutContent} p={0}>
<ScrollArea h={scrollHeight} type="always" offsetScrollbars>
<Title order={5}>{t`Pages`}</Title>
<MenuLinks links={menuItems} />
<MenuLinks links={onlyItems} />
<Space h="md" />
{plugins.length > 0 ? (
<>
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/src/contexts/LanguageContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export function LanguageContext({ children }: { children: JSX.Element }) {
// Clear out cached table column names
useLocalState.getState().clearTableColumnNames();
})
/* istanbul ignore next */
.catch((err) => {
console.error('Failed loading translations', err);
if (isMounted.current) setLoadedState('error');
Expand All @@ -115,6 +116,7 @@ export function LanguageContext({ children }: { children: JSX.Element }) {
return <LoadingOverlay visible={true} />;
}

/* istanbul ignore next */
if (loadedState === 'error') {
return (
<Text>
Expand Down
59 changes: 59 additions & 0 deletions src/frontend/src/defaults/actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { t } from '@lingui/macro';
import type { SpotlightAction } from '@mantine/spotlight';
import { IconHome, IconLink, IconPointer } from '@tabler/icons-react';
import { NavigateFunction } from 'react-router-dom';

import { useLocalState } from '../states/LocalState';
import { aboutInvenTree, docLinks, licenseInfo, serverInfo } from './links';
import { menuItems } from './menuItems';

export function getActions(navigate: NavigateFunction) {
const setNavigationOpen = useLocalState((state) => state.setNavigationOpen);

const actions: SpotlightAction[] = [
{
title: t`Home`,
description: `Go to the home page`,
onTrigger: () => navigate(menuItems.home.link),
icon: <IconHome size="1.2rem" />
},
{
title: t`Dashboard`,
description: t`Go to the InvenTree dashboard`,
onTrigger: () => navigate(menuItems.dashboard.link),
icon: <IconLink size="1.2rem" />
},
{
title: t`Documentation`,
description: t`Visit the documentation to learn more about InvenTree`,
onTrigger: () => (window.location.href = docLinks.faq),
icon: <IconLink size="1.2rem" />
},
{
title: t`About InvenTree`,
description: t`About the InvenTree org`,
onTrigger: () => aboutInvenTree(),
icon: <IconLink size="1.2rem" />
},
{
title: t`Server Information`,
description: t`About this Inventree instance`,
onTrigger: () => serverInfo(),
icon: <IconLink size="1.2rem" />
},
{
title: t`License Information`,
description: t`Licenses for dependencies of the service`,
onTrigger: () => licenseInfo(),
icon: <IconLink size="1.2rem" />
},
{
title: t`Open Navigation`,
description: t`Open the main navigation menu`,
onTrigger: () => setNavigationOpen(true),
icon: <IconPointer size="1.2rem" />
}
];

return actions;
}
7 changes: 4 additions & 3 deletions src/frontend/src/defaults/links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const navDocLinks: DocumentationLinkItem[] = [
}
];

function serverInfo() {
export function serverInfo() {
return openContextModal({
modal: 'info',
title: (
Expand All @@ -84,7 +84,7 @@ function serverInfo() {
});
}

function aboutInvenTree() {
export function aboutInvenTree() {
return openContextModal({
modal: 'about',
title: (
Expand All @@ -96,7 +96,8 @@ function aboutInvenTree() {
innerProps: {}
});
}
function licenseInfo() {

export function licenseInfo() {
return openContextModal({
modal: 'license',
title: (
Expand Down
32 changes: 16 additions & 16 deletions src/frontend/src/defaults/menuItems.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,75 @@
import { Trans } from '@lingui/macro';

import { MenuLinkItem } from '../components/items/MenuLinks';
import { menuItemsCollection } from '../components/items/MenuLinks';
import { IS_DEV_OR_DEMO } from '../main';

export const menuItems: MenuLinkItem[] = [
{
export const menuItems: menuItemsCollection = {
home: {
id: 'home',
text: <Trans>Home</Trans>,
link: '/',
highlight: true
},
{
profile: {
id: 'profile',
text: <Trans>Account settings</Trans>,
link: '/settings/user',
doctext: <Trans>User attributes and design settings.</Trans>
},
{
scan: {
id: 'scan',
text: <Trans>Scanning</Trans>,
link: '/scan',
doctext: <Trans>View for interactive scanning and multiple actions.</Trans>,
highlight: true
},
{
dashboard: {
id: 'dashboard',
text: <Trans>Dashboard</Trans>,
link: '/dashboard'
},
{
parts: {
id: 'parts',
text: <Trans>Parts</Trans>,
link: '/part/'
},
{
stock: {
id: 'stock',
text: <Trans>Stock</Trans>,
link: '/stock'
},
{
build: {
id: 'build',
text: <Trans>Build</Trans>,
link: '/build/'
},
{
purchasing: {
id: 'purchasing',
text: <Trans>Purchasing</Trans>,
link: '/purchasing/'
},
{
sales: {
id: 'sales',
text: <Trans>Sales</Trans>,
link: '/sales/'
},
{
'settings-system': {
id: 'settings-system',
text: <Trans>System Settings</Trans>,
link: '/settings/system'
},
{
'settings-admin': {
id: 'settings-admin',
text: <Trans>Admin Center</Trans>,
link: '/settings/admin'
}
];
};

if (IS_DEV_OR_DEMO) {
menuItems.push({
menuItems['playground'] = {
id: 'playground',
text: <Trans>Playground</Trans>,
link: '/playground',
highlight: true
});
};
}
Loading

0 comments on commit cbbdb70

Please sign in to comment.