Skip to content

Commit

Permalink
feat: implement a button to switch between new and old UI (#609)
Browse files Browse the repository at this point in the history
* feat: implement a button to switch between new and old UI

* fix: get rid of unnecessary invisible space

* fix: disable settings button while loading

* feat: add notification on how to switch back to old UI
  • Loading branch information
shadowusr authored Oct 16, 2024
1 parent 33ce925 commit dcebd53
Show file tree
Hide file tree
Showing 22 changed files with 486 additions and 44 deletions.
8 changes: 8 additions & 0 deletions lib/constants/local-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export enum LocalStorageKey {
UIMode = 'ui-mode'
}

export enum UiMode {
Old = 'old',
New = 'new',
}
5 changes: 3 additions & 2 deletions lib/static/components/controls/browser-list/index.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

import {Button, Select, useSelectOptions} from '@gravity-ui/uikit';
import classNames from 'classnames';
import React, {useState, useMemo, useEffect} from 'react';
import {compact} from 'lodash';
import PropTypes from 'prop-types';
Expand All @@ -8,7 +10,6 @@ import {mkBrowserIcon, buildComplexId} from './utils';

import 'react-checkbox-tree/lib/react-checkbox-tree.css';
import './index.styl';
import {Button, Select, useSelectOptions} from '@gravity-ui/uikit';

const BrowserList = ({available, onChange, selected: selectedProp}) => {
const getOptions = () => {
Expand Down Expand Up @@ -138,7 +139,7 @@ const BrowserList = ({available, onChange, selected: selectedProp}) => {
<div className='browserlist__row_content'>
{option.content}
</div>
<Button size='s' onClick={isTheOnlySelected ? selectExcept : selectOnly} className='action-button'>{isTheOnlySelected ? 'Except' : 'Only'}</Button>
<Button size='s' onClick={isTheOnlySelected ? selectExcept : selectOnly} className={classNames('regular-button', 'action-button')}>{isTheOnlySelected ? 'Except' : 'Only'}</Button>
</div>
);
};
Expand Down
101 changes: 101 additions & 0 deletions lib/static/components/controls/controls.less
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,108 @@
width: 200px;
}

@property --gradient-angle {
syntax: "<angle>";
inherits: false;
initial-value: 0turn;
}

@property --from-color {
syntax: "<color>";
inherits: false;
initial-value: #00ffff00;
}

@property --to-color {
syntax: "<color>";
inherits: false;
initial-value: #eee;
}

.report-info {
.new-ui-button {
margin-right: 1em;
transition: color 1s linear;
position: relative;

@keyframes glow {
from {background-position: 0}
to {background-position: 400%}
}

& .new-ui-button__glow {
opacity: 0;
left: 0;
filter: blur(10px);
transition: opacity 1s ease;
animation: 20s linear infinite glow;
position: absolute;
width: 100%;
height: 100%;
background-image: linear-gradient(90deg, #03a9f4, #f441a5, #ffeb3b, #03a9f4);
border-radius: 5px;
background-size: 400%;
z-index: -5;
}

&:hover .new-ui-button__glow {
opacity: 1;
}

@keyframes color-fade {
from {color: black}
to {color: white}
}

&:hover {
animation: 1s color-fade ease forwards;
animation-delay: .6s;
}

@keyframes pulse {
0% {
--gradient-angle: 0deg;
--to-color: rgb(108,71,255);
}

90% {
--to-color: rgb(108,71,255);
--from-color: #00ffff00;
}

100% {
--gradient-angle: 180deg;
--from-color: rgb(108,71,255);
--to-color: rgb(108,71,255);
}
}

&::before {
transition: none;
background-image: conic-gradient(from var(--gradient-angle) at -10% 100%, var(--from-color) 0%, var(--to-color) 100%);
}

&:hover::before {
animation: 1s ease pulse forwards;
}

@keyframes bg-fade {
from { background-color: #eee }
to { background-color: rgb(108,71,255) }
}

&::after {
background-color: #eee;
margin: 2px;
border-radius: 4px;
}

&:hover::after {
animation: 1s ease bg-fade forwards;
animation-delay: .7s;
}
}

.label {
margin: 0 15px 0 0;
}
Expand Down
63 changes: 39 additions & 24 deletions lib/static/components/controls/report-info.jsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,51 @@
import React, {Component} from 'react';
import {Flask} from '@gravity-ui/icons';
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {Label} from '@gravity-ui/uikit';
import {Button, Icon, Label} from '@gravity-ui/uikit';
import {isEmpty} from 'lodash';
import {version} from '../../../../package.json';
import useLocalStorage from '@/static/hooks/useLocalStorage';
import {LocalStorageKey, UiMode} from '@/constants/local-storage';

class ReportInfo extends Component {
static propTypes = {
gui: PropTypes.bool.isRequired,
timestamp: PropTypes.number.isRequired
function ReportInfo(props) {
const {gui, timestamp} = props;
const lang = isEmpty(navigator.languages) ? navigator.language : navigator.languages[0];
const date = new Date(timestamp).toLocaleString(lang);

const [, setUiMode] = useLocalStorage(LocalStorageKey.UIMode, UiMode.New);

const onNewUiButtonClick = () => {
setUiMode(UiMode.New);

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;
};

render() {
const {gui, timestamp} = this.props;
const lang = isEmpty(navigator.languages) ? navigator.language : navigator.languages[0];
const date = new Date(timestamp).toLocaleString(lang);

return (
<div className="report-info">
<Label qa='version-label' size='m' className='label'>
Version
<div className='detail'>{version}</div>
</Label>
{!gui && <Label qa='created-at-label' size='m' className='label'>
Created at
<div className='detail'>{date}</div>
</Label>}
</div>
);
}
return (
<div className="report-info">
<Button className={'new-ui-button'} onClick={onNewUiButtonClick}><div className='new-ui-button__glow'></div><Icon data={Flask}/>Try New UI</Button>
<Label qa='version-label' size='m' className='label'>
Version
<div className='detail'>{version}</div>
</Label>
{!gui && <Label qa='created-at-label' size='m' className='label'>
Created at
<div className='detail'>{date}</div>
</Label>}
</div>
);
}

ReportInfo.propTypes = {
gui: PropTypes.bool.isRequired,
timestamp: PropTypes.number.isRequired
};

export default connect(
({gui, timestamp}) => ({gui, timestamp})
)(ReportInfo);
5 changes: 5 additions & 0 deletions lib/static/hooks/useLocalStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import {useCallback, useEffect, useState} from 'react';
import useEventListener from './useEventListener';
import * as localStorageWrapper from '../modules/local-storage-wrapper';

/**
* @param key
* @param initialValue
* @returns {[*, function]} An array containing the current state value and a function to update it.
*/
export default function useLocalStorage(key, initialValue) {
const readValue = useCallback(() => {
return localStorageWrapper.getItem(key, initialValue);
Expand Down
5 changes: 4 additions & 1 deletion lib/static/new-ui.css
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,12 @@ body {
}
}

.action-button {
.regular-button {
font-size: 15px;
font-weight: 450;
}

.action-button {
/* Sets spinner color */
--g-color-line-brand: var(--g-color-text-hint);
}
6 changes: 2 additions & 4 deletions lib/static/new-ui/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import {ThemeProvider} from '@gravity-ui/uikit';
import React, {ReactNode, StrictMode} from 'react';
import {MainLayout} from '../components/MainLayout';
import {HashRouter, Navigate, Route, Routes} from 'react-router-dom';
import {CircleInfo, Eye, ListCheck} from '@gravity-ui/icons';
import {Eye, ListCheck} from '@gravity-ui/icons';
import {SuitesPage} from '../features/suites/components/SuitesPage';
import {VisualChecksPage} from '../features/visual-checks/components/VisualChecksPage';
import {InfoPage} from '../features/info/components/InfoPage';

import '@gravity-ui/uikit/styles/fonts.css';
import '@gravity-ui/uikit/styles/styles.css';
Expand All @@ -23,8 +22,7 @@ export function App(): ReactNode {
element: <SuitesPage/>,
children: [<Route key={'suite'} path=':suiteId' element= {<SuitesPage/>} />]
},
{title: 'Visual Checks', url: '/visual-checks', icon: Eye, element: <VisualChecksPage/>},
{title: 'Info', url: '/info', icon: CircleInfo, element: <InfoPage/>}
{title: 'Visual Checks', url: '/visual-checks', icon: Eye, element: <VisualChecksPage/>}
];

return <StrictMode>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
}

.retry-button {
composes: action-button from global;
composes: regular-button from global, action-button from global;
margin-left: auto;
}
2 changes: 1 addition & 1 deletion lib/static/new-ui/components/LoadingBar/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
height: 30px;
width: 100%;
position: absolute;
z-index: 99;
z-index: 999999;
display: flex;
align-items: center;
flex-direction: column;
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<boolean | null>(null);
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} />
})
}} />
</>;
}
13 changes: 13 additions & 0 deletions lib/static/new-ui/components/MainLayout/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,16 @@
background-color: var(--color-bg-dark);
width: 100%;
}

:global(.gn-composite-bar-item):has(.footer-item:global(.disabled)) {
pointer-events: none;
opacity: .5;
}

.footer-item {
color: var(--gn-aside-header-item-icon-color) !important;
}

.footer-item--active {
color: var(--gn-aside-header-item-current-icon-color) !important;
}
Loading

0 comments on commit dcebd53

Please sign in to comment.