diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..739127c7e --- /dev/null +++ b/codecov.yml @@ -0,0 +1,3 @@ +ignore: + - "src/sources/Blheli/*" + - "src/sources/Blheli/**/*" diff --git a/package.json b/package.json index c7047d524..2edfb05b5 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,11 @@ "license": "AGPL-3.0", "dependencies": { "@babel/plugin-transform-react-jsx": "^7.16.7", - "@palmabit/react-cookie-law": "^0.6.2", + "@emotion/react": "^11.7.1", + "@emotion/styled": "^11.6.0", + "@fontsource/roboto": "^4.5.1", + "@mui/icons-material": "^5.2.5", + "@mui/material": "^5.2.7", "autoprefixer": "^10.4.2", "bluejay-rtttl-parse": "^2.0.2", "compare-versions": "^4.1.3", diff --git a/src/Components/App/index.jsx b/src/Components/App/index.jsx index 2c26c6970..b3792ad69 100644 --- a/src/Components/App/index.jsx +++ b/src/Components/App/index.jsx @@ -4,6 +4,13 @@ import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import React from 'react'; +import { + createTheme, + ThemeProvider, +} from '@mui/material/styles'; + +import Box from '@mui/material/Box'; + import AppSettings from '../AppSettings'; import CookieConsent from '../CookieConsent'; import LanguageSelection from '../LanguageSelection'; @@ -16,10 +23,21 @@ import Statusbar from '../Statusbar'; import changelogEntries from '../../changelog.json'; import './style.scss'; +const theme = createTheme({ + palette: { + primary: { + main: '#6a8347', + bright: '#71b238', + ph: '', + }, + }, +}); + function App({ actions, appSettings, configs, + cookieDone, escs, language, melodies, @@ -36,45 +54,47 @@ function App({ const isIdle = !Object.values(actions).some((element) => element); return ( -
-
+ +
-
-
- - - -
- +
+
+ + -
- -
+
+ + +
+ +
+
-
- + +
-
- - {appSettings.show && - } - - {melodies.show && - } - - + + + + + + + -
+ ); } @@ -169,6 +192,7 @@ App.propTypes = { show: PropTypes.bool.isRequired, }).isRequired, configs: PropTypes.shape({}).isRequired, + cookieDone: PropTypes.bool.isRequired, escs: PropTypes.shape({ actions: PropTypes.shape({ handleMasterUpdate: PropTypes.func.isRequired, diff --git a/src/Components/App/style.scss b/src/Components/App/style.scss index 20aded676..444230303 100644 --- a/src/Components/App/style.scss +++ b/src/Components/App/style.scss @@ -1,3 +1,5 @@ +@import '../../variables.scss'; + * { margin: 0; padding: 0; @@ -7,13 +9,9 @@ } body { - font-family: 'open_sansregular', 'Segoe UI', Tahoma, sans-serif; - font-size: 12px; - color: #303030; background-color: lightgray; margin: 0px; padding: 0px; - width: 100%; overflow-x: hidden; } @@ -141,8 +139,10 @@ body { } .main { + /* padding: 0px 0px 0 0px; height: calc(100% - 7px); + */ .main__header { background: #3d3f3e; @@ -239,7 +239,6 @@ body { a { text-decoration: none; color: #000; - font-family: 'open_sanssemibold', Arial; } a:hover { @@ -316,9 +315,11 @@ body { border-radius: 4px; border: 1px solid var(--color-tertiary); color: var(--color-tertiary); + /* font-family: 'open_sanssemibold', Arial; font-size: 12px; line-height: 13px; + */ display: block; width: 100%; transition: all ease 0.2s; @@ -363,7 +364,7 @@ body { .content_wrapper { padding: 20px; position: relative; - margin-bottom: 50px; + margin-bottom: 70px; @media only screen and (max-width: 600px) { margin-bottom: 120px; diff --git a/src/Components/AppSettings/__tests__/index.test.jsx b/src/Components/AppSettings/__tests__/index.test.jsx index eb321cf72..71abccd98 100644 --- a/src/Components/AppSettings/__tests__/index.test.jsx +++ b/src/Components/AppSettings/__tests__/index.test.jsx @@ -28,12 +28,13 @@ describe('AppSettings', () => { ); expect(screen.getByText(/settingsHeader/i)).toBeInTheDocument(); - expect(screen.getByText(/close/i)).toBeInTheDocument(); + expect(screen.getByTestId('CloseIcon')).toBeInTheDocument(); }); it('should handle clicks on checkbox', () => { @@ -51,6 +52,7 @@ describe('AppSettings', () => { ); @@ -71,11 +73,12 @@ describe('AppSettings', () => { ); - userEvent.click(screen.getByText(/close/i)); + userEvent.click(screen.getByTestId('CloseIcon')); expect(onClose).toHaveBeenCalled(); }); @@ -94,6 +97,7 @@ describe('AppSettings', () => { ); diff --git a/src/Components/AppSettings/index.jsx b/src/Components/AppSettings/index.jsx index 2013cbcdd..c3138a41b 100644 --- a/src/Components/AppSettings/index.jsx +++ b/src/Components/AppSettings/index.jsx @@ -2,15 +2,17 @@ import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import React, { useCallback } from 'react'; +import FormControl from '@mui/material/FormControl'; +import Stack from '@mui/material/Stack'; + import Checkbox from '../Input/Checkbox'; import Overlay from '../Overlay'; -import './style.scss'; - function AppSettings({ settings, onClose, onUpdate, + open, }) { const { t } = useTranslation('settings'); @@ -43,27 +45,33 @@ function AppSettings({ return null; } } - - return null; }); return ( -
- + -
+ {settingElements} -
-
-
+ + + ); } AppSettings.propTypes = { onClose: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired, + open: PropTypes.bool.isRequired, settings: PropTypes.shape().isRequired, }; diff --git a/src/Components/AppSettings/style.scss b/src/Components/AppSettings/style.scss deleted file mode 100644 index d4752603d..000000000 --- a/src/Components/AppSettings/style.scss +++ /dev/null @@ -1,5 +0,0 @@ -.settings { - .checkbox { - display: flex; - } -} diff --git a/src/Components/Buttonbar/GenericButton/index.jsx b/src/Components/Buttonbar/GenericButton/index.jsx index de46cb422..551d0c76a 100644 --- a/src/Components/Buttonbar/GenericButton/index.jsx +++ b/src/Components/Buttonbar/GenericButton/index.jsx @@ -1,30 +1,46 @@ import PropTypes from 'prop-types'; import React from 'react'; -import './style.scss'; +import Button from '@mui/material/Button'; function GenericButton({ + className, disabled, + fullWidth, onClick, + sx, text, + variant, }) { return ( -
- -
+ ); } -GenericButton.defaultProps = { disabled: false }; + +GenericButton.defaultProps = { + className: null, + disabled: false, + fullWidth: false, + sx: null, + variant: 'contained', +}; GenericButton.propTypes = { + className: PropTypes.string, disabled: PropTypes.bool, + fullWidth: PropTypes.bool, onClick: PropTypes.func.isRequired, + sx: PropTypes.shape({}), text: PropTypes.string.isRequired, + variant: PropTypes.string, }; export default GenericButton; diff --git a/src/Components/Buttonbar/GenericButton/style.scss b/src/Components/Buttonbar/GenericButton/style.scss deleted file mode 100644 index 6eb297373..000000000 --- a/src/Components/Buttonbar/GenericButton/style.scss +++ /dev/null @@ -1,42 +0,0 @@ -.generic-button { - button { - cursor: pointer; - margin-top: 0px; - margin-bottom: 0px; - margin-right: 20px; - border-radius: 3px; - background-color: var(--color-primary); - border: 1px solid var(--color-primary); - color: #fff; - font-family: 'open_sansbold', Arial; - font-size: 12px; - text-shadow: 0px 1px rgba(0, 0, 0, 0.25); - display: block; - transition: all ease 0.2s; - padding: 0px; - padding-left: 9px; - padding-right: 9px; - line-height: 28px; - } - - button:hover { - background-color: var(--color-secondary); - transition: all ease 0.2s; - } - - button:active { - background-color: var(--color-primary); - transition: all ease 0.0s; - box-shadow: inset 0px 1px 5px rgba(0, 0, 0, 0.35); - } - - button[disabled] { - cursor: default; - color: #fff; - background-color: #AFAFAF; - border: 1px solid #AFAFAF; - pointer-events: none; - text-shadow: none; - opacity: 0.5; - } -} diff --git a/src/Components/Buttonbar/__tests__/index.test.jsx b/src/Components/Buttonbar/__tests__/index.test.jsx index 927fb42cc..6720f88d2 100644 --- a/src/Components/Buttonbar/__tests__/index.test.jsx +++ b/src/Components/Buttonbar/__tests__/index.test.jsx @@ -8,15 +8,26 @@ import Buttonbar from '../'; jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key) => key }) })); +let onClearLog; +let onOpenMelodyEditor; +let onReadSetup; +let onResetDefaults; +let onSaveLog; +let onSelectFirmwareForAll; +let onWriteSetup; + describe('Buttonbar', () => { - it('should display buttons', () => { - const onWriteSetup = jest.fn(); - const onReadSetup = jest.fn(); - const onResetDefaults = jest.fn(); - const onSaveLog = jest.fn(); - const onSeletFirmwareForAll = jest.fn(); - const onOpenMelodyEditor = jest.fn(); + beforeEach(() => { + onClearLog = jest.fn(); + onWriteSetup = jest.fn(); + onReadSetup = jest.fn(); + onResetDefaults = jest.fn(); + onSaveLog = jest.fn(); + onSelectFirmwareForAll = jest.fn(); + onOpenMelodyEditor = jest.fn(); + }); + it('should display buttons', () => { render( { canReadDefaults={false} canResetDefaults={false} canWrite={false} + onClearLog={onClearLog} onOpenMelodyEditor={onOpenMelodyEditor} onReadSetup={onReadSetup} onResetDefaults={onResetDefaults} onSaveLog={onSaveLog} - onSeletFirmwareForAll={onSeletFirmwareForAll} + onSeletFirmwareForAll={onSelectFirmwareForAll} onWriteSetup={onWriteSetup} showMelodyEditor /> @@ -43,13 +55,6 @@ describe('Buttonbar', () => { }); it('should always trigger log save', () => { - const onWriteSetup = jest.fn(); - const onReadSetup = jest.fn(); - const onResetDefaults = jest.fn(); - const onSaveLog = jest.fn(); - const onSeletFirmwareForAll = jest.fn(); - const onOpenMelodyEditor = jest.fn(); - render( { canReadDefaults={false} canResetDefaults={false} canWrite={false} + onClearLog={onClearLog} onOpenMelodyEditor={onOpenMelodyEditor} onReadSetup={onReadSetup} onResetDefaults={onResetDefaults} onSaveLog={onSaveLog} - onSeletFirmwareForAll={onSeletFirmwareForAll} + onSeletFirmwareForAll={onSelectFirmwareForAll} onWriteSetup={onWriteSetup} showMelodyEditor /> @@ -71,14 +77,30 @@ describe('Buttonbar', () => { expect(onSaveLog).toHaveBeenCalled(); }); - it('should not trigger handlers when disabled', () => { - const onWriteSetup = jest.fn(); - const onReadSetup = jest.fn(); - const onResetDefaults = jest.fn(); - const onSaveLog = jest.fn(); - const onSelectFirmwareForAll = jest.fn(); - const onOpenMelodyEditor = jest.fn(); + it('should always trigger log clear', () => { + render( + + ); + userEvent.click(screen.getByText(/escButtonClearLog/i)); + expect(onClearLog).toHaveBeenCalled(); + }); + + it('should not trigger handlers when disabled', () => { render( { canReadDefaults={false} canResetDefaults={false} canWrite={false} + onClearLog={onClearLog} onOpenMelodyEditor={onOpenMelodyEditor} onReadSetup={onReadSetup} onResetDefaults={onResetDefaults} @@ -96,27 +119,20 @@ describe('Buttonbar', () => { /> ); - userEvent.click(screen.queryAllByText(/resetDefaults/i)[1]); + expect(screen.queryAllByText(/resetDefaults/i)[1]).toHaveAttribute('disabled'); expect(onResetDefaults).not.toHaveBeenCalled(); - userEvent.click(screen.getByText(/escButtonRead/i)); + expect(screen.getByText(/escButtonRead/i)).toHaveAttribute('disabled'); expect(onReadSetup).not.toHaveBeenCalled(); - userEvent.click(screen.getByText(/escButtonWrite/i)); + expect(screen.getByText(/escButtonWrite/i)).toHaveAttribute('disabled'); expect(onWriteSetup).not.toHaveBeenCalled(); - userEvent.click(screen.getByText(/escButtonFlashAll/i)); + expect(screen.getByText(/escButtonFlashAll/i)).toHaveAttribute('disabled'); expect(onSelectFirmwareForAll).not.toHaveBeenCalled(); }); it('should trigger handlers when enabled', () => { - const onWriteSetup = jest.fn(); - const onReadSetup = jest.fn(); - const onResetDefaults = jest.fn(); - const onSaveLog = jest.fn(); - const onSelectFirmwareForAll = jest.fn(); - const onOpenMelodyEditor = jest.fn(); - render( { canReadDefaults canResetDefaults canWrite + onClearLog={onClearLog} onOpenMelodyEditor={onOpenMelodyEditor} onReadSetup={onReadSetup} onResetDefaults={onResetDefaults} diff --git a/src/Components/Buttonbar/index.jsx b/src/Components/Buttonbar/index.jsx index c62c0ee1c..8527680f1 100644 --- a/src/Components/Buttonbar/index.jsx +++ b/src/Components/Buttonbar/index.jsx @@ -2,9 +2,11 @@ import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import React from 'react'; -import GenericButton from './GenericButton'; +import Box from '@mui/material/Box'; +import ButtonGroup from '@mui/material/ButtonGroup'; +import Grid from '@mui/material/Grid'; -import './style.scss'; +import GenericButton from './GenericButton'; function Buttonbar({ onClearLog, @@ -23,69 +25,113 @@ function Buttonbar({ const { t } = useTranslation('common'); return ( -
-
- {showMelodyEditor && - } -
+ + + + + {showMelodyEditor && + } + -
- + + + - + -
- -
-
+ + +
-
- + + + {showMelodyEditor && + } - + - + - + - {showMelodyEditor && - } -
-
+ + + + + + ); } diff --git a/src/Components/Buttonbar/style.scss b/src/Components/Buttonbar/style.scss deleted file mode 100644 index 16cee6f4f..000000000 --- a/src/Components/Buttonbar/style.scss +++ /dev/null @@ -1,90 +0,0 @@ -@import '../../variables.scss'; - -.button-bar { - z-index: 1; - position: fixed; - bottom: 21px; - width: 100%; - background-color: #EFEFEF; - box-shadow: rgba(0, 0, 0, 0.10) 0 -3px 8px; - padding: 10px 0; - overflow: hidden; - border-top: 1px solid #F9F9F9; - display: flex; - flex-direction: row; - justify-content: space-between; - - @media only screen and (max-width: 600px) { - padding-bottom: 0px; - padding-top: 8px; - } - - & > div { - display: flex; - - flex-direction: row; - justify-content: flex-end; - align-items: center; - - &.buttons-right { - align-items: flex-end; - flex-direction: row-reverse; - } - - &.buttons-left { - justify-content: flex-start; - margin-left: 20px; - - @media only screen and (max-width: 600px) { - margin-left: 10px; - } - } - - &.mobile-show { - display: none; - } - } - - @media only screen and (max-width: 600px) { - flex-direction: column-reverse; - - & > div { - justify-content: flex-start; - - &.buttons-bottom, - &.buttons-left, - &.buttons-right { - margin-bottom: 8px; - margin-left: 5px; - margin-right: 5px; - - .generic-button { - flex: 1; - margin-left: 2px; - margin-right: 2px; - button { - width: 100%; - } - } - - &.mobile-show { - display: flex; - - button { - margin-bottom: 8px; - } - } - } - - &:first-child { - margin-bottom: 0px; - } - } - } - - @media only screen and (max-width: 600px) { - .generic-button button { - margin-right: 10px; - } - } -} diff --git a/src/Components/Changelog/Content/index.jsx b/src/Components/Changelog/Content/index.jsx index 26b42553c..bebe8d630 100644 --- a/src/Components/Changelog/Content/index.jsx +++ b/src/Components/Changelog/Content/index.jsx @@ -1,23 +1,31 @@ import PropTypes from 'prop-types'; import React from 'react'; +import Divider from '@mui/material/Divider'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; +import Typography from '@mui/material/Typography'; + function Content({ entries }) { const entryElements = entries.map((entry) => { const listItems = entry.items.map((item) => ( -
  • - {item} -
  • + + + )); return (
    - + {entry.title} - + + + -
      + {listItems} -
    +
    ); }); diff --git a/src/Components/Changelog/__tests__/index.test.jsx b/src/Components/Changelog/__tests__/index.test.jsx index 767c1c96e..2ee9eed09 100644 --- a/src/Components/Changelog/__tests__/index.test.jsx +++ b/src/Components/Changelog/__tests__/index.test.jsx @@ -25,7 +25,6 @@ describe('Changelog', () => { /> ); - expect(screen.getByText(/defaultChangelogHead/i)).toBeInTheDocument(); expect(screen.getByText(/defaultChangelogTitle/i)).toBeInTheDocument(); expect(screen.queryByText(/changelogClose/i)).not.toBeInTheDocument(); }); diff --git a/src/Components/Changelog/index.jsx b/src/Components/Changelog/index.jsx index b28da33bb..8a0f28d6a 100644 --- a/src/Components/Changelog/index.jsx +++ b/src/Components/Changelog/index.jsx @@ -5,12 +5,29 @@ import React, { useCallback, } from 'react'; -import Content from './Content'; +import { useTheme } from '@mui/material/styles'; + +import Box from '@mui/material/Box'; +import Drawer from '@mui/material/Drawer'; +import Typography from '@mui/material/Typography'; -import './style.scss'; +import Content from './Content'; function Changelog({ entries }) { const { t } = useTranslation('common'); + const theme = useTheme(); + + const buttonStyle = { + transform: 'rotate(270deg)', + position: 'absolute', + top: 185, + right: '-32px', + color: 'white', + borderRadius: '5px 5px 0 0', + background: theme.palette.primary.bright, + p: '5px 10px', + cursor: 'pointer', + }; const [state, setState] = useState({ expanded: false, @@ -26,25 +43,41 @@ function Changelog({ entries }) { }, [t, state.expanded]); return ( -
    -
    + {state.title} -
    + -
    -
    - {t('defaultChangelogHead')} -
    + + + + {t('defaultChangelogHead')} + -
    -
    -
    -
    + + + ); } diff --git a/src/Components/Changelog/style.scss b/src/Components/Changelog/style.scss deleted file mode 100644 index aeb8a4524..000000000 --- a/src/Components/Changelog/style.scss +++ /dev/null @@ -1,75 +0,0 @@ -.changelog { - width: 250px; - height: 100%; - position: fixed; - right: -245px; - top: 137px; - height: calc(100% - 137px - 21px); - - @media only screen and (max-width: 600px) { - top: 170px; - height: calc(100% - 170px - 21px); - } - - .changelog__wrapper { - height: 100%; - padding: 0 20px; - border-left: 5px solid var(--color-secondary); - overflow-y: auto; - display: none; - } - - &.expanded { - right: 0px; - background: white; - - .changelog__wrapper { - display: block; - } - } - - .button { - transform: rotate(270deg); - top: 50px; - right: 212px; - position: absolute; - background: var(--color-secondary); - border-radius: 5px 5px 0 0; - border-bottom: none; - display: block; - padding: 5px 10px; - width: 70px; - text-align: center; - color: white; - cursor: pointer; - } - - .changelog__title { - margin: 20px 0; - font-size: 16px; - } - - .changelog__content { - line-height: 17px; - - span { - display: block; - font-weight: bold; - padding-bottom: 5px; - border-bottom: 1px solid #ddd; - } - - ul { - margin: 5px 0 20px 10px; - - li { - font-weight: normal; - margin-bottom: 5px; - } - } - - p { - margin-bottom: 20px; - } - } -} diff --git a/src/Components/CookieConsent/__tests__/index.test.jsx b/src/Components/CookieConsent/__tests__/index.test.jsx new file mode 100644 index 000000000..df87f3f79 --- /dev/null +++ b/src/Components/CookieConsent/__tests__/index.test.jsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { + render, screen, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import CookieConsent from '../'; + +jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key) => key }) })); + +let onClick; + +describe('CookieConsent', () => { + beforeEach(() => { + onClick = jest.fn(); + }); + + it('should display', () => { + render( + + ); + + expect(screen.getByText(/cookieText/i)).toBeInTheDocument(); + expect(screen.getByText(/allow/i)).toBeInTheDocument(); + expect(screen.getByText(/deny/i)).toBeInTheDocument(); + }); + + it('should handle allow button', () => { + render( + + ); + + expect(screen.getByText(/cookieText/i)).toBeInTheDocument(); + expect(screen.getByText(/allow/i)).toBeInTheDocument(); + expect(screen.getByText(/deny/i)).toBeInTheDocument(); + + userEvent.click(screen.getByText(/allow/i)); + expect(onClick).toHaveBeenCalled(); + }); + + it('should handle deny button', () => { + render( + + ); + + expect(screen.getByText(/cookieText/i)).toBeInTheDocument(); + expect(screen.getByText(/allow/i)).toBeInTheDocument(); + expect(screen.getByText(/deny/i)).toBeInTheDocument(); + + userEvent.click(screen.getByText(/deny/i)); + expect(onClick).toHaveBeenCalled(); + }); +}); diff --git a/src/Components/CookieConsent/index.jsx b/src/Components/CookieConsent/index.jsx index 57baee0c2..6c2569b5c 100644 --- a/src/Components/CookieConsent/index.jsx +++ b/src/Components/CookieConsent/index.jsx @@ -1,52 +1,97 @@ -import { CookieBanner } from '@palmabit/react-cookie-law'; import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; -import React from 'react'; +import React, { useCallback } from 'react'; -function CookieConsent({ onCookieAccept }) { +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import ButtonGroup from '@mui/material/ButtonGroup'; +import Grid from '@mui/material/Grid'; +import Grow from '@mui/material/Grow'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; + +function CookieConsent({ + onCookieAccept, + show, +}) { const { t } = useTranslation('common'); + const handleAccept = useCallback(() => { + onCookieAccept(true); + }, [onCookieAccept]); + + const handleDeny = useCallback(() => { + onCookieAccept(false); + }, [onCookieAccept]); + return ( - + > + + + + + + {t('cookieText')} + + + + + + + + + + + + + + + + ); } -CookieConsent.propTypes = { onCookieAccept: PropTypes.func.isRequired }; +CookieConsent.propTypes = { + onCookieAccept: PropTypes.func.isRequired, + show: PropTypes.bool.isRequired, +}; export default React.memo(CookieConsent); diff --git a/src/Components/FirmwareSelector/__tests__/index.test.jsx b/src/Components/FirmwareSelector/__tests__/index.test.jsx index 2d85c5e73..65f8f6d4f 100644 --- a/src/Components/FirmwareSelector/__tests__/index.test.jsx +++ b/src/Components/FirmwareSelector/__tests__/index.test.jsx @@ -20,59 +20,28 @@ const mockJsonResponse = (content) => }); describe('FirmwareSelector', () => { + let onSubmit; + let onLocalSubmit; + let onCancel; + let configs; + beforeAll(async () => { /** * require component instead of import so that we can properly * pre-populate the local storage */ FirmwareSelector = require('../').default; - }); - - it('should display firmware selection', () => { - const configs = { - versions: {}, - escs: {}, - pwm: {}, - }; - - const onSubmit = jest.fn(); - const onLocalSubmit = jest.fn(); - const onCancel = jest.fn(); - - render( - - ); - expect(screen.getByText(/forceFlashText/i)).toBeInTheDocument(); - expect(screen.getByText(/forceFlashHint/i)).toBeInTheDocument(); - expect(screen.getByText(/migrateFlashText/i)).toBeInTheDocument(); - expect(screen.getByText(/migrateFlashHint/i)).toBeInTheDocument(); - expect(screen.getByText(/forceFlashText/i)).toBeInTheDocument(); - - expect(screen.getByText("escButtonSelect")).toBeInTheDocument(); - expect(screen.getByText(/escButtonSelectLocally/i)).toBeInTheDocument(); - expect(screen.getByText(/buttonCancel/i)).toBeInTheDocument(); - - expect(screen.getByText(/selectFirmware/i)).toBeInTheDocument(); - expect(screen.getByText(/selectTarget/i)).toBeInTheDocument(); - }); - - it('should allow changing firmware options for BLHeli_S', async() => { - const json = `[{ "tag_name": "v0.10", "assets": [{}] }]`; + const jsonString = `[{ "tag_name": "v0.15 (Test)", "assets": [{}] }, { "tag_name": "v1.78", "assets": [{}] }]`; global.caches = { open: jest.fn().mockImplementation(() => new Promise((resolve) => { - resolve({ match: () => new Promise((resolve) => resolve(mockJsonResponse(json))) }); + resolve({ match: () => new Promise((resolve) => resolve(mockJsonResponse(jsonString))) }); }) ), }; - const configs = { + configs = { versions: {}, escs: {}, pwm: {}, @@ -86,11 +55,37 @@ describe('FirmwareSelector', () => { configs.escs[name] = source.getEscLayouts(); configs.pwm[name] = source.getPwm(); } + }); - const onSubmit = jest.fn(); - const onLocalSubmit = jest.fn(); - const onCancel = jest.fn(); + beforeEach(() => { + onSubmit = jest.fn(); + onLocalSubmit = jest.fn(); + onCancel = jest.fn(); + }); + it('should display firmware selection', () => { + render( + + ); + + expect(screen.getByText('forceFlashText')).toBeInTheDocument(); + expect(screen.getByText('migrateFlashText')).toBeInTheDocument(); + expect(screen.getByText('forceFlashText')).toBeInTheDocument(); + + expect(screen.getByText('escButtonSelect')).toBeInTheDocument(); + expect(screen.getByText('escButtonSelectLocally')).toBeInTheDocument(); + expect(screen.getByText('buttonCancel')).toBeInTheDocument(); + + expect(screen.getByRole('button', { name: 'selectFirmware selectFirmware' })).toBeInTheDocument(); + expect(screen.getByText('selectTarget (UNKNOWN)')).toBeInTheDocument(); + }); + + it('should allow changing firmware options for BLHeli_S', async() => { const escMock = { settings: { LAYOUT: "#S_H_90#" }, meta: { signature: 0xE8B2 }, @@ -107,9 +102,7 @@ describe('FirmwareSelector', () => { ); expect(screen.getByText(/forceFlashText/i)).toBeInTheDocument(); - expect(screen.getByText(/forceFlashHint/i)).toBeInTheDocument(); expect(screen.getByText(/migrateFlashText/i)).toBeInTheDocument(); - expect(screen.getByText(/migrateFlashHint/i)).toBeInTheDocument(); expect(screen.getByText(/forceFlashText/i)).toBeInTheDocument(); expect(screen.getByText("escButtonSelect")).toBeInTheDocument(); @@ -119,59 +112,22 @@ describe('FirmwareSelector', () => { expect(screen.getByText(/selectFirmware/i)).toBeInTheDocument(); expect(screen.getByText(/selectTarget/i)).toBeInTheDocument(); - fireEvent.change(screen.getByRole(/combobox/i, { name: 'Firmware' }), { - target: { - value: 'BLHeli_S', - name: 'Firmware', - }, - }); - - fireEvent.change(screen.getByRole(/combobox/i, { name: 'ESC' }), { - target: { - value: '#S_H_50#', - name: 'ESC', - }, - }); - - fireEvent.change(screen.getByRole(/combobox/i, { name: 'Version' }), { - target: { - value: '16.7 [Official]', - name: 'Version', - }, - }); - - const checkboxes = screen.getAllByRole(/checkbox/i); - for(let i = 0; i < checkboxes.length; i += 1) { - userEvent.click(checkboxes[i]); - } + // Select Bluejay + fireEvent.mouseDown(screen.getByRole('button', { name: 'selectFirmware BLHeli_S' })); + let element = screen.getByRole('option', { name: 'Bluejay' }); + userEvent.click(element); + + // Layout selection not needed since it should be auto-detected + + // Select a version + fireEvent.mouseDown(screen.getByRole('button', { name: 'selectVersion selectVersion' })); + element = screen.getByRole('option', { name: '0.15 (Test)' }); + userEvent.click(element); - fireEvent.change(screen.getByRole(/combobox/i, { name: 'Firmware' }), { - target: { - value: 'Bluejay', - name: 'Firmware', - }, - }); - - fireEvent.change(screen.getByRole(/combobox/i, { name: 'ESC' }), { - target: { - value: '#S_H_50#', - name: 'ESC', - }, - }); - - fireEvent.change(screen.getByRole(/combobox/i, { name: 'Version' }), { - target: { - value: 'https://github.com/mathiasvr/bluejay/releases/download/v0.10/', - name: 'Version', - }, - }); - - fireEvent.change(screen.getByRole(/combobox/i, { name: 'PWM Frequency' }), { - target: { - value: '96', - name: 'PWM Frequency', - }, - }); + // Select PWM frequency + fireEvent.mouseDown(screen.getByRole('button', { name: 'selectPwmFrequency selectPwmFrequency' })); + element = screen.getByRole('option', { name: '96' }); + userEvent.click(element); userEvent.click(screen.getByText('escButtonSelect')); expect(onSubmit).toHaveBeenCalled(); @@ -184,35 +140,54 @@ describe('FirmwareSelector', () => { expect(onCancel).toHaveBeenCalled(); }); - it('should display title', async() => { - const json = `[{ "tag_name": "v0.10", "assets": [{}] }]`; - global.caches = { - open: jest.fn().mockImplementation(() => - new Promise((resolve) => { - resolve({ match: () => new Promise((resolve) => resolve(mockJsonResponse(json))) }); - }) - ), + it('should allow changing firmware layout', async() => { + const escMock = { + settings: { LAYOUT: "#S_H_90#" }, + meta: { signature: 0xE8B2 }, }; - const configs = { - versions: {}, - escs: {}, - pwm: {}, - }; + render( + + ); - for(let i = 0; i < sources.length; i += 1) { - const source = sources[i]; - const name = source.getName(); + // Select Bluejay + fireEvent.mouseDown(screen.getByRole('button', { name: 'selectFirmware BLHeli_S' })); + let element = screen.getByRole('option', { name: 'Bluejay' }); + userEvent.click(element); - configs.versions[name] = await source.getVersions(); - configs.escs[name] = source.getEscLayouts(); - configs.pwm[name] = source.getPwm(); - } + // Select different layout + fireEvent.mouseDown(screen.getByRole('button', { name: 'selectEsc S-H-90' })); + element = screen.getByRole('option', { name: 'S-H-50' }); + userEvent.click(element); + + // Select a version + fireEvent.mouseDown(screen.getByRole('button', { name: 'selectVersion selectVersion' })); + element = screen.getByRole('option', { name: '0.15 (Test)' }); + userEvent.click(element); - const onSubmit = jest.fn(); - const onLocalSubmit = jest.fn(); - const onCancel = jest.fn(); + // Select PWM frequency + fireEvent.mouseDown(screen.getByRole('button', { name: 'selectPwmFrequency selectPwmFrequency' })); + element = screen.getByRole('option', { name: '96' }); + userEvent.click(element); + + userEvent.click(screen.getByText('escButtonSelect')); + expect(onSubmit).toHaveBeenCalled(); + userEvent.click(screen.getByText('escButtonSelectLocally')); + fireEvent.change(screen.getByTestId('input-file')); + expect(onLocalSubmit).toHaveBeenCalled(); + + userEvent.click(screen.getByText('buttonCancel')); + expect(onCancel).toHaveBeenCalled(); + }); + + it('should display title', async() => { const escMock = { displayName: 'Display Name', settings: { LAYOUT: "#S_H_90#" }, @@ -233,36 +208,8 @@ describe('FirmwareSelector', () => { }); it('should allow changing firmware options for AM32', async() => { - const json = `[{ "tag_name": "v1.65", "assets": [{}] }]`; - global.caches = { - open: jest.fn().mockImplementation(() => - new Promise((resolve) => { - resolve({ match: () => new Promise((resolve) => resolve(mockJsonResponse(json))) }); - }) - ), - }; - - const configs = { - versions: {}, - escs: {}, - pwm: {}, - }; - - for(let i = 0; i < sources.length; i += 1) { - const source = sources[i]; - const name = source.getName(); - - configs.versions[name] = await source.getVersions(); - configs.escs[name] = source.getEscLayouts(); - configs.pwm[name] = source.getPwm(); - } - - const onSubmit = jest.fn(); - const onLocalSubmit = jest.fn(); - const onCancel = jest.fn(); - const escMock = { - settings: { LAYOUT: "T-MOTOR 55A" }, + settings: { LAYOUT: "IFlight_50A" }, meta: { signature: 0x1F06 }, }; @@ -273,30 +220,69 @@ describe('FirmwareSelector', () => { onCancel={onCancel} onLocalSubmit={onLocalSubmit} onSubmit={onSubmit} + showUnstable={false} /> ); expect(screen.getByText(/forceFlashText/i)).toBeInTheDocument(); - expect(screen.getByText(/forceFlashHint/i)).toBeInTheDocument(); expect(screen.getByText(/migrateFlashText/i)).toBeInTheDocument(); - expect(screen.getByText(/migrateFlashHint/i)).toBeInTheDocument(); expect(screen.getByText(/forceFlashText/i)).toBeInTheDocument(); - expect(screen.getByText("escButtonSelect")).toBeInTheDocument(); - expect(screen.getByText(/escButtonSelectLocally/i)).toBeInTheDocument(); - expect(screen.getByText(/buttonCancel/i)).toBeInTheDocument(); + expect(screen.getByText('escButtonSelect')).toBeInTheDocument(); + expect(screen.getByText('escButtonSelectLocally')).toBeInTheDocument(); + expect(screen.getByText('buttonCancel')).toBeInTheDocument(); - expect(screen.getByText(/selectFirmware/i)).toBeInTheDocument(); - expect(screen.getByText(/selectTarget/i)).toBeInTheDocument(); + expect(screen.getByText('selectFirmware')).toBeInTheDocument(); + expect(screen.getByText('selectTarget')).toBeInTheDocument(); - fireEvent.change(screen.getByRole(/combobox/i, { name: 'Version' }), { - target: { - value: 'https://github.com/AlkaMotors/AM32-MultiRotor-ESC-firmware/releases/download/v1.65/', - name: 'Version', - }, - }); + fireEvent.mouseDown(screen.getByRole('button', { 'name': 'selectVersion selectVersion' })); + + const element = screen.getByRole('option', { 'name': '1.78' }); + userEvent.click(element); userEvent.click(screen.getByText('escButtonSelect')); expect(onSubmit).toHaveBeenCalled(); }); + + it('should show warning when forcing target', async() => { + const configs = { + versions: {}, + escs: {}, + pwm: {}, + }; + + render( + + ); + + fireEvent.click(screen.getByRole('checkbox', { name: 'forceFlashText' })); + + expect(screen.getByText(/forceFlashHint/i)).toBeInTheDocument(); + }); + + it('should show warning when forcing migration', async() => { + const configs = { + versions: {}, + escs: {}, + pwm: {}, + }; + + render( + + ); + + fireEvent.click(screen.getByRole('checkbox', { name: 'migrateFlashText' })); + + expect(screen.getByText(/migrateFlashHint/i)).toBeInTheDocument(); + }); }); diff --git a/src/Components/FirmwareSelector/index.jsx b/src/Components/FirmwareSelector/index.jsx index 26255600c..215391e11 100644 --- a/src/Components/FirmwareSelector/index.jsx +++ b/src/Components/FirmwareSelector/index.jsx @@ -7,6 +7,15 @@ import React, { useRef, } from 'react'; +import Alert from '@mui/material/Alert'; +import Button from '@mui/material/Button'; +import Container from '@mui/material/Container'; +import Grid from '@mui/material/Grid'; +import Stack from '@mui/material/Stack'; + +import Checkbox from '../Input/Checkbox'; +import MainCard from '../MainCard'; + import { isValidLayout, getSupportedSources, @@ -17,8 +26,6 @@ import sources from '../../sources'; import LabeledSelect from '../Input/LabeledSelect'; -import './style.scss'; - const blheliModes = blheliSource.getEeprom().MODES; function FirmwareSelector({ @@ -180,13 +187,14 @@ function FirmwareSelector({ }, [onLocalSubmit, force, migrate]); const handleVersionChange = useCallback((e) => { - const selected = e.target.options.selectedIndex; - const selectedOption = e.target.options[selected]; + const selectedValue = e.target.value; + const selectedItem = options.versions.filter((item) => item.value === selectedValue); + const selectedVersion = selectedItem[0].key; setSelection({ ...selection, - url: e.target.value, - version: selectedOption && options.versions[selected - 1].key, + url: selectedValue, + version: selectedVersion, }); }, [options, selection]); @@ -222,90 +230,42 @@ function FirmwareSelector({ const disableFlashButton = !selection.url || (!selection.pwm && options.frequencies.length > 0); return ( -
    -
    -
    - -
    - -
    - -
    - -
    -

    - - { t('note') } - - - - { t('migrationNote') } - -

    -
    - - {warning && -
    - {warning} -
    } - -
    -
    -
    - {`${t('selectTarget')}${esc.displayName ? ` (${esc.displayName})` : ''}`} -
    -
    - -
    - + + + + + + + {selection.firmware && <> - + + + {/* {type === blheliTypes.SILABS || type === blheliTypes.ATMEL && @@ -318,64 +278,97 @@ function FirmwareSelector({ />} */} - + + + {options.frequencies.length > 0 && - } + + + } } - -
    - -
    - -
    - - - -
    - -
    - -
    -
    -
    -
    -
    + + + + + + + + {t('migrationNote')} + + + {warning && + + {warning} + } + + + + + + + + + + + ); } FirmwareSelector.defaultProps = { @@ -386,6 +379,7 @@ FirmwareSelector.defaultProps = { settings: {}, }, selectedMode: null, + showUnstable: false, warning: null, }; FirmwareSelector.propTypes = { @@ -404,7 +398,7 @@ FirmwareSelector.propTypes = { onLocalSubmit: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, selectedMode: PropTypes.string, - showUnstable: PropTypes.bool.isRequired, + showUnstable: PropTypes.bool, warning: PropTypes.node, }; diff --git a/src/Components/FirmwareSelector/style.scss b/src/Components/FirmwareSelector/style.scss deleted file mode 100644 index 9697b956a..000000000 --- a/src/Components/FirmwareSelector/style.scss +++ /dev/null @@ -1,62 +0,0 @@ -#content .tab #firmware-selector { - .checkbox label { - flex: 1; - } - - .note { - margin-bottom: 7px; - } -} - -#firmware-selector { - .checkbox { - float: none; - width: auto; - margin-left: 0px; - margin-bottom: 20px; - margin-top: 0; - padding-bottom: 0px; - - span:last-child { - margin-left: 5px; - } - } - - .center-wrapper { - width: 50%; - margin: auto; - - @media only screen and (max-width: 768px) { - width: 100%; - } - } - - .red { - color: red; - } - - .default-btn { - margin-bottom: 10px; - } - - .default-btn:last-child { - margin-bottom: 0px; - } - - .alert { - table { - margin-top: 5px; - margin-bottom: 5px; - font-weight: bold; - td { - padding-right: 5px; - } - } - - a { - color: white; - text-decoration: underline; - font-weight: bold; - } - } -} diff --git a/src/Components/Flash/CommonSettings/__tests__/index.test.jsx b/src/Components/Flash/CommonSettings/__tests__/index.test.jsx index 1bd623e52..397e57415 100644 --- a/src/Components/Flash/CommonSettings/__tests__/index.test.jsx +++ b/src/Components/Flash/CommonSettings/__tests__/index.test.jsx @@ -158,7 +158,7 @@ describe('CommonSettings', () => { expect(screen.queryByText(/invalid/i)).not.toBeInTheDocument(); }); - it('should allow updating settings', () => { + it('should allow updating a checkbox', () => { const availableSettings = { LAYOUT_REVISION: 203, MAIN_REVISION: 1, @@ -292,13 +292,148 @@ describe('CommonSettings', () => { userEvent.click(screen.getByRole(/checkbox/i)); expect(onSettingsUpdate).toHaveBeenCalled(); + }); + + it('should allow updating a selectbox', () => { + const availableSettings = { + LAYOUT_REVISION: 203, + MAIN_REVISION: 1, + NAME: 'FW name', + SUB_REVISION: 100, + STARTUP_BEEP: 0, + MOTOR_DIRECTION: 1, + }; - fireEvent.change(screen.getByRole(/combobox/i), { - taget: { - value: 3, - name: 'MOTOR_DIRECTION', + const esc = { + meta: { available: true }, + settings: { + MODE: 0, + STARTUP_BEEP: 0, }, - }); + make: 'make 1234', + settingsDescriptions: { + overrides: { + '0.201': [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + ], + }, + base: [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + { + name: '_PPM_MIN_THROTTLE', + type: 'number', + min: 1000, + max: 1500, + step: 4, + label: 'escPPMMinThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'STARTUP_BEEP', + type: 'bool', + label: 'escStartupBeep', + }, + { + name: 'IVALID', + type: 'IVALID', + label: 'invalid', + }, + { + name: '_PPM_CENTER_THROTTLE', + type: 'number', + min: 1000, + max: 2020, + step: 4, + label: 'escPPMCenterThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + visibleIf: (settings) => [3, 4].includes(settings.MOTOR_DIRECTION), + }, + ], + }, + individualSettings: { + MAIN_REVISION: 0, + SUB_REVISION: 201, + NAME: 'Bluejay (Beta)', + }, + }; + + const escs = []; + + for(let i = 0; i < 4; i += 1) { + const current = { ...esc }; + escs.push(current); + } + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + const button = screen.getByRole('button', { name: 'escMotorDirection hints:MOTOR_DIRECTION Normal' }); + expect(button).toBeInTheDocument(); + fireEvent.mouseDown(button); + + let option = screen.getByRole('option', { name: 'Reversed' }); + userEvent.click(option); + + expect(onSettingsUpdate).toHaveBeenCalled(); }); it('should allow updating with direct input', () => { @@ -614,13 +749,6 @@ describe('CommonSettings', () => { userEvent.click(screen.getByRole(/checkbox/i)); expect(onSettingsUpdate).toHaveBeenCalled(); - - fireEvent.change(screen.getByRole(/combobox/i), { - taget: { - value: 3, - name: 'MOTOR_DIRECTION', - }, - }); }); it('should handle setting overrides', () => { @@ -763,13 +891,6 @@ describe('CommonSettings', () => { userEvent.click(screen.getByRole(/checkbox/i)); expect(onSettingsUpdate).toHaveBeenCalled(); - - fireEvent.change(screen.getByRole(/combobox/i), { - taget: { - value: 3, - name: 'MOTOR_DIRECTION', - }, - }); }); it('should display warning when firmware is unsopported', () => { diff --git a/src/Components/Flash/CommonSettings/index.jsx b/src/Components/Flash/CommonSettings/index.jsx index 49adcf48b..b71b2f81b 100644 --- a/src/Components/Flash/CommonSettings/index.jsx +++ b/src/Components/Flash/CommonSettings/index.jsx @@ -7,6 +7,10 @@ import React, { } from 'react'; import ReactMarkdown from 'react-markdown'; +import Grid from '@mui/material/Grid'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; + import { getMasterSettings, getMaster, @@ -15,6 +19,7 @@ import { } from '../../../utils/helpers/Settings'; import Checkbox from '../../Input/Checkbox'; +import MainCard from '../../MainCard'; import Select from '../../Input/Select'; import Slider from '../../Input/Slider'; import Number from '../../Input/Number'; @@ -84,40 +89,34 @@ function CommonSettings({ let unsupportedText = ( <> -

    + {t('common:versionUnsupportedLine1', { version: version, name: availableSettings.NAME, layout: availableSettings.LAYOUT_REVISION, })} -

    + - - {t('common:versionUnsupportedLine2')} - + + + {t('common:versionUnsupportedLine2')} + + ); if (unsupported) { unsupportedText = ( -

    - { t('common:useDedicatedConfigurator', { name: availableSettings.NAME }) } -

    + + {t('common:useDedicatedConfigurator', { name: availableSettings.NAME }) } + ); } return ( -
    -
    -
    - {t('unsupportedFirmware')} -
    -
    - -
    - {unsupportedText} -
    -
    + + {unsupportedText} + ); } @@ -162,16 +161,31 @@ function CommonSettings({ switch (setting.type) { case 'bool': { return ( - + > + + + + + + ); } @@ -239,17 +253,11 @@ function CommonSettings({ }); return ( -
    -
    -
    - {t('commonParameters')} -
    -
    - -
    + + {settingElements} -
    -
    + + ); } diff --git a/src/Components/Flash/CommonSettings/style.scss b/src/Components/Flash/CommonSettings/style.scss index 775723f1d..886e6c05b 100644 --- a/src/Components/Flash/CommonSettings/style.scss +++ b/src/Components/Flash/CommonSettings/style.scss @@ -1,5 +1,9 @@ -.not-in-sync::before { - content: '*'; +.not-in-sync::after { + content: ' *'; color: red; font-weight: bold; } + +.inset > * { + margin-left: -1.25rem; +} diff --git a/src/Components/Flash/CountWarning/index.jsx b/src/Components/Flash/CountWarning/index.jsx index 109c8c484..ab5f9620a 100644 --- a/src/Components/Flash/CountWarning/index.jsx +++ b/src/Components/Flash/CountWarning/index.jsx @@ -2,45 +2,46 @@ import { useTranslation } from 'react-i18next'; import React from 'react'; import ReactMarkdown from 'react-markdown'; -import './style.scss'; +import Typography from '@mui/material/Typography'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; + +import MainCard from '../../MainCard'; function CountWarning() { const { t } = useTranslation('common'); const lines = [1, 2, 3, 4, 5].map((index) => { const line = `escMissing${index}`; return ( - - {t(line)} - + + + + {t(line)} + + + ); }); - return ( -
    -
    -
    - {t('escMissingHeader')} -
    -
    - -
    -

    - {t('escMissingText')} -

    - -
      - {lines} -
    - - + + + {t('escMissingText')} + + + + {lines} + + + + {t('escMissingHint')} -
    -
    + + ); } diff --git a/src/Components/Flash/CountWarning/style.scss b/src/Components/Flash/CountWarning/style.scss deleted file mode 100644 index bfeab1fb8..000000000 --- a/src/Components/Flash/CountWarning/style.scss +++ /dev/null @@ -1,16 +0,0 @@ -.missing-esc.gui-box { - p { - padding-bottom: 10px; - font-weight: normal; - } - - ul { - margin-left: 15px; - - li { - list-style-type: disc; - font-weight: normal; - padding-bottom: 5px; - } - } -} diff --git a/src/Components/Flash/Escs/Esc/SettingsHandler/Settings/__tests__/index.test.jsx b/src/Components/Flash/Escs/Esc/SettingsHandler/Settings/__tests__/index.test.jsx new file mode 100644 index 000000000..11a7239cb --- /dev/null +++ b/src/Components/Flash/Escs/Esc/SettingsHandler/Settings/__tests__/index.test.jsx @@ -0,0 +1,177 @@ +import React from 'react'; +import { + fireEvent, + render, + screen, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import Settings from '../'; + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key) => key, + i18n: { + exists: (name) => { + if(name === 'hints:STARTUP_BEEP') { + return false; + } + + return true; + }, + }, + }), +})); + +describe('Settings', () => { + let handleCheckboxChange; + let handleNumberChange; + let handleSelectChange; + + const descriptions = [ + { + name: 'COMMON_MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + { + name: 'COMMON_STARTUP_BEEP', + type: 'bool', + label: 'escStartupBeep', + }, + { + name: "BEEP_STRENGTH", + type: "number", + min: 1, + max: 255, + step: 1, + label: "escBeepStrength", + }, + ]; + const settings = {}; + + beforeEach(() => { + handleCheckboxChange = jest.fn(); + handleNumberChange = jest.fn(); + handleSelectChange = jest.fn(); + }); + + it('should render', () => { + render( + + ); + + expect(screen.getByText(/escMotorDirection/i)).toBeInTheDocument(); + expect(screen.getByText(/escStartupBeep/i)).toBeInTheDocument(); + expect(screen.getByText(/escBeepStrength/i)).toBeInTheDocument(); + }); + + it('should handle Select', () => { + render( + + ); + + const button = screen.getByRole('button', {}); + userEvent.click(button); + + const option = screen.getByRole('option', { name: 'Bidirectional' }); + userEvent.click(option); + + expect(handleSelectChange).toHaveBeenCalled(); + }); + + it('should handle Checkbox', () => { + render( + + ); + + const checkbox = screen.getByRole('checkbox', {}); + userEvent.click(checkbox); + + expect(handleCheckboxChange).toHaveBeenCalled(); + }); + + it('should handle Slider', () => { + render( + + ); + + expect(screen.getByRole('slider', {})).toBeInTheDocument(); + }); + + it('should handle direct input', () => { + render( + + ); + + const spinbutton = screen.getByRole('spinbutton', {}); + fireEvent.change(screen.getByRole(/spinbutton/i), { + target: { + value: 100, + name: 'test', + }, + }); + fireEvent.blur(screen.getByRole(/spinbutton/i)); + expect(screen.getByRole(/spinbutton/i).value).toEqual("100"); + + expect(handleNumberChange).toHaveBeenCalled(); + }); +}); diff --git a/src/Components/Flash/Escs/Esc/SettingsHandler/__tests__/index.test.jsx b/src/Components/Flash/Escs/Esc/SettingsHandler/__tests__/index.test.jsx new file mode 100644 index 000000000..51061272b --- /dev/null +++ b/src/Components/Flash/Escs/Esc/SettingsHandler/__tests__/index.test.jsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { + render, + screen, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import SettingsHandler from '../'; + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key) => key, + i18n: { + exists: (name) => { + if(name === 'hints:STARTUP_BEEP') { + return false; + } + + return true; + }, + }, + }), +})); + +let onUpdate; + +describe('SettingsHandler', () => { + beforeEach(() => { + onUpdate = jest.fn(); + }); + + it('should update settings', () => { + const descriptions = [ + { + name: 'COMMON_MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + { + name: 'COMMON_STARTUP_BEEP', + type: 'bool', + label: 'escStartupBeep', + }, + ]; + const settings = {}; + + render( + + ); + + expect(screen.getByText(/escMotorDirection/i)).toBeInTheDocument(); + expect(screen.getByText(/escStartupBeep/i)).toBeInTheDocument(); + + const button = screen.getByRole('button', {}); + userEvent.click(button); + + const option = screen.getByRole('option', { name: 'Bidirectional' }); + userEvent.click(option); + + expect(onUpdate).toHaveBeenCalled(); + }); +}); diff --git a/src/Components/Flash/Escs/Esc/__tests__/index.test.jsx b/src/Components/Flash/Escs/Esc/__tests__/index.test.jsx index eeffb7649..f4b392821 100644 --- a/src/Components/Flash/Escs/Esc/__tests__/index.test.jsx +++ b/src/Components/Flash/Escs/Esc/__tests__/index.test.jsx @@ -133,7 +133,7 @@ describe('Esc', () => { /> ); - userEvent.click(screen.getByText(/escButtonFlash/i)); + expect(screen.getByText(/escButtonFlash/i)).toHaveAttribute('disabled'); expect(onFlash).not.toHaveBeenCalled(); }); @@ -256,14 +256,6 @@ describe('Esc', () => { expect(screen.queryByText(/invalid/i)).not.toBeInTheDocument(); userEvent.click(screen.getByRole(/checkbox/i)); - - // Change select - fireEvent.change(screen.getByRole(/combobox/i), { - taget: { - value: 3, - name: 'MOTOR_DIRECTION', - }, - }); }); it('should show custom settings and handle direct input', () => { @@ -465,7 +457,6 @@ describe('Esc', () => { const progressbar = screen.getByRole(/progressbar/i); expect(progressbar).toBeInTheDocument(); - expect(progressbar.value).toEqual(50); }); it('should show common settings and handle change', () => { @@ -524,18 +515,9 @@ describe('Esc', () => { /> ); - expect(screen.getByText(/hints:COMMON_MOTOR_DIRECTION/i)).toBeInTheDocument(); expect(screen.getByText(/escMotorDirection/i)).toBeInTheDocument(); userEvent.click(screen.getByRole(/checkbox/i)); - - // Change select - fireEvent.change(screen.getByRole(/combobox/i), { - taget: { - value: 3, - name: 'COMMON_MOTOR_DIRECTION', - }, - }); }); it('should trigger firmware dump', () => { diff --git a/src/Components/Flash/Escs/Esc/index.jsx b/src/Components/Flash/Escs/Esc/index.jsx index aa14068d0..6e9654be9 100644 --- a/src/Components/Flash/Escs/Esc/index.jsx +++ b/src/Components/Flash/Escs/Esc/index.jsx @@ -7,10 +7,16 @@ import React, { useState, } from 'react'; +import Button from '@mui/material/Button'; +import ButtonGroup from '@mui/material/ButtonGroup'; +import Divider from '@mui/material/Divider'; +import LinearProgress from '@mui/material/LinearProgress'; +import Grid from '@mui/material/Grid'; +import Stack from '@mui/material/Stack'; + +import MainCard from '../../../MainCard'; import SettingsHandler from './SettingsHandler'; -import './style.scss'; - const Esc = forwardRef(({ canFlash, directInput, @@ -58,14 +64,11 @@ const Esc = forwardRef(({ }, [onFirmwareDump, index]); return ( -
    -
    -
    - {title} -
    -
    - -
    + + } + spacing={1} + > {disableCommon && commonSettingsDescriptions && } -
    -
    - 0 ? 'progress' : 'hidden'} - max="100" - min="0" - value={progress} - /> - - - - {enableAdvanced && - } -
    -
    -
    -
    + {progress > 0 && + } + + {progress === 0 && + + + + {enableAdvanced && + } + } + + + + + ); }); Esc.displayName = 'Esc'; -Esc.defaultProps = { canFlash: true }; +Esc.defaultProps = { + canFlash: true, + disableCommon: false, + enableAdvanced: false, +}; Esc.propTypes = { canFlash: PropTypes.bool, directInput: PropTypes.bool.isRequired, - disableCommon: PropTypes.bool.isRequired, - enableAdvanced: PropTypes.bool.isRequired, + disableCommon: PropTypes.bool, + enableAdvanced: PropTypes.bool, esc: PropTypes.shape().isRequired, index: PropTypes.number.isRequired, onCommonSettingsUpdate: PropTypes.func.isRequired, diff --git a/src/Components/Flash/Escs/Esc/style.scss b/src/Components/Flash/Escs/Esc/style.scss deleted file mode 100644 index 998baa673..000000000 --- a/src/Components/Flash/Escs/Esc/style.scss +++ /dev/null @@ -1,22 +0,0 @@ -.esc { - progress { - position: absolute; - width: 100%; - height: 100%; - border: 1px solid var(--color-tertiary); - border-radius: 5px; - } - - progress::-webkit-progress-bar { - background: #fff; - border-radius: 5px; - } - - progress::-webkit-progress-value { - background: var(--color-tertiary); - } - - .firmware-dump { - margin-top: 7px; - } -} diff --git a/src/Components/Flash/Escs/index.jsx b/src/Components/Flash/Escs/index.jsx index 1f821aa8a..aee9c27aa 100644 --- a/src/Components/Flash/Escs/index.jsx +++ b/src/Components/Flash/Escs/index.jsx @@ -1,6 +1,8 @@ import PropTypes from 'prop-types'; import React from 'react'; +import Grid from '@mui/material/Grid'; + import Esc from './Esc'; function Escs({ @@ -16,25 +18,37 @@ function Escs({ }) { function EscElements() { return escs.map((esc) => ( - + lg={6} + xs={12} + > + + )); } return ( - + + + ); } diff --git a/src/Components/Flash/__tests__/index.test.jsx b/src/Components/Flash/__tests__/index.test.jsx index 3fa3e60e2..20dcd3332 100644 --- a/src/Components/Flash/__tests__/index.test.jsx +++ b/src/Components/Flash/__tests__/index.test.jsx @@ -22,6 +22,20 @@ jest.mock('react-i18next', () => ({ })); describe('Flash', () => { + let onFirmwareDump; + let onFlash; + let onIndividualSettingsUpdate; + let onCommonSettingsUpdate; + let onSettingsUpdate; + + beforeEach(() => { + onFirmwareDump = jest.fn(); + onFlash = jest.fn(); + onIndividualSettingsUpdate = jest.fn(); + onCommonSettingsUpdate = jest.fn(); + onSettingsUpdate = jest.fn(); + }); + it('should load and display unsupported custom settings', () => { const availableSettings = { LAYOUT_REVISION: 203, @@ -104,16 +118,16 @@ describe('Flash', () => { }, ]; - const onFlash = jest.fn(); - const onSettingsUpdate = jest.fn(); - render( ); @@ -203,9 +217,6 @@ describe('Flash', () => { }, ]; - const onFlash = jest.fn(); - const onSettingsUpdate = jest.fn(); - render( { disableCommon escCount={1} escs={escs} + onCommonSettingsUpdate={onCommonSettingsUpdate} + onFirmwareDump={onFirmwareDump} onFlash={onFlash} + onIndividualSettingsUpdate={onIndividualSettingsUpdate} onSettingsUpdate={onSettingsUpdate} /> ); @@ -304,16 +318,16 @@ describe('Flash', () => { }, ]; - const onFlash = jest.fn(); - const onSettingsUpdate = jest.fn(); - render( ); diff --git a/src/Components/Flash/index.jsx b/src/Components/Flash/index.jsx index d8b8345e3..d18e41f52 100644 --- a/src/Components/Flash/index.jsx +++ b/src/Components/Flash/index.jsx @@ -2,11 +2,13 @@ import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import React from 'react'; +import Grid from '@mui/material/Grid'; +import Stack from '@mui/material/Stack'; + import CommonSettings from './CommonSettings'; import CountWarning from './CountWarning'; import Escs from './Escs'; - -import './style.scss'; +import Warning from '../Warning'; function Flash({ availableSettings, @@ -27,8 +29,22 @@ function Flash({ const { t } = useTranslation('common'); return ( -
    -
    + + + + + +
    {escs.length > 0 && !disableCommon &&
    }
    + -
    + + } -
    -
    - + + + ); } @@ -81,6 +103,7 @@ Flash.defaultProps = { directInput: false, disableCommon: false, enableAdvanced: false, + flashProgress: [], }; Flash.propTypes = { @@ -91,7 +114,7 @@ Flash.propTypes = { enableAdvanced: PropTypes.bool, escCount: PropTypes.number.isRequired, escs: PropTypes.arrayOf(PropTypes.shape()).isRequired, - flashProgress: PropTypes.arrayOf(PropTypes.number).isRequired, + flashProgress: PropTypes.arrayOf(PropTypes.number), onCommonSettingsUpdate: PropTypes.func.isRequired, onFirmwareDump: PropTypes.func.isRequired, onFlash: PropTypes.func.isRequired, diff --git a/src/Components/Flash/style.scss b/src/Components/Flash/style.scss deleted file mode 100644 index 28a9ef21c..000000000 --- a/src/Components/Flash/style.scss +++ /dev/null @@ -1,28 +0,0 @@ -.flash { - .flash__wrapper { - display: flex; - } - - .flash__common { - margin-right: 10px; - flex-basis: 0; - flex-grow: 1; - } - - .flash__individual { - margin-left: 10px; - flex-basis: 0; - flex-grow: 1; - } - - @media screen and (max-width: 768px) { - .flash__wrapper { - flex-direction: column; - } - - .flash__common, - .flash__individual { - margin: 0px; - } - } -} diff --git a/src/Components/Home/__tests__/index.test.jsx b/src/Components/Home/__tests__/index.test.jsx index 591699575..16503c143 100644 --- a/src/Components/Home/__tests__/index.test.jsx +++ b/src/Components/Home/__tests__/index.test.jsx @@ -13,6 +13,7 @@ describe('Home', () => { const onChangePort = jest.fn(); const onConnect = jest.fn(); const onDisconnect = jest.fn(); + const onOpenMelodyEditor = jest.fn(); const onSetBaudRate = jest.fn(); const onSetPort = jest.fn(); const prompt = jest.fn(); @@ -22,6 +23,7 @@ describe('Home', () => { onChangePort={onChangePort} onConnect={onConnect} onDisconnect={onDisconnect} + onOpenMelodyEditor={onOpenMelodyEditor} onSetBaudRate={onSetBaudRate} onSetPort={onSetPort} /> diff --git a/src/Components/Home/index.jsx b/src/Components/Home/index.jsx index 4d3eb54e3..e13d267a4 100644 --- a/src/Components/Home/index.jsx +++ b/src/Components/Home/index.jsx @@ -7,6 +7,21 @@ import React, { } from 'react'; import ReactMarkdown from 'react-markdown'; +import { useTheme } from '@mui/material/styles'; + +import Alert from '@mui/material/Alert'; +import AlertTitle from '@mui/material/AlertTitle'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Fade from '@mui/material/Fade'; +import Grid from '@mui/material/Grid'; +import Link from '@mui/material/Link'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; + import bluejay from './images/bluejay_logo.png'; import './style.scss'; @@ -28,28 +43,39 @@ function Install() { }, [deferredPrompt]); return( -
    -
    -
    - + + + + {t('homeInstallLine1')} + - + + {t('homeInstallLine2')} -
    - -
    - -
    -
    -
    + + + + + ); } @@ -60,38 +86,63 @@ function HomeColumnLeft() { const line = `homeDisclaimerTextLine${index}`; return ( - - {t(line)} - + + + {t(line)} + + ); }); return( -
    -
    -
    -

    + + + + {t('homeDisclaimerHeader')} -

    + + -
    - {disclaimerLines} -
    -
    + + + {t('betaWarningLine1')} + + -
    -

    - {t('homeAttributionHeader')} -

    + + + {t('betaWarningLine2')} + + -
    - - {t('homeAttributionText')} - -
    -
    -
    -
    + + + {t('findHelp')} + + + + + {disclaimerLines} + + + {t('homeAttributionHeader')} + + + + + {t('homeAttributionText')} + + + ); } @@ -99,130 +150,114 @@ function HomeColumnCenter({ onOpenMelodyEditor }) { const { t } = useTranslation('common'); return( -
    -
    - -

    - {t('homeExperimental')} -

    - -
    - {t('homeVersionInfo')} - - -
    - -
    -

    - BLHeli_S -

    - -
    -
    - - {t('blhelisTextLine1')} - - - - {t('blhelisTextLine2')} - -
    -
    -
    - -
    -

    - Bluejay -

    - -
    - Bluejay - -
    -
    - - {t('bluejayTextLine1')} - - - - {t('bluejayTextLine2')} - -
    + + + {t('homeExperimental')} + -
    - -
    + + {t('homeVersionInfo')} + -
    - - {t('bluejaySupportedHardwareLine1')} - + + BLHeli_S + + + + + {t('blhelisTextLine1')} + + + + + + {t('blhelisTextLine2')} + + + + + Bluejay + + + + + + + {t('bluejayTextLine1')} + + - - {t('bluejaySupportedHardwareLine2')} - -
    -
    -
    -
    - -
    -

    - AM32 -

    - -
    -
    - - {t('blheli32ToAM32Line1')} - - - - {t('blheli32ToAM32Line2')} - -
    -
    -
    -
    -
    + + + {t('bluejayTextLine2')} + + + + + + Bluejay + + + + + + + + + + {t('bluejaySupportedHardwareLine1')} + + + + + + {t('bluejaySupportedHardwareLine2')} + + + + + AM32 + + + + + {t('blheli32ToAM32Line1')} + + + + + + {t('blheli32ToAM32Line2')} + + + ); } HomeColumnCenter.propTypes = { onOpenMelodyEditor: PropTypes.func.isRequired }; @@ -233,139 +268,184 @@ function HomeColumnRight() { const line = `homeContributionItem${index}`; return( - - {t(line)} - + + + {t(line)} + + )} + /> + ); }); return( -
    -
    -
    -

    - {t('homeDiscordHeader')} -

    - -
    - {t('homeDiscordText')} -
    - - - Discord - -
    - -
    -

    - {t('homeChinaHeader')} -

    - - - {t('homeChinaText')} - -
    - -
    -

    - {t('homeContributionHeader')} -

    - -
    - - {t('homeContributionText')} - - -
      - {contributionItems} -
    -
    -
    + + + {t('homeDiscordHeader')} + + + + + {t('homeDiscordText')} + + + + + + Discord + + + + + {t('homeChinaHeader')} + + + + + {t('homeChinaText')} + + + + + {t('homeContributionHeader')} + + + + + {t('homeContributionText')} + + + + + {contributionItems} + + + + {t('whatsNextHeader')} + -
    -

    - {t('whatsNextHeader')} -

    + + + {t('whatsNextText')} + + -
    - - {t('whatsNextText')} - -
    -
    -
    -
    + ); } function Home({ onOpenMelodyEditor }) { const { t } = useTranslation('common'); + const theme = useTheme(); return (
    -
    -
    -
    -
    - - {t('homeWelcome')} - -
    - - - -
    -
    - - {t('betaWarningLine1')} - + + + -
    + +
    +
    + + + {t('homeWelcome')} + + - - {t('betaWarningLine2')} - -
    -
    - - {t('findHelp')} -
    -
    -
    + -
    - + + + - + + + + + + - -
    -
    + + + + + + + + +
    ); diff --git a/src/Components/Home/style.scss b/src/Components/Home/style.scss index a682b5d46..ac6e4afd0 100644 --- a/src/Components/Home/style.scss +++ b/src/Components/Home/style.scss @@ -1,221 +1,12 @@ #tab-landing { - min-height: 100%; - overflow: hidden; - - p { - padding-bottom: 10px; - } - - .line-1 { - margin-bottom: 20px; - } - - .install-wrapper { - position: absolute; - top: 17px; - right: 60px; - height: 0; - overflow: hidden; - - @media only screen and (min-width: 1660px) { - &.active { - animation: slide 1s ease 0.5s forwards; - } - } - } - - @keyframes slide { - from {height: 0;} - to {height: 150px;} - } - - .install { - max-width: 400px; - padding: 10px; - background: rgba(211, 211, 211, 0.25); - - .description { - text-align: center; - } - - .default-btn { - margin: auto; - button { - max-width: 200px; - } - } - } - - .melody-editor-button { - display: flex; - justify-content: center; - margin-top: 15px; - margin-bottom: 15px; - - button { - max-width: 200px; - } - } - - .firmware-logo-bar { - display: flex; - align-items: center; - justify-content: center; - - & > * { - width: 50%; - } - } - - a.discord-link { - width: 100%; - display: block; - text-align: center; - margin-top: 20px; - - img.discord { - height: 50px; - } - } - - .content_wrapper { - padding: 0; - overflow-y: auto; - } - - .content_top { - min-height: 140px; - background: var(--color-primary); - padding: 20px; - display: flex; - - @media only screen and (max-width: 600px) { - padding-left: 30px; - padding-right: 30px; - } - } - - .content_mid { - background-color: #eaeaea; - overflow: hidden; - display: flex; - padding-bottom: 45px; - padding-top: 20px; - - @media only screen and (max-width: 991px) { - & { - flex-direction: column; - } - } - - .summary-section { - margin-top: 20px; - - &:first-child { - margin-top: 0px; - } - - h3 { - padding-bottom: 5px; - margin-bottom: 10px; - border-bottom: 1px solid silver; - } - - section { - display: flex; - - p { - padding-bottom: 10px; - } - - p:last-of-type { - padding-bottom: 0px; - } - - img { - width: 128px; - height: 128px; - margin-right: 15px; - } - } - } - - a, - a:hover { - text-decoration: underline; - } - - & > div { - flex: 1 - } - - .column { - .wrap { - padding: 15px; - padding-bottom: 0px; - padding-top: 0px; - } - } - - h2 { - margin-bottom: 5px; - font-size: 18px; - } - - h3 { - font-size: 16px; - margin-bottom: 2px; - } - } - - .logowrapper { - display: flex; - flex-direction: column; - justify-content: space-around; - margin-left: auto; - margin-right: auto; - width: 800px; - color: white; - font-size: 14px; - font-family: 'open_sanslight', Arial; - + .MuiAlert-message { a { - color: white; + color: rgb(102, 60, 0); text-decoration: underline; } - - img { - width: 420px; - margin: 5px; - } - - span { - font-size: 22px; - font-family: 'open_sanslight', Arial; - } - } - - .text1, .text2, .text3 { - margin-top: 15px; - margin-bottom: 15px; - font-weight: normal; - font-family: 'open_sansregular', Arial; - font-size: 14px; - line-height: 20px; } - .text1 .wrap { - margin-left: 0px; - } - - .text3 ul, - .text2 ul { - margin-top: 10px; - - li { - line-height: 24px; - margin-left: 20px; - list-style: disc; - } + a { + text-decoration: underline; } } diff --git a/src/Components/Input/Checkbox/index.jsx b/src/Components/Input/Checkbox/index.jsx index de409459a..bb6e6394c 100644 --- a/src/Components/Input/Checkbox/index.jsx +++ b/src/Components/Input/Checkbox/index.jsx @@ -1,9 +1,11 @@ import PropTypes from 'prop-types'; import React from 'react'; -import Info from '../Info'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import FormHelperText from '@mui/material/FormHelperText'; +import MuiCheckbox from '@mui/material/Checkbox'; -import './style.scss'; +import Info from '../Info'; function Checkbox({ name, @@ -13,34 +15,47 @@ function Checkbox({ onChange, inSync, hint, + help, }) { + const formattedLabel = ( + + ); + return (
    - - - - + + { help && + + {help} + }
    ); } Checkbox.defaultProps = { disabled: false, + help: null, hint: null, inSync: true, value: 0, @@ -48,6 +63,7 @@ Checkbox.defaultProps = { Checkbox.propTypes = { disabled: PropTypes.bool, + help: PropTypes.string, hint: PropTypes.string, inSync: PropTypes.bool, label: PropTypes.string.isRequired, diff --git a/src/Components/Input/Checkbox/style.scss b/src/Components/Input/Checkbox/style.scss deleted file mode 100644 index 4dcf1a17d..000000000 --- a/src/Components/Input/Checkbox/style.scss +++ /dev/null @@ -1,10 +0,0 @@ -.checkbox { - width: 100%; - margin-bottom: 6px; - padding-bottom: 5px; - margin-top: 2px; - - &:last-child { - border-bottom: none; - } -} diff --git a/src/Components/Input/Info/index.jsx b/src/Components/Input/Info/index.jsx index a1bb4574f..5b3330ba9 100644 --- a/src/Components/Input/Info/index.jsx +++ b/src/Components/Input/Info/index.jsx @@ -1,9 +1,27 @@ import PropTypes from 'prop-types'; import React from 'react'; -import ReactTooltip from 'react-tooltip'; + +import { styled } from '@mui/material/styles'; +import Tooltip, { tooltipClasses } from '@mui/material/Tooltip'; import './style.scss'; +const BootstrapTooltip = styled(({ + className, + ...props +}) => ( + + {props.children} + +))(({ theme }) => ({ + [`& .${tooltipClasses.arrow}`]: { color: theme.palette.common.black }, + [`& .${tooltipClasses.tooltip}`]: { backgroundColor: theme.palette.common.black }, +})); + function Info({ hint, inSync, @@ -15,33 +33,32 @@ function Info({ {label} {hint && -
    - + - ? - - - - {hint} - -
    } + + ? + + + } ); } -Info.defaultProps = { hint: null }; +Info.defaultProps = { + hint: null, + label: null, +}; Info.propTypes = { hint: PropTypes.string, inSync: PropTypes.bool.isRequired, - label: PropTypes.string.isRequired, + label: PropTypes.string, name: PropTypes.string.isRequired, }; diff --git a/src/Components/Input/Info/style.scss b/src/Components/Input/Info/style.scss index 02397da9c..9ea606f6a 100644 --- a/src/Components/Input/Info/style.scss +++ b/src/Components/Input/Info/style.scss @@ -1,5 +1,8 @@ -.info-wrapper-wrapper { - margin-left: 15px; +.select, +.number { + .info-wrapper-wrapper { + margin-left: 15px; + } } .info-wrapper, @@ -14,22 +17,18 @@ } span.info-icon { - width: 15px; - height: 15px; + width: 1.2rem; + height: 1.2rem; border-radius: 50%; background: lightgray; display: inline-block; color: black; text-align: center; - line-height: 15px; - font-size: 12px; + font-size: 0.8rem; + line-height: 1.2rem; font-weight: bold; margin-right: 5px; cursor: pointer; z-index: -1; } - - .tooltip { - max-width: 200px; - } } diff --git a/src/Components/Input/LabeledSelect/index.jsx b/src/Components/Input/LabeledSelect/index.jsx index ed23bcc70..f6c79aee4 100644 --- a/src/Components/Input/LabeledSelect/index.jsx +++ b/src/Components/Input/LabeledSelect/index.jsx @@ -1,6 +1,11 @@ import PropTypes from 'prop-types'; import React, { useCallback } from 'react'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import MuiSelect from '@mui/material/Select'; + function LabeledSelect({ label, firstLabel, @@ -10,45 +15,52 @@ function LabeledSelect({ }) { const Select = useCallback(() => { const optionElements = options.map((item) => ( - + )); return ( - + ); }, [options, label, firstLabel, onChange, selected]); return (
    -