From a4a3dbb95e7b395c9d6aae3d0da2b99f475636f3 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi <90936742+Reckless-Satoshi@users.noreply.github.com> Date: Sat, 22 Oct 2022 14:32:33 +0000 Subject: [PATCH] Add RoboSats PRO frame as react layout grid playground (#299) * Add react layout grid playground * Add BookWidget and style * Rename basic.js back to main.js --- frontend/package-lock.json | 82 ++++++++ frontend/package.json | 1 + frontend/src/App.tsx | 84 +++----- frontend/src/basic/BookPage/index.tsx | 34 --- frontend/src/basic/Main.tsx | 38 +++- frontend/src/basic/MakerPage/index.tsx | 1 - frontend/src/components/App.tsx | 102 --------- frontend/src/components/BookTable/index.tsx | 54 ++++- .../src/components/Charts/NivoScheme/index.ts | 4 +- frontend/src/components/Main.tsx | 198 ------------------ .../MakerForm/AutocompletePayments.js | 24 ++- .../src/components/MakerForm/MakerForm.tsx | 4 +- frontend/src/models/Settings.default.basic.ts | 7 + frontend/src/models/Settings.default.pro.ts | 8 + frontend/src/models/Settings.model.ts | 33 ++- frontend/src/models/index.ts | 2 +- frontend/src/pro/LandingDialog/index.ts | 0 frontend/src/pro/LandingDialog/index.tsx | 53 +++++ frontend/src/pro/Main.tsx | 146 ++++++++++++- frontend/src/pro/ToolBar/index.ts | 0 frontend/src/pro/ToolBar/index.tsx | 51 +++++ frontend/src/pro/ViewPorts/index.ts | 0 frontend/src/pro/Widgets/Book.tsx | 64 ++++++ frontend/src/pro/Widgets/Maker.tsx | 58 +++++ frontend/src/pro/Widgets/Placeholder.tsx | 17 ++ frontend/src/pro/Widgets/index.ts | 3 + frontend/static/css_pro/react-grid-layout.css | 138 ++++++++++++ frontend/static/css_pro/react-resizable.css | 65 ++++++ frontend/templates/frontend/basic.html | 2 +- frontend/templates/frontend/pro.html | 2 + frontend/webpack.config.ts | 11 +- 31 files changed, 854 insertions(+), 432 deletions(-) delete mode 100644 frontend/src/components/App.tsx delete mode 100644 frontend/src/components/Main.tsx create mode 100644 frontend/src/models/Settings.default.basic.ts create mode 100644 frontend/src/models/Settings.default.pro.ts delete mode 100644 frontend/src/pro/LandingDialog/index.ts create mode 100644 frontend/src/pro/LandingDialog/index.tsx delete mode 100644 frontend/src/pro/ToolBar/index.ts create mode 100644 frontend/src/pro/ToolBar/index.tsx delete mode 100644 frontend/src/pro/ViewPorts/index.ts create mode 100644 frontend/src/pro/Widgets/Placeholder.tsx create mode 100644 frontend/static/css_pro/react-grid-layout.css create mode 100644 frontend/static/css_pro/react-resizable.css diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8a4bce7d..4c5ddc67 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -35,6 +35,7 @@ "react": "^18.2.0", "react-countdown": "^2.3.2", "react-dom": "^18.1.0", + "react-grid-layout": "^1.3.4", "react-i18next": "^11.16.2", "react-image": "^4.0.3", "react-qr-code": "^2.0.3", @@ -9863,6 +9864,11 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -13073,6 +13079,35 @@ "react": "^18.2.0" } }, + "node_modules/react-draggable": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz", + "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==", + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, + "node_modules/react-grid-layout": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.3.4.tgz", + "integrity": "sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==", + "dependencies": { + "clsx": "^1.1.1", + "lodash.isequal": "^4.0.0", + "prop-types": "^15.8.1", + "react-draggable": "^4.0.0", + "react-resizable": "^3.0.4" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, "node_modules/react-i18next": { "version": "11.18.5", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.5.tgz", @@ -13132,6 +13167,18 @@ } } }, + "node_modules/react-resizable": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.4.tgz", + "integrity": "sha512-StnwmiESiamNzdRHbSSvA65b0ZQJ7eVQpPusrSmcpyGKzC0gojhtO62xxH6YOBmepk9dQTBi9yxidL3W4s3EBA==", + "dependencies": { + "prop-types": "15.x", + "react-draggable": "^4.0.3" + }, + "peerDependencies": { + "react": ">= 16.3" + } + }, "node_modules/react-responsive": { "version": "9.0.0-beta.10", "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-9.0.0-beta.10.tgz", @@ -22098,6 +22145,11 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -24312,6 +24364,27 @@ "scheduler": "^0.23.0" } }, + "react-draggable": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz", + "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==", + "requires": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + } + }, + "react-grid-layout": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.3.4.tgz", + "integrity": "sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==", + "requires": { + "clsx": "^1.1.1", + "lodash.isequal": "^4.0.0", + "prop-types": "^15.8.1", + "react-draggable": "^4.0.0", + "react-resizable": "^3.0.4" + } + }, "react-i18next": { "version": "11.18.5", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.5.tgz", @@ -24346,6 +24419,15 @@ "qr.js": "0.0.0" } }, + "react-resizable": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.4.tgz", + "integrity": "sha512-StnwmiESiamNzdRHbSSvA65b0ZQJ7eVQpPusrSmcpyGKzC0gojhtO62xxH6YOBmepk9dQTBi9yxidL3W4s3EBA==", + "requires": { + "prop-types": "15.x", + "react-draggable": "^4.0.3" + } + }, "react-responsive": { "version": "9.0.0-beta.10", "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-9.0.0-beta.10.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6b196ffd..f7426a92 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -73,6 +73,7 @@ "react": "^18.2.0", "react-countdown": "^2.3.2", "react-dom": "^18.1.0", + "react-grid-layout": "^1.3.4", "react-i18next": "^11.16.2", "react-image": "^4.0.3", "react-qr-code": "^2.0.3", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a156d65a..a6002e41 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,89 +1,57 @@ -import React, { Suspense, useState } from 'react'; +import React, { Suspense, useState, useEffect } from 'react'; import ReactDOM from 'react-dom/client'; import Main from './basic/Main'; -import { CssBaseline, IconButton } from '@mui/material'; -import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { CssBaseline } from '@mui/material'; +import { ThemeProvider, createTheme, Theme } from '@mui/material/styles'; import UnsafeAlert from './components/UnsafeAlert'; -import { LearnDialog } from './components/Dialogs'; import TorConnection from './components/TorConnection'; import { I18nextProvider } from 'react-i18next'; import i18n from './i18n/Web'; -// Icons -import DarkModeIcon from '@mui/icons-material/DarkMode'; -import LightModeIcon from '@mui/icons-material/LightMode'; -import SchoolIcon from '@mui/icons-material/School'; import { systemClient } from './services/System'; +import { Settings, defaultSettings } from './models'; -const defaultTheme = createTheme({ +const defaultTheme: Theme = createTheme({ palette: { - mode: - window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches - ? 'dark' - : 'light', + mode: defaultSettings.mode, background: { - default: - window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches - ? '#070707' - : '#fff', + default: defaultSettings.mode === 'dark' ? '#070707' : '#fff', }, }, + typography: { fontSize: defaultSettings.fontSize }, }); const App = (): JSX.Element => { - const [openLearn, setOpenLearn] = useState(false); - const [theme, setTheme] = useState(defaultTheme); + const [theme, setTheme] = useState(defaultTheme); + const [settings, setSettings] = useState(defaultSettings); - const handleModeChange = function () { - if (theme.palette.mode === 'light') { - setTheme( - createTheme({ - palette: { - mode: 'dark', - background: { - default: '#070707', - }, + const updateTheme = function () { + setTheme( + createTheme({ + palette: { + mode: settings.mode, + background: { + default: settings.mode === 'dark' ? '#070707' : '#fff', }, - }), - ); - } else if (theme.palette.mode === 'dark') { - setTheme( - createTheme({ - palette: { - mode: 'light', - background: { - default: '#fff', - }, - }, - }), - ); - } + }, + typography: { fontSize: settings.fontSize }, + }), + ); }; + useEffect(() => { + updateTheme(); + }, [settings]); + return ( - setOpenLearn(false)} /> - setOpenLearn(true)} - > - - - handleModeChange()} - > - {theme.palette.mode === 'dark' ? : } - -
+
diff --git a/frontend/src/basic/BookPage/index.tsx b/frontend/src/basic/BookPage/index.tsx index e1c85ffa..3e74fb41 100644 --- a/frontend/src/basic/BookPage/index.tsx +++ b/frontend/src/basic/BookPage/index.tsx @@ -83,37 +83,6 @@ const BookPage = ({ setFav({ ...fav, type: val }); }; - const NoOrdersFound = function () { - return ( - - - - {fav.type == 0 - ? t('No orders found to sell BTC for {{currencyCode}}', { - currencyCode: - fav.currency == 0 ? t('ANY') : currencyDict[fav.currency.toString()], - }) - : t('No orders found to buy BTC for {{currencyCode}}', { - currencyCode: - fav.currency == 0 ? t('ANY') : currencyDict[fav.currency.toString()], - })} - - - - - {t('Be the first one to create an order')} - - - - ); - }; - const NavButtons = function () { return ( @@ -153,7 +122,6 @@ const BookPage = ({ @@ -220,7 +187,6 @@ const BookPage = ({ defaultFullscreen={false} onCurrencyChange={handleCurrencyChange} onTypeChange={handleTypeChange} - noResultsOverlay={NoOrdersFound} /> )} diff --git a/frontend/src/basic/Main.tsx b/frontend/src/basic/Main.tsx index c2535696..f9c4eeeb 100644 --- a/frontend/src/basic/Main.tsx +++ b/frontend/src/basic/Main.tsx @@ -1,12 +1,13 @@ import React, { useEffect, useState } from 'react'; import { HashRouter, BrowserRouter, Switch, Route, useHistory } from 'react-router-dom'; -import { useTheme } from '@mui/material'; +import { useTheme, IconButton } from '@mui/material'; import UserGenPage from './UserGenPage'; import MakerPage from './MakerPage'; import BookPage from './BookPage'; import OrderPage from './OrderPage'; import BottomBar from './BottomBar'; +import { LearnDialog } from '../components/Dialogs'; import { apiClient } from '../services/api'; import checkVer from '../utils/checkVer'; @@ -22,9 +23,13 @@ import { defaultMaker, defaultRobot, defaultInfo, - defaultSettings, } from '../models'; +// Icons +import DarkModeIcon from '@mui/icons-material/DarkMode'; +import LightModeIcon from '@mui/icons-material/LightMode'; +import SchoolIcon from '@mui/icons-material/School'; + const getWindowSize = function (fontSize: number) { // returns window size in EM units return { @@ -33,11 +38,18 @@ const getWindowSize = function (fontSize: number) { }; }; -const Main = (): JSX.Element => { +interface MainProps { + updateTheme: () => void; + settings: Settings; + setSettings: (state: Settings) => void; +} + +const Main = ({ settings, setSettings }: MainProps): JSX.Element => { const theme = useTheme(); const history = useHistory(); const Router = window.NativeRobosats != null ? HashRouter : BrowserRouter; const basename = window.NativeRobosats != null ? window.location.pathname : ''; + const [openLearn, setOpenLearn] = useState(false); // All app data structured const [book, setBook] = useState({ orders: [], loading: true }); @@ -49,7 +61,6 @@ const Main = (): JSX.Element => { const [maker, setMaker] = useState(defaultMaker); const [info, setInfo] = useState(defaultInfo); const [fav, setFav] = useState({ type: null, currency: 0 }); - const [settings, setSettings] = useState(defaultSettings); const [windowSize, setWindowSize] = useState<{ width: number; height: number }>( getWindowSize(theme.typography.fontSize), @@ -119,6 +130,25 @@ const Main = (): JSX.Element => { return ( +
+ setOpenLearn(false)} /> + setOpenLearn(true)} + > + + + + setSettings({ ...settings, mode: settings.mode === 'dark' ? 'light' : 'dark' }) + } + > + {theme.palette.mode === 'dark' ? : } + +
{ - const [openLearn, setOpenLearn] = useState(false); - const [theme, setTheme] = useState(defaultTheme); - - const handleModeChange = function () { - if (theme.palette.mode === 'light') { - setTheme( - createTheme({ - palette: { - mode: 'dark', - background: { - default: '#070707', - }, - }, - }), - ); - } else if (theme.palette.mode === 'dark') { - setTheme( - createTheme({ - palette: { - mode: 'light', - background: { - default: '#fff', - }, - }, - }), - ); - } - }; - - return ( - - - - - setOpenLearn(false)} /> - - setOpenLearn(true)} - > - - - handleModeChange()} - > - {theme.palette.mode === 'dark' ? : } - - -
- - - - ); -}; - -const loadApp = () => { - if (systemClient.loading) { - setTimeout(loadApp, 200); - } else { - const root = ReactDOM.createRoot(document.getElementById('app') ?? new HTMLElement()); - root.render(); - } -}; - -loadApp(); diff --git a/frontend/src/components/BookTable/index.tsx b/frontend/src/components/BookTable/index.tsx index 35e81aca..b4bbdb6b 100644 --- a/frontend/src/components/BookTable/index.tsx +++ b/frontend/src/components/BookTable/index.tsx @@ -40,12 +40,13 @@ interface Props { maxHeight: number; fullWidth?: number; fullHeight?: number; - defaultFullscreen: boolean; + elevation: number; + defaultFullscreen?: boolean; + fillContainer?: boolean; showControls?: boolean; showFooter?: boolean; onCurrencyChange?: (e: any) => void; onTypeChange?: (mouseEvent: any, val: number) => void; - noResultsOverlay?: () => JSX.Element; } const BookTable = ({ @@ -57,11 +58,12 @@ const BookTable = ({ fullWidth, fullHeight, defaultFullscreen = false, + elevation = 6, + fillContainer = false, showControls = true, showFooter = true, onCurrencyChange, onTypeChange, - noResultsOverlay, }: Props): JSX.Element => { const { t } = useTranslation(); const theme = useTheme(); @@ -656,6 +658,37 @@ const BookTable = ({ Toolbar?: JSX.Element; } + const NoResultsOverlay = function () { + return ( + + + + {fav.type == 0 + ? t('No orders found to sell BTC for {{currencyCode}}', { + currencyCode: + fav.currency == 0 ? t('ANY') : currencyDict[fav.currency.toString()], + }) + : t('No orders found to buy BTC for {{currencyCode}}', { + currencyCode: + fav.currency == 0 ? t('ANY') : currencyDict[fav.currency.toString()], + })} + + + + + {t('Be the first one to create an order')} + + + + ); + }; + const Controls = function () { return ( + NivoTheme = (theme) => { const lightMode = { diff --git a/frontend/src/components/Main.tsx b/frontend/src/components/Main.tsx deleted file mode 100644 index 274d04c7..00000000 --- a/frontend/src/components/Main.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { HashRouter, BrowserRouter, Switch, Route, useHistory } from 'react-router-dom'; -import { useTheme } from '@mui/material'; - -import UserGenPage from './UserGenPage'; -import MakerPage from './MakerPage'; -import BookPage from './BookPage'; -import OrderPage from './OrderPage'; -import BottomBar from './BottomBar'; - -import { apiClient } from '../services/api'; -import checkVer from '../utils/checkVer'; - -import { - Book, - LimitList, - Maker, - Robot, - Info, - Settings, - Favorites, - defaultMaker, - defaultRobot, - defaultInfo, - defaultSettings, -} from '../models'; - -const getWindowSize = function (fontSize: number) { - // returns window size in EM units - return { - width: window.innerWidth / fontSize, - height: window.innerHeight / fontSize, - }; -}; - -const Main = (): JSX.Element => { - const theme = useTheme(); - const history = useHistory(); - const Router = window.NativeRobosats != null ? HashRouter : BrowserRouter; - const basename = window.NativeRobosats != null ? window.location.pathname : ''; - - // All app data structured - const [book, setBook] = useState({ orders: [], loading: true }); - const [limits, setLimits] = useState<{ list: LimitList; loading: boolean }>({ - list: [], - loading: true, - }); - const [robot, setRobot] = useState(defaultRobot); - const [maker, setMaker] = useState(defaultMaker); - const [info, setInfo] = useState(defaultInfo); - const [fav, setFav] = useState({ type: null, currency: 0 }); - const [settings, setSettings] = useState(defaultSettings); - - const [windowSize, setWindowSize] = useState<{ width: number; height: number }>( - getWindowSize(theme.typography.fontSize), - ); - - useEffect(() => { - if (typeof window !== undefined) { - window.addEventListener('resize', onResize); - } - fetchBook(); - fetchLimits(); - fetchInfo(); - return () => { - if (typeof window !== undefined) { - window.removeEventListener('resize', onResize); - } - }; - }, []); - - const onResize = function () { - setWindowSize(getWindowSize(theme.typography.fontSize)); - }; - - const fetchBook = function () { - setBook({ ...book, loading: true }); - apiClient.get('/api/book/').then((data: any) => - setBook({ - loading: false, - orders: data.not_found ? [] : data, - }), - ); - }; - - const fetchLimits = async () => { - setLimits({ ...limits, loading: true }); - const data = apiClient.get('/api/limits/').then((data) => { - setLimits({ list: data ?? [], loading: false }); - return data; - }); - return await data; - }; - - const fetchInfo = function () { - apiClient.get('/api/info/').then((data: any) => { - const versionInfo: any = checkVer(data.version.major, data.version.minor, data.version.patch); - setInfo({ - ...data, - openUpdateClient: versionInfo.updateAvailable, - coordinatorVersion: versionInfo.coordinatorVersion, - clientVersion: versionInfo.clientVersion, - }); - setRobot({ - ...robot, - nickname: data.nickname, - loading: false, - activeOrderId: data.active_order_id ?? null, - lastOrderId: data.last_order_id ?? null, - referralCode: data.referral_code, - tgEnabled: data.tg_enabled, - tgBotName: data.tg_bot_name, - tgToken: data.tg_token, - earnedRewards: data.earned_rewards ?? 0, - stealthInvoices: data.wants_stealth, - }); - }); - }; - - console.log(robot); - return ( - -
- - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - } - /> - -
-
- history.push(location)} - robot={robot} - setRobot={setRobot} - info={info} - setInfo={setInfo} - fetchInfo={fetchInfo} - /> -
-
- ); -}; - -export default Main; diff --git a/frontend/src/components/MakerForm/AutocompletePayments.js b/frontend/src/components/MakerForm/AutocompletePayments.js index 441d4e25..263e7e35 100644 --- a/frontend/src/components/MakerForm/AutocompletePayments.js +++ b/frontend/src/components/MakerForm/AutocompletePayments.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import { useAutocomplete } from '@mui/base/AutocompleteUnstyled'; import { styled } from '@mui/material/styles'; -import { Button, Fade, Tooltip, Typography, Grow } from '@mui/material'; +import { Button, Fade, Tooltip, Typography, Grow, useTheme } from '@mui/material'; import { fiatMethods, swapMethods, PaymentIcon } from '../PaymentMethods'; // Icons @@ -77,9 +77,9 @@ const InputWrapper = styled('div')( & input { background-color: ${theme.palette.mode === 'dark' ? '#141414' : '#fff'}; color: ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.65)' : 'rgba(0,0,0,.85)'}; - height: 2.15em; + height: 2em; box-sizing: border-box; - padding: 4px 6px; + padding: 0.28em 0.4em; width: 0; min-width: 2.15em; font-size: ${theme.typography.fontSize * 1.0714}; @@ -93,11 +93,13 @@ const InputWrapper = styled('div')( ); function Tag(props) { + const theme = useTheme(); const { label, icon, onDelete, ...other } = props; + const iconSize = 1.5 * theme.typography.fontSize; return (
-
- +
+
{label} @@ -122,7 +124,7 @@ const StyledTag = styled(Tag)( border: 1px solid ${theme.palette.mode === 'dark' ? '#303030' : '#e8e8e8'}; border-radius: 2px; box-sizing: content-box; - padding: 0 4px 0 10px; + padding: 0 0.28em 0 0.65em; outline: 0; overflow: hidden; @@ -141,7 +143,7 @@ const StyledTag = styled(Tag)( & svg { font-size: 0.857em; cursor: pointer; - padding: 4px; + padding: 0.28em; } `, ); @@ -150,8 +152,8 @@ const ListHeader = styled('span')( ({ theme }) => ` color: ${theme.palette.mode === 'dark' ? '#90caf9' : '#1976d2'}; align: left; - line-height:10px; - max-height: 10px; + line-height:0.7em; + max-height: 10.7em; display: inline-block; background-color: ${theme.palette.mode === 'dark' ? '#141414' : '#ffffff'}; font-size: 0.875em; @@ -234,6 +236,8 @@ export default function AutocompletePayments(props) { const [val, setVal] = useState(''); const fewerOptions = groupedOptions.length > 8 ? groupedOptions.slice(0, 8) : groupedOptions; + const theme = useTheme(); + const iconSize = 1.5 * theme.typography.fontSize; function handleAddNew(inputProps) { fiatMethods.push({ name: inputProps.value, icon: 'custom' }); @@ -319,7 +323,7 @@ export default function AutocompletePayments(props) { style={{ justifyContent: 'flex-start' }} >
- +
{t(option.name)} diff --git a/frontend/src/components/MakerForm/MakerForm.tsx b/frontend/src/components/MakerForm/MakerForm.tsx index 2711240b..fcd03c10 100644 --- a/frontend/src/components/MakerForm/MakerForm.tsx +++ b/frontend/src/components/MakerForm/MakerForm.tsx @@ -45,7 +45,7 @@ import { LoadingButton } from '@mui/lab'; interface MakerFormProps { limits: { list: LimitList; loading: boolean }; fetchLimits: () => void; - pricingMethods: boolean; + pricingMethods?: boolean; maker: Maker; fav: Favorites; setFav: (state: Favorites) => void; @@ -60,7 +60,7 @@ interface MakerFormProps { const MakerForm = ({ limits, fetchLimits, - pricingMethods, + pricingMethods = false, fav, setFav, maker, diff --git a/frontend/src/models/Settings.default.basic.ts b/frontend/src/models/Settings.default.basic.ts new file mode 100644 index 00000000..235f7e4f --- /dev/null +++ b/frontend/src/models/Settings.default.basic.ts @@ -0,0 +1,7 @@ +import { baseSettings, Settings } from './Settings.model'; + +export const defaultSettings: Settings = { + ...baseSettings, +}; + +export default defaultSettings; diff --git a/frontend/src/models/Settings.default.pro.ts b/frontend/src/models/Settings.default.pro.ts new file mode 100644 index 00000000..42ed07b4 --- /dev/null +++ b/frontend/src/models/Settings.default.pro.ts @@ -0,0 +1,8 @@ +import { baseSettings, Settings } from './Settings.model'; + +export const defaultSettings: Settings = { + ...baseSettings, + fontSize: 12, +}; + +export default defaultSettings; diff --git a/frontend/src/models/Settings.model.ts b/frontend/src/models/Settings.model.ts index 83cb48ae..b9480f25 100644 --- a/frontend/src/models/Settings.model.ts +++ b/frontend/src/models/Settings.model.ts @@ -1,5 +1,34 @@ -export interface Settings {} +export interface Settings { + mode: 'light' | 'dark'; + fontSize: number; + language: + | 'en' + | 'es' + | 'ru' + | 'de' + | 'pl' + | 'fr' + | 'ca' + | 'it' + | 'pt' + | 'eu' + | 'cs' + | 'th' + | 'pl' + | 'sv' + | 'zh-SI' + | 'zh-TR'; + freezeViewports: boolean; +} -export const defaultSettings: Settings = {}; +export const baseSettings: Settings = { + mode: + window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches + ? 'dark' + : 'light', + fontSize: 14, + language: 'en', + freezeViewports: false, +}; export default Settings; diff --git a/frontend/src/models/index.ts b/frontend/src/models/index.ts index 02155d08..e8aefb1b 100644 --- a/frontend/src/models/index.ts +++ b/frontend/src/models/index.ts @@ -10,5 +10,5 @@ export type { Favorites } from './Favorites.model'; export { defaultMaker } from './Maker.model'; export { defaultRobot } from './Robot.model'; -export { defaultSettings } from './Settings.model'; +export { defaultSettings } from './Settings.default.basic'; export { defaultInfo } from './Info.model'; diff --git a/frontend/src/pro/LandingDialog/index.ts b/frontend/src/pro/LandingDialog/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/src/pro/LandingDialog/index.tsx b/frontend/src/pro/LandingDialog/index.tsx new file mode 100644 index 00000000..e1f84441 --- /dev/null +++ b/frontend/src/pro/LandingDialog/index.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Dialog, DialogTitle, DialogContent, Grid, Box } from '@mui/material'; +import { useTheme } from '@mui/system'; + +interface Props { + open: boolean; + onClose: () => void; +} + +const LandingDialog = ({ open, onClose }: Props): JSX.Element => { + const { t } = useTranslation(); + const theme = useTheme(); + + return ( + + {t('Oh... a robot technician has arrived...')} + + + + + + {t('Indeed, but it is my first time. Generate a new workspace and extended token.')} + + + + + {t('Yup, here are my robots. Drag and drop workspace.json')} + + + + + + ); +}; + +export default LandingDialog; diff --git a/frontend/src/pro/Main.tsx b/frontend/src/pro/Main.tsx index 41351672..e3143259 100644 --- a/frontend/src/pro/Main.tsx +++ b/frontend/src/pro/Main.tsx @@ -1,9 +1,8 @@ -import React, { useEffect, useState } from 'react'; -import { HashRouter, BrowserRouter, Switch, Route, useHistory } from 'react-router-dom'; -import { useTheme } from '@mui/material'; +import React, { useEffect, useRef, useState } from 'react'; +import GridLayout, { Layout } from 'react-grid-layout'; +import { Grid, useTheme } from '@mui/material'; import { apiClient } from '../services/api'; -import checkVer from '../utils/checkVer'; import { Book, @@ -16,9 +15,12 @@ import { defaultMaker, defaultRobot, defaultInfo, - defaultSettings, } from '../models'; +import { PlaceholderWidget, MakerWidget, BookWidget } from '../pro/Widgets'; +import ToolBar from '../pro/ToolBar'; +import LandingDialog from '../pro/LandingDialog'; + const getWindowSize = function (fontSize: number) { // returns window size in EM units return { @@ -27,8 +29,138 @@ const getWindowSize = function (fontSize: number) { }; }; -const Main = (): JSX.Element => { - return "Robosats PRO" ; +interface MainProps { + settings: Settings; + setSettings: (state: Settings) => void; +} + +const Main = ({ settings, setSettings }: MainProps): JSX.Element => { + const theme = useTheme(); + + const defaultLayout: Layout = [ + { i: 'MakerWidget', w: 6, h: 13, x: 42, y: 0, minW: 6, maxW: 12, minH: 9, maxH: 18 }, + { i: 'BookWidget', w: 27, h: 13, x: 21, y: 13, minW: 6, maxW: 40, minH: 9, maxH: 15 }, + { i: 'robots', w: 33, h: 13, x: 0, y: 0, minW: 15, maxW: 48, minH: 8, maxH: 20 }, + { i: 'history', w: 7, h: 9, x: 6, y: 13, minW: 6, maxW: 12, minH: 9, maxH: 15 }, + { i: 'trade', w: 9, h: 13, x: 33, y: 0, minW: 6, maxW: 12, minH: 9, maxH: 15 }, + { i: 'depth', w: 8, h: 9, x: 13, y: 13, minW: 6, maxW: 12, minH: 9, maxH: 15 }, + { i: 'settings', w: 6, h: 13, x: 0, y: 13, minW: 6, maxW: 12, minH: 9, maxH: 15 }, + { i: 'other', w: 15, h: 4, x: 6, y: 22, minW: 2, maxW: 30, minH: 4, maxH: 15 }, + ]; + + // All app data structured + const [book, setBook] = useState({ orders: [], loading: true }); + const [limits, setLimits] = useState<{ list: LimitList; loading: boolean }>({ + list: [], + loading: true, + }); + const [robot, setRobot] = useState(defaultRobot); + const [maker, setMaker] = useState(defaultMaker); + const [info, setInfo] = useState(defaultInfo); + const [fav, setFav] = useState({ type: null, currency: 0 }); + const [layout, setLayout] = useState(defaultLayout); + + const [openLanding, setOpenLanding] = useState(true); + const [windowSize, setWindowSize] = useState<{ width: number; height: number }>( + getWindowSize(theme.typography.fontSize), + ); + + useEffect(() => { + if (typeof window !== undefined) { + window.addEventListener('resize', onResize); + } + fetchLimits(); + fetchBook(); + return () => { + if (typeof window !== undefined) { + window.removeEventListener('resize', onResize); + } + }; + }, []); + + const onResize = function () { + setWindowSize(getWindowSize(theme.typography.fontSize)); + }; + + const fetchLimits = async () => { + setLimits({ ...limits, loading: true }); + const data = apiClient.get('/api/limits/').then((data) => { + setLimits({ list: data ?? [], loading: false }); + return data; + }); + return await data; + }; + + const fetchBook = function () { + setBook({ ...book, loading: true }); + apiClient.get('/api/book/').then((data: any) => + setBook({ + loading: false, + orders: data.not_found ? [] : data, + }), + ); + }; + + const bookRef = useRef(null); + console.log(bookRef); + + return ( + + + + setOpenLanding(!openLanding)} /> + + + + setLayout(layout)} + > +
+ +
+
+ +
+ Robot Table + Workspace History + + Trade Box (for selected order in Robot Table) + + Depth Chart + Settings + Other +
+
+
+ ); }; export default Main; diff --git a/frontend/src/pro/ToolBar/index.ts b/frontend/src/pro/ToolBar/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/src/pro/ToolBar/index.tsx b/frontend/src/pro/ToolBar/index.tsx new file mode 100644 index 00000000..c3cb8bf8 --- /dev/null +++ b/frontend/src/pro/ToolBar/index.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Paper, Grid, IconButton, Tooltip } from '@mui/material'; +import { Lock, LockOpen } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; +import { Settings } from '../../models'; + +interface ToolBarProps { + settings: Settings; + setSettings: (state: Settings) => void; +} + +const ToolBar = ({ settings, setSettings }: ToolBarProps): JSX.Element => { + const { t } = useTranslation(); + + return ( + + + ToolBar Goes here! + + + + setSettings({ ...settings, freezeViewports: !settings.freezeViewports }) + } + sx={{ position: 'fixed', right: '1em', top: '0em', color: 'text.secondary' }} + > + {settings.freezeViewports ? : } + + + + + + ); +}; + +export default ToolBar; diff --git a/frontend/src/pro/ViewPorts/index.ts b/frontend/src/pro/ViewPorts/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/src/pro/Widgets/Book.tsx b/frontend/src/pro/Widgets/Book.tsx index e69de29b..67246b1f 100644 --- a/frontend/src/pro/Widgets/Book.tsx +++ b/frontend/src/pro/Widgets/Book.tsx @@ -0,0 +1,64 @@ +import React, { Ref } from 'react'; + +import MakerForm from '../../components/MakerForm'; +import { Book, Favorites } from '../../models'; +import { Paper, useTheme } from '@mui/material'; +import BookTable from '../../components/BookTable'; + +interface BookWidgetProps { + layoutBook: any; + book: Book; + fetchBook: () => void; + fav: Favorites; + setFav: (state: Favorites) => void; + windowSize: { width: number; height: number }; + style?: Object; + className?: string; + onMouseDown?: () => void; + onMouseUp?: () => void; + onTouchEnd?: () => void; +} + +const BookWidget = React.forwardRef( + ( + { + layoutBook, + book, + fetchBook, + fav, + setFav, + windowSize, + style, + className, + onMouseDown, + onMouseUp, + onTouchEnd, + }: BookWidgetProps, + ref, + ) => { + console.log(layoutBook); + const theme = useTheme(); + return React.useMemo(() => { + return ( + + fetchBook()} + book={book} + fav={fav} + fillContainer={true} + maxWidth={(windowSize.width / 48) * layoutBook.w} // EM units + maxHeight={(layoutBook.h * 30) / theme.typography.fontSize} // EM units + fullWidth={windowSize.width} // EM units + fullHeight={windowSize.height} // EM units + defaultFullscreen={false} + onCurrencyChange={(e) => setFav({ ...fav, currency: e.target.value })} + onTypeChange={(mouseEvent, val) => setFav({ ...fav, type: val })} + /> + + ); + }, [book, layoutBook, windowSize, fav]); + }, +); + +export default BookWidget; diff --git a/frontend/src/pro/Widgets/Maker.tsx b/frontend/src/pro/Widgets/Maker.tsx index e69de29b..03d75205 100644 --- a/frontend/src/pro/Widgets/Maker.tsx +++ b/frontend/src/pro/Widgets/Maker.tsx @@ -0,0 +1,58 @@ +import React from 'react'; + +import MakerForm from '../../components/MakerForm'; +import { LimitList, Maker, Favorites } from '../../models'; +import { Paper } from '@mui/material'; + +interface MakerWidgetProps { + limits: { list: LimitList; loading: boolean }; + fetchLimits: () => void; + fav: Favorites; + maker: Maker; + setFav: (state: Favorites) => void; + setMaker: (state: Maker) => void; + style?: Object; + className?: string; + onMouseDown?: () => void; + onMouseUp?: () => void; + onTouchEnd?: () => void; +} + +const MakerWidget = React.forwardRef( + ( + { + maker, + setMaker, + limits, + fetchLimits, + fav, + setFav, + style, + className, + onMouseDown, + onMouseUp, + onTouchEnd, + }: MakerWidgetProps, + ref, + ) => { + return React.useMemo(() => { + return ( + + + + ); + }, [maker, limits, fav]); + }, +); + +export default MakerWidget; diff --git a/frontend/src/pro/Widgets/Placeholder.tsx b/frontend/src/pro/Widgets/Placeholder.tsx new file mode 100644 index 00000000..3c951d19 --- /dev/null +++ b/frontend/src/pro/Widgets/Placeholder.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +import { styled } from '@mui/material/styles'; + +const PlaceholderWidget = styled('div')( + ({ theme }) => ` + background-color: rgb(128,128,128,0.3); + color: ${theme.palette.text.primary}; + font-size: ${theme.typography.fontSize}; + text-align: center; + vertical-align: center; + padding: 1em; + border-radius: 0.3em; + `, +); + +export default PlaceholderWidget; diff --git a/frontend/src/pro/Widgets/index.ts b/frontend/src/pro/Widgets/index.ts index e69de29b..211ec5c5 100644 --- a/frontend/src/pro/Widgets/index.ts +++ b/frontend/src/pro/Widgets/index.ts @@ -0,0 +1,3 @@ +export { default as MakerWidget } from './Maker'; +export { default as BookWidget } from './Book'; +export { default as PlaceholderWidget } from './Placeholder'; diff --git a/frontend/static/css_pro/react-grid-layout.css b/frontend/static/css_pro/react-grid-layout.css new file mode 100644 index 00000000..e3bdf135 --- /dev/null +++ b/frontend/static/css_pro/react-grid-layout.css @@ -0,0 +1,138 @@ +.react-grid-layout { + position: relative; + transition: height 200ms ease; +} +.react-grid-item { + transition: all 200ms ease; + transition-property: left, top; +} +.react-grid-item img { + pointer-events: none; + user-select: none; +} +.react-grid-item.cssTransforms { + transition-property: transform; +} +.react-grid-item.resizing { + z-index: 1; + will-change: width, height; +} + +.react-grid-item.react-draggable-dragging { + transition: none; + z-index: 3; + will-change: transform; +} + +.react-grid-item.dropping { + visibility: hidden; +} + +.react-grid-item.react-grid-placeholder { + background: #1976d2; + opacity: 0.2; + transition-duration: 100ms; + z-index: 2; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} + +.react-grid-item > .react-resizable-handle { + position: absolute; + width: 1em; + height: 1em; +} + +.react-grid-item > .react-resizable-handle::after { + content: ''; + position: absolute; + right: 0.3em; + bottom: 0.3em; + width: 0.8em; + height: 0.8em; + border-right: 2px solid #1976d2; + border-bottom: 2px solid #1976d2; +} + +.react-resizable-hide > .react-resizable-handle { + display: none; +} + +.react-grid-item > .react-resizable-handle.react-resizable-handle-sw { + bottom: 0; + left: 0; + cursor: sw-resize; + transform: rotate(90deg); +} +.react-grid-item > .react-resizable-handle.react-resizable-handle-se { + bottom: 0; + right: 0; + cursor: se-resize; +} +.react-grid-item > .react-resizable-handle.react-resizable-handle-nw { + top: 0; + left: 0; + cursor: nw-resize; + transform: rotate(180deg); +} +.react-grid-item > .react-resizable-handle.react-resizable-handle-ne { + top: 0; + right: 0; + cursor: ne-resize; + transform: rotate(270deg); +} +.react-grid-item > .react-resizable-handle.react-resizable-handle-w, +.react-grid-item > .react-resizable-handle.react-resizable-handle-e { + top: 50%; + margin-top: -10px; + cursor: ew-resize; +} +.react-grid-item > .react-resizable-handle.react-resizable-handle-w { + left: 0; + transform: rotate(135deg); +} +.react-grid-item > .react-resizable-handle.react-resizable-handle-e { + right: 0; + transform: rotate(315deg); +} +.react-grid-item > .react-resizable-handle.react-resizable-handle-n, +.react-grid-item > .react-resizable-handle.react-resizable-handle-s { + left: 50%; + margin-left: -10px; + cursor: ns-resize; +} +.react-grid-item > .react-resizable-handle.react-resizable-handle-n { + top: 0; + transform: rotate(225deg); +} +.react-grid-item > .react-resizable-handle.react-resizable-handle-s { + bottom: 0; + transform: rotate(45deg); +} + +@media (prefers-color-scheme: dark) { + .react-grid-item.react-grid-placeholder { + background: #90caf9; + opacity: 0.4; + transition-duration: 100ms; + z-index: 2; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + } + .react-grid-item > .react-resizable-handle::after { + content: ''; + position: absolute; + right: 0.3em; + bottom: 0.3em; + width: 0.8em; + height: 0.8em; + border-right: 2px solid #90caf9; + border-bottom: 2px solid #90caf9; + } +} diff --git a/frontend/static/css_pro/react-resizable.css b/frontend/static/css_pro/react-resizable.css new file mode 100644 index 00000000..36997595 --- /dev/null +++ b/frontend/static/css_pro/react-resizable.css @@ -0,0 +1,65 @@ +/* .react-resizable { + position: relative; +} +.react-resizable-handle { + position: absolute; + width: 20px; + height: 20px; + background-repeat: no-repeat; + background-origin: content-box; + box-sizing: border-box; + background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCAwIDYgTCAwIDQuMiBMIDQgNC4yIEwgNC4yIDQuMiBMIDQuMiAwIEwgNiAwIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiMwMDAwMDAiLz48L2c+PC9zdmc+'); + background-position: bottom right; + padding: 0 3px 3px 0; +} +.react-resizable-handle-sw { + bottom: 0; + left: 0; + cursor: sw-resize; + transform: rotate(90deg); +} +.react-resizable-handle-se { + bottom: 0; + right: 0; + cursor: se-resize; +} +.react-resizable-handle-nw { + top: 0; + left: 0; + cursor: nw-resize; + transform: rotate(180deg); +} +.react-resizable-handle-ne { + top: 0; + right: 0; + cursor: ne-resize; + transform: rotate(270deg); +} +.react-resizable-handle-w, +.react-resizable-handle-e { + top: 50%; + margin-top: -10px; + cursor: ew-resize; +} +.react-resizable-handle-w { + left: 0; + transform: rotate(135deg); +} +.react-resizable-handle-e { + right: 0; + transform: rotate(315deg); +} +.react-resizable-handle-n, +.react-resizable-handle-s { + left: 50%; + margin-left: -10px; + cursor: ns-resize; +} +.react-resizable-handle-n { + top: 0; + transform: rotate(225deg); +} +.react-resizable-handle-s { + bottom: 0; + transform: rotate(45deg); +} */ diff --git a/frontend/templates/frontend/basic.html b/frontend/templates/frontend/basic.html index 7b1ae269..8efa792b 100644 --- a/frontend/templates/frontend/basic.html +++ b/frontend/templates/frontend/basic.html @@ -55,6 +55,6 @@
- + \ No newline at end of file diff --git a/frontend/templates/frontend/pro.html b/frontend/templates/frontend/pro.html index f8d31413..dd477ef4 100644 --- a/frontend/templates/frontend/pro.html +++ b/frontend/templates/frontend/pro.html @@ -16,6 +16,8 @@ + +