Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement a button to switch between new and old UI #609

Merged
merged 5 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion lib/static/components/controls/report-info.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ function ReportInfo(props) {
const onNewUiButtonClick = () => {
setUiMode(UiMode.New);

window.location.pathname = window.location.pathname.replace(/\/(index\.html)?$/, (match, ending) => ending ? '/new-ui.html' : '/new-ui');
const targetUrl = new URL(window.location.href);

targetUrl.pathname = targetUrl.pathname.replace(/\/(index\.html)?$/, (match, ending) => ending ? '/new-ui.html' : '/new-ui');
targetUrl.searchParams.set('switched-from-old-ui', '1');

window.location.href = targetUrl.href;
};

return (
Expand Down
67 changes: 67 additions & 0 deletions lib/static/new-ui/components/MainLayout/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {Gear} from '@gravity-ui/icons';
import {FooterItem, MenuItem as GravityMenuItem} from '@gravity-ui/navigation';
import {Icon} from '@gravity-ui/uikit';
import classNames from 'classnames';
import React, {ReactNode, useEffect, useState} from 'react';
import {useSelector} from 'react-redux';

import {UiModeHintNotification} from '@/static/new-ui/components/UiModeHintNotification';
import styles from '@/static/new-ui/components/MainLayout/index.module.css';
import {getIsInitialized} from '@/static/new-ui/store/selectors';
import useLocalStorage from '@/static/hooks/useLocalStorage';
import {PanelId} from '@/static/new-ui/components/MainLayout/index';

interface FooterProps {
visiblePanel: PanelId | null;
onFooterItemClick: (item: GravityMenuItem) => void;
}

export function Footer(props: FooterProps): ReactNode {
const isInitialized = useSelector(getIsInitialized);
const [isHintVisible, setIsHintVisible] = useState<undefined | boolean>(undefined);
const [wasHintShownBefore, setWasHintShownBefore] = useLocalStorage('ui-mode-hint-shown', false);

useEffect(() => {
const hasJustSwitched = new URL(window.location.href).searchParams.get('switched-from-old-ui') === '1';
if (isInitialized && hasJustSwitched && !wasHintShownBefore) {
setIsHintVisible(true);
setWasHintShownBefore(true);

const timeoutId = setTimeout(() => {
setIsHintVisible(false);
}, 20000);

return () => {
clearTimeout(timeoutId);
};
}

return;
}, [isInitialized]);

useEffect(() => {
if (isHintVisible && props.visiblePanel) {
setIsHintVisible(false);
}
}, [props.visiblePanel]);

const isCurrent = props.visiblePanel === PanelId.Settings;

return <>
<UiModeHintNotification isVisible={isHintVisible} onClose={(): void => setIsHintVisible(false)} />
<FooterItem compact={false} item={{
id: PanelId.Settings,
title: 'Settings',
onItemClick: props.onFooterItemClick,
current: isCurrent,
itemWrapper: (params, makeItem) => makeItem({
...params,
icon: <Icon className={classNames({
[styles.footerItem]: !isCurrent,
[styles['footer-item--active']]: isCurrent,
disabled: !isInitialized
})} data={Gear} />
})
}} />
</>;
}
27 changes: 4 additions & 23 deletions lib/static/new-ui/components/MainLayout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {Gear} from '@gravity-ui/icons';
import {AsideHeader, FooterItem, MenuItem as GravityMenuItem} from '@gravity-ui/navigation';
import {Icon} from '@gravity-ui/uikit';
import {AsideHeader, MenuItem as GravityMenuItem} from '@gravity-ui/navigation';
import classNames from 'classnames';
import React, {ReactNode, useState} from 'react';
import {useSelector} from 'react-redux';
Expand All @@ -10,8 +8,9 @@ import {getIsInitialized} from '@/static/new-ui/store/selectors';
import {SettingsPanel} from '@/static/new-ui/components/SettingsPanel';
import TestplaneIcon from '../../../icons/testplane-mono.svg';
import styles from './index.module.css';
import {Footer} from './Footer';

enum PanelId {
export enum PanelId {
Settings = 'settings',
}

Expand Down Expand Up @@ -55,25 +54,7 @@ export function MainLayout(props: MainLayoutProps): JSX.Element {
customBackgroundClassName={styles.asideHeaderBgWrapper}
renderContent={(): React.ReactNode => props.children}
hideCollapseButton={true}
renderFooter={({compact}): ReactNode => {
const isCurrent = visiblePanel === PanelId.Settings;
return <>
<FooterItem compact={compact} item={{
id: PanelId.Settings,
title: 'Settings',
onItemClick: onFooterItemClick,
current: isCurrent,
itemWrapper: (params, makeItem) => makeItem({
...params,
icon: <Icon className={classNames({
[styles.footerItem]: !isCurrent,
[styles['footer-item--active']]: isCurrent,
disabled: !isInitialized
})} data={Gear} />
})
}} />
</>;
}}
renderFooter={(): ReactNode => <Footer visiblePanel={visiblePanel} onFooterItemClick={onFooterItemClick}/>}
panelItems={[{
id: PanelId.Settings,
children: <SettingsPanel />,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
@keyframes notification-appear {
0% {
opacity: 0;
visibility: visible;
filter: blur(20px);
}

30% {
opacity: 1;
}

80% {
filter: blur(0px);
transform: scale(1.02);
}

100% {
filter: blur(0px);
transform: scale(1);
visibility: visible;
}
}

@keyframes notification-disappear {
0% {
opacity: 1;
visibility: visible;
}

70% {
opacity: 1;
}

100% {
filter: blur(20px);
opacity: 0;
visibility: hidden;
}
}

.container {
filter: blur(0px);
position: fixed;
bottom: 6px;
left: 62px;
z-index: 999;
background: #6c47ff;
padding: 14px 14px;
border-radius: 10px;
color: rgba(255, 255, 255, .9);
fill: rgba(255, 255, 255, .9);
width: 700px;
display: flex;
gap: 8px;
box-shadow: 0 0 16px 0 #00000036;
align-items: center;

visibility: hidden;
}

.visible {
animation: notification-appear .6s linear forwards;
}

.hidden {
animation: notification-disappear .4s linear forwards;
}

@keyframes arrow-shake {
0% { transform: translateX(-3px); }
50% { transform: translateX(3px); }
100% { transform: translateX(-3px); }
}

.arrow {
animation: arrow-shake 6s infinite ease;
}

.hint-title {
font-weight: 500;
}

.close-button {
cursor: pointer;
transition: opacity .4s ease;
margin-left: auto;
}

.close-button:hover {
opacity: 0.7;
}
26 changes: 26 additions & 0 deletions lib/static/new-ui/components/UiModeHintNotification/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {createPortal} from 'react-dom';
import React, {ReactNode} from 'react';
import {ArrowLeft, Xmark} from '@gravity-ui/icons';

import styles from './index.module.css';
import classNames from 'classnames';

interface HintNotificationProps {
isVisible?: boolean;
onClose?: () => unknown;
}

export function UiModeHintNotification(props: HintNotificationProps): ReactNode {
return createPortal(<div className={classNames(styles.container, {
[styles.visible]: props.isVisible,
[styles.hidden]: props.isVisible === false
})}>
<ArrowLeft className={styles.arrow}/>
<div className={styles.hintTitle}>Hint</div>
<svg viewBox="0 0 2 2" height={2}>
<circle r="1" cx="1" cy="1"></circle>
</svg>
<div>You can always switch back to the old UI in Settings</div>
<Xmark className={styles.closeButton} onClick={(): unknown => props.onClose?.()}/>
</div>, document.body);
}
Loading