Tune in command palette (#389)
This commit is contained in:
parent
800db9a139
commit
c458c24c91
|
@ -55,6 +55,7 @@ html, body {
|
|||
font-size: @font-size-sm;
|
||||
color: @text-color;
|
||||
z-index: @bars-z-index;
|
||||
padding-top: 5px;
|
||||
// border-top-width: 1px;
|
||||
// border-top-color: @border-color-split;
|
||||
// border-top-style: solid;
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
useMemo,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import {
|
||||
ActionId,
|
||||
|
@ -17,7 +18,10 @@ import {
|
|||
KBarResults,
|
||||
useMatches,
|
||||
ActionImpl,
|
||||
Action,
|
||||
useKBar,
|
||||
} from 'kbar';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
CloudUploadOutlined,
|
||||
LoginOutlined,
|
||||
|
@ -25,6 +29,11 @@ import {
|
|||
LogoutOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useHistory } from 'react-router';
|
||||
import {
|
||||
Config as ConfigType,
|
||||
Tune as TuneType,
|
||||
Menus as MenusType,
|
||||
} from '@speedy-tuner/types';
|
||||
import { Routes } from '../routes';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import {
|
||||
|
@ -33,6 +42,16 @@ import {
|
|||
} from '../pages/auth/notifications';
|
||||
import store from '../store';
|
||||
import { isMac } from '../utils/env';
|
||||
import {
|
||||
AppState,
|
||||
NavigationState,
|
||||
} from '../types/state';
|
||||
import {
|
||||
buildUrl,
|
||||
SKIP_MENUS,
|
||||
SKIP_SUB_MENUS,
|
||||
} from './Tune/SideBar';
|
||||
import Icon from './SideBar/Icon';
|
||||
|
||||
enum Sections {
|
||||
NAVIGATION = 'Navigation',
|
||||
|
@ -183,12 +202,71 @@ const RenderResults = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const CommandPalette = (props: { children: ReactNode }) => {
|
||||
const { children } = props;
|
||||
const history = useHistory();
|
||||
const { logout } = useAuth();
|
||||
const mapStateToProps = (state: AppState) => ({
|
||||
config: state.config,
|
||||
tune: state.tune,
|
||||
ui: state.ui,
|
||||
navigation: state.navigation,
|
||||
});
|
||||
|
||||
const logoutAction = useCallback(async() => {
|
||||
interface CommandPaletteProps {
|
||||
config: ConfigType;
|
||||
tune: TuneType;
|
||||
navigation: NavigationState;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
const ActionsProvider = (props: CommandPaletteProps) => {
|
||||
const { config, tune, navigation } = props;
|
||||
const { query } = useKBar();
|
||||
const history = useHistory();
|
||||
|
||||
const generateActions = useCallback((types: MenusType) => {
|
||||
const newActions: Action[] = [];
|
||||
|
||||
Object.keys(types).forEach((menuName: string) => {
|
||||
if (SKIP_MENUS.includes(menuName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(types[menuName].subMenus).forEach((subMenuName: string) => {
|
||||
if (subMenuName === 'std_separator') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SKIP_SUB_MENUS.includes(`${menuName}/${subMenuName}`)) {
|
||||
return;
|
||||
}
|
||||
const subMenu = types[menuName].subMenus[subMenuName];
|
||||
|
||||
newActions.push({
|
||||
id: buildUrl(navigation.tuneId!, menuName, subMenuName),
|
||||
section: types[menuName].title,
|
||||
name: subMenu.title,
|
||||
icon: <Icon name={subMenuName} />,
|
||||
perform: () => history.push(buildUrl(navigation.tuneId!, menuName, subMenuName)),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return newActions;
|
||||
}, [history, navigation.tuneId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(tune.constants).length) {
|
||||
query.registerActions(generateActions(config.menus));
|
||||
}
|
||||
}, [config.menus, generateActions, query, tune.constants]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const CommandPalette = (props: CommandPaletteProps) => {
|
||||
const { children, config, tune, navigation } = props;
|
||||
const { logout } = useAuth();
|
||||
const history = useHistory();
|
||||
|
||||
const logoutAction = useCallback(async () => {
|
||||
try {
|
||||
await logout();
|
||||
logOutSuccessful();
|
||||
|
@ -198,7 +276,7 @@ const CommandPalette = (props: { children: ReactNode }) => {
|
|||
}
|
||||
}, [logout]);
|
||||
|
||||
const initialActions = useMemo(() => [
|
||||
const initialActions = [
|
||||
{
|
||||
id: 'ToggleSidebar',
|
||||
name: 'Toggle Sidebar',
|
||||
|
@ -207,37 +285,37 @@ const CommandPalette = (props: { children: ReactNode }) => {
|
|||
},
|
||||
{
|
||||
id: 'UploadAction',
|
||||
name: 'Upload',
|
||||
section: Sections.NAVIGATION,
|
||||
icon: <CloudUploadOutlined />,
|
||||
name: 'Upload',
|
||||
subtitle: 'Upload tune and logs.',
|
||||
icon: <CloudUploadOutlined />,
|
||||
perform: () => history.push(Routes.UPLOAD),
|
||||
},
|
||||
{
|
||||
id: 'LoginAction',
|
||||
name: 'Login',
|
||||
section: Sections.AUTH,
|
||||
icon: <LoginOutlined />,
|
||||
name: 'Login',
|
||||
subtitle: 'Login using email, Google or GitHub account.',
|
||||
icon: <LoginOutlined />,
|
||||
perform: () => history.push(Routes.LOGIN),
|
||||
},
|
||||
{
|
||||
id: 'SignUpAction',
|
||||
name: 'Sign-up',
|
||||
section: Sections.AUTH,
|
||||
icon: <UserAddOutlined />,
|
||||
name: 'Sign-up',
|
||||
subtitle: 'Create new account.',
|
||||
icon: <UserAddOutlined />,
|
||||
perform: () => history.push(Routes.SIGN_UP),
|
||||
},
|
||||
{
|
||||
id: 'LogoutAction',
|
||||
name: 'Logout',
|
||||
section: Sections.AUTH,
|
||||
icon: <LogoutOutlined />,
|
||||
name: 'Logout',
|
||||
subtitle: 'Logout current user.',
|
||||
icon: <LogoutOutlined />,
|
||||
perform: logoutAction,
|
||||
},
|
||||
], [history, logoutAction]);
|
||||
];
|
||||
|
||||
return (
|
||||
<KBarProvider actions={initialActions}>
|
||||
|
@ -249,9 +327,10 @@ const CommandPalette = (props: { children: ReactNode }) => {
|
|||
</KBarAnimator>
|
||||
</KBarPositioner>
|
||||
</KBarPortal>
|
||||
<ActionsProvider config={config} tune={tune} navigation={navigation} />
|
||||
{children}
|
||||
</KBarProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommandPalette;
|
||||
export default connect(mapStateToProps)(CommandPalette);
|
||||
|
|
|
@ -19,20 +19,39 @@ import {
|
|||
Menus as MenusType,
|
||||
Tune as TuneType,
|
||||
} from '@speedy-tuner/types';
|
||||
import store from '../store';
|
||||
import Icon from './SideBar/Icon';
|
||||
import { evaluateExpression } from '../utils/tune/expression';
|
||||
import { Routes } from '../routes';
|
||||
import useConfig from '../hooks/useConfig';
|
||||
import store from '../../store';
|
||||
import Icon from '../SideBar/Icon';
|
||||
import { Routes } from '../../routes';
|
||||
import useConfig from '../../hooks/useConfig';
|
||||
import {
|
||||
AppState,
|
||||
NavigationState,
|
||||
UIState,
|
||||
} from '../types/state';
|
||||
} from '../../types/state';
|
||||
|
||||
const { Sider } = Layout;
|
||||
const { SubMenu } = Menu;
|
||||
|
||||
export const SKIP_MENUS = [
|
||||
'help',
|
||||
'hardwareTesting',
|
||||
'3dTuningMaps',
|
||||
'dataLogging',
|
||||
'tools',
|
||||
];
|
||||
|
||||
export const SKIP_SUB_MENUS = [
|
||||
'settings/gaugeLimits',
|
||||
'settings/io_summary',
|
||||
'tuning/std_realtime',
|
||||
];
|
||||
|
||||
export const buildUrl = (tuneId: string, main: string, sub: string) => generatePath(Routes.TUNE_DIALOG, {
|
||||
tuneId,
|
||||
category: main,
|
||||
dialog: sub,
|
||||
});
|
||||
|
||||
export interface DialogMatchedPathType {
|
||||
url: string;
|
||||
params: {
|
||||
|
@ -48,33 +67,15 @@ const mapStateToProps = (state: AppState) => ({
|
|||
navigation: state.navigation,
|
||||
});
|
||||
|
||||
const SKIP_MENUS = [
|
||||
'help',
|
||||
'hardwareTesting',
|
||||
'3dTuningMaps',
|
||||
'dataLogging',
|
||||
'tools',
|
||||
];
|
||||
interface SideBarProps {
|
||||
config: ConfigType;
|
||||
tune: TuneType;
|
||||
ui: UIState;
|
||||
navigation: NavigationState;
|
||||
matchedPath: DialogMatchedPathType;
|
||||
};
|
||||
|
||||
const SKIP_SUB_MENUS = [
|
||||
'settings/gaugeLimits',
|
||||
'settings/io_summary',
|
||||
'tuning/std_realtime',
|
||||
];
|
||||
|
||||
const SideBar = ({
|
||||
config,
|
||||
tune,
|
||||
ui,
|
||||
navigation,
|
||||
matchedPath,
|
||||
}: {
|
||||
config: ConfigType,
|
||||
tune: TuneType,
|
||||
ui: UIState,
|
||||
navigation: NavigationState,
|
||||
matchedPath: DialogMatchedPathType,
|
||||
}) => {
|
||||
const SideBar = ({ config, tune, ui, navigation, matchedPath }: SideBarProps) => {
|
||||
const sidebarWidth = 250;
|
||||
const siderProps = {
|
||||
width: sidebarWidth,
|
||||
|
@ -84,12 +85,6 @@ const SideBar = ({
|
|||
onCollapse: (collapsed: boolean) => store.dispatch({ type: 'ui/sidebarCollapsed', payload: collapsed }),
|
||||
} as any;
|
||||
const { isConfigReady } = useConfig(config);
|
||||
const checkCondition = useCallback((condition: string) => evaluateExpression(condition, tune.constants, config), [tune.constants, config]);
|
||||
const buildUrl = useCallback((main: string, sub: string) => generatePath(Routes.TUNE_DIALOG, {
|
||||
tuneId: navigation.tuneId!,
|
||||
category: main,
|
||||
dialog: sub,
|
||||
}), [navigation.tuneId]);
|
||||
const [menus, setMenus] = useState<any[]>([]);
|
||||
|
||||
const menusList = useCallback((types: MenusType) => (
|
||||
|
@ -103,29 +98,23 @@ const SideBar = ({
|
|||
key={`/${menuName}`}
|
||||
icon={<Icon name={menuName} />}
|
||||
title={types[menuName].title}
|
||||
onTitleClick={() => store.dispatch({ type: 'ui/sidebarCollapsed', payload: false })}
|
||||
>
|
||||
{Object.keys(types[menuName].subMenus).map((subMenuName: string) => {
|
||||
if (subMenuName === 'std_separator') {
|
||||
return <Menu.Divider key={buildUrl(menuName, subMenuName)} />;
|
||||
return <Menu.Divider key={buildUrl(navigation.tuneId!, menuName, subMenuName)} />;
|
||||
}
|
||||
|
||||
if (SKIP_SUB_MENUS.includes(`${menuName}/${subMenuName}`)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const subMenu = types[menuName].subMenus[subMenuName];
|
||||
let enabled = true;
|
||||
|
||||
if (subMenu.condition) {
|
||||
enabled = checkCondition(subMenu.condition);
|
||||
}
|
||||
|
||||
return (<Menu.Item
|
||||
key={buildUrl(menuName, subMenuName)}
|
||||
key={buildUrl(navigation.tuneId!, menuName, subMenuName)}
|
||||
icon={<Icon name={subMenuName} />}
|
||||
disabled={!enabled}
|
||||
>
|
||||
<Link to={buildUrl(menuName, subMenuName)}>
|
||||
<Link to={buildUrl(navigation.tuneId!, menuName, subMenuName)}>
|
||||
{subMenu.title}
|
||||
</Link>
|
||||
</Menu.Item>);
|
||||
|
@ -133,7 +122,7 @@ const SideBar = ({
|
|||
</SubMenu>
|
||||
);
|
||||
})
|
||||
), [buildUrl, checkCondition]);
|
||||
), [navigation.tuneId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(tune.constants).length) {
|
|
@ -9,7 +9,7 @@ import { connect } from 'react-redux';
|
|||
import { useMemo } from 'react';
|
||||
import { Config as ConfigType } from '@speedy-tuner/types';
|
||||
import Dialog from '../components/Tune/Dialog';
|
||||
import SideBar, { DialogMatchedPathType } from '../components/SideBar';
|
||||
import SideBar, { DialogMatchedPathType } from '../components/Tune/SideBar';
|
||||
import { Routes } from '../routes';
|
||||
import useBrowserStorage from '../hooks/useBrowserStorage';
|
||||
import useConfig from '../hooks/useConfig';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
@layout-header-height: 45px;
|
||||
|
||||
@layout-footer-padding: 2px 10px;
|
||||
@layout-footer-height: 24px;
|
||||
@layout-footer-height: 28px;
|
||||
|
||||
@bars-z-index: 5;
|
||||
@zindex-modal: 1080;
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
|
||||
// Side
|
||||
@layout-sider-background: @main;
|
||||
@layout-trigger-background: @main;
|
||||
@layout-trigger-background: @component-background;
|
||||
@layout-trigger-color: @text-color;
|
||||
|
||||
// Tooltip
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
@layout-header-background: @main;
|
||||
|
||||
@layout-sider-background: @main;
|
||||
@layout-trigger-background: @main;
|
||||
@layout-trigger-background: @component-background;
|
||||
@layout-trigger-color: @text-color;
|
||||
|
|
Loading…
Reference in New Issue