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
This commit is contained in:
Reckless_Satoshi 2022-10-22 14:32:33 +00:00 committed by GitHub
parent 5723cde20e
commit a4a3dbb95e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 854 additions and 432 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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<boolean>(false);
const [theme, setTheme] = useState(defaultTheme);
const [theme, setTheme] = useState<Theme>(defaultTheme);
const [settings, setSettings] = useState<Settings>(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 (
<Suspense fallback='loading language'>
<I18nextProvider i18n={i18n}>
<ThemeProvider theme={theme}>
<CssBaseline />
<LearnDialog open={openLearn} onClose={() => setOpenLearn(false)} />
<TorConnection />
<IconButton
color='inherit'
sx={{ position: 'fixed', right: '34px', color: 'text.secondary' }}
onClick={() => setOpenLearn(true)}
>
<SchoolIcon />
</IconButton>
<IconButton
color='inherit'
sx={{ position: 'fixed', right: '0px', color: 'text.secondary' }}
onClick={() => handleModeChange()}
>
{theme.palette.mode === 'dark' ? <LightModeIcon /> : <DarkModeIcon />}
</IconButton>
<UnsafeAlert className='unsafeAlert' />
<Main />
<Main settings={settings} setSettings={setSettings} />
</ThemeProvider>
</I18nextProvider>
</Suspense>

View File

@ -83,37 +83,6 @@ const BookPage = ({
setFav({ ...fav, type: val });
};
const NoOrdersFound = function () {
return (
<Grid
container
direction='column'
justifyContent='center'
alignItems='center'
sx={{ width: '100%', height: '100%' }}
>
<Grid item>
<Typography align='center' component='h5' variant='h5'>
{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()],
})}
</Typography>
</Grid>
<Grid item>
<Typography align='center' color='primary' variant='h6'>
{t('Be the first one to create an order')}
</Typography>
</Grid>
</Grid>
);
};
const NavButtons = function () {
return (
<ButtonGroup variant='contained' color='inherit'>
@ -153,7 +122,6 @@ const BookPage = ({
<MakerForm
limits={limits}
fetchLimits={fetchLimits}
pricingMethods={false}
maker={maker}
setMaker={setMaker}
fav={fav}
@ -185,7 +153,6 @@ const BookPage = ({
defaultFullscreen={false}
onCurrencyChange={handleCurrencyChange}
onTypeChange={handleTypeChange}
noResultsOverlay={NoOrdersFound}
/>
</Grid>
<Grid item>
@ -220,7 +187,6 @@ const BookPage = ({
defaultFullscreen={false}
onCurrencyChange={handleCurrencyChange}
onTypeChange={handleTypeChange}
noResultsOverlay={NoOrdersFound}
/>
)}
</Grid>

View File

@ -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<boolean>(false);
// All app data structured
const [book, setBook] = useState<Book>({ orders: [], loading: true });
@ -49,7 +61,6 @@ const Main = (): JSX.Element => {
const [maker, setMaker] = useState<Maker>(defaultMaker);
const [info, setInfo] = useState<Info>(defaultInfo);
const [fav, setFav] = useState<Favorites>({ type: null, currency: 0 });
const [settings, setSettings] = useState<Settings>(defaultSettings);
const [windowSize, setWindowSize] = useState<{ width: number; height: number }>(
getWindowSize(theme.typography.fontSize),
@ -119,6 +130,25 @@ const Main = (): JSX.Element => {
return (
<Router basename={basename}>
<div className='temporaryUpperIcons'>
<LearnDialog open={openLearn} onClose={() => setOpenLearn(false)} />
<IconButton
color='inherit'
sx={{ position: 'fixed', right: '34px', color: 'text.secondary' }}
onClick={() => setOpenLearn(true)}
>
<SchoolIcon />
</IconButton>
<IconButton
color='inherit'
sx={{ position: 'fixed', right: '0px', color: 'text.secondary' }}
onClick={() =>
setSettings({ ...settings, mode: settings.mode === 'dark' ? 'light' : 'dark' })
}
>
{theme.palette.mode === 'dark' ? <LightModeIcon /> : <DarkModeIcon />}
</IconButton>
</div>
<div className='appCenter'>
<Switch>
<Route

View File

@ -81,7 +81,6 @@ const MakerPage = ({
<MakerForm
limits={limits}
fetchLimits={fetchLimits}
pricingMethods={false}
fav={fav}
setFav={setFav}
maker={maker}

View File

@ -1,102 +0,0 @@
import React, { Suspense, useState } from 'react';
import ReactDOM from 'react-dom/client';
import Main from './Main';
import { CssBaseline, IconButton } from '@mui/material';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import UnsafeAlert from './UnsafeAlert';
import { LearnDialog } from './Dialogs';
import TorConnection from './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';
const defaultTheme = createTheme({
palette: {
mode:
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light',
background: {
default:
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
? '#070707'
: '#fff',
},
},
});
const App = (): JSX.Element => {
const [openLearn, setOpenLearn] = useState<boolean>(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 (
<Suspense fallback='loading language'>
<I18nextProvider i18n={i18n}>
<ThemeProvider theme={theme}>
<CssBaseline />
<LearnDialog open={openLearn} onClose={() => setOpenLearn(false)} />
<TorConnection />
<IconButton
color='inherit'
sx={{ position: 'fixed', right: '34px', color: 'text.secondary' }}
onClick={() => setOpenLearn(true)}
>
<SchoolIcon />
</IconButton>
<IconButton
color='inherit'
sx={{ position: 'fixed', right: '0px', color: 'text.secondary' }}
onClick={() => handleModeChange()}
>
{theme.palette.mode === 'dark' ? <LightModeIcon /> : <DarkModeIcon />}
</IconButton>
<UnsafeAlert className='unsafeAlert' />
<Main />
</ThemeProvider>
</I18nextProvider>
</Suspense>
);
};
const loadApp = () => {
if (systemClient.loading) {
setTimeout(loadApp, 200);
} else {
const root = ReactDOM.createRoot(document.getElementById('app') ?? new HTMLElement());
root.render(<App />);
}
};
loadApp();

View File

@ -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 (
<Grid
container
direction='column'
justifyContent='center'
alignItems='center'
sx={{ width: '100%', height: '100%' }}
>
<Grid item>
<Typography align='center' component='h5' variant='h5'>
{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()],
})}
</Typography>
</Grid>
<Grid item>
<Typography align='center' color='primary' variant='h6'>
{t('Be the first one to create an order')}
</Typography>
</Grid>
</Grid>
);
};
const Controls = function () {
return (
<BookControl
@ -673,12 +706,10 @@ const BookTable = ({
const gridComponents = function () {
const components: GridComponentProps = {
LoadingOverlay: LinearProgress,
NoResultsOverlay: NoResultsOverlay,
NoRowsOverlay: NoResultsOverlay,
};
if (noResultsOverlay != null) {
components.NoResultsOverlay = noResultsOverlay;
components.NoRowsOverlay = noResultsOverlay;
}
if (showFooter) {
components.Footer = Footer;
}
@ -690,7 +721,14 @@ const BookTable = ({
if (!fullscreen) {
return (
<Paper style={{ width: `${width}em`, height: `${height}em`, overflow: 'auto' }}>
<Paper
elevation={elevation}
style={
fillContainer
? { width: '100%', height: '100%' }
: { width: `${width}em`, height: `${height}em`, overflow: 'auto' }
}
>
<DataGrid
localeText={localeText}
rows={

View File

@ -1,7 +1,5 @@
import { light } from '@mui/material/styles/createPalette';
import { palette } from '@mui/system';
import { Theme as NivoTheme } from '@nivo/core';
import { Theme as MuiTheme } from './createTheme';
import { Theme as MuiTheme } from '@mui/material/styles';
export const getNivoScheme: (theme: MuiTheme) => NivoTheme = (theme) => {
const lightMode = {

View File

@ -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<Book>({ orders: [], loading: true });
const [limits, setLimits] = useState<{ list: LimitList; loading: boolean }>({
list: [],
loading: true,
});
const [robot, setRobot] = useState<Robot>(defaultRobot);
const [maker, setMaker] = useState<Maker>(defaultMaker);
const [info, setInfo] = useState<Info>(defaultInfo);
const [fav, setFav] = useState<Favorites>({ type: null, currency: 0 });
const [settings, setSettings] = useState<Settings>(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 (
<Router basename={basename}>
<div className='appCenter'>
<Switch>
<Route
exact
path='/'
render={(props: any) => (
<UserGenPage match={props.match} theme={theme} robot={robot} setRobot={setRobot} />
)}
/>
<Route
path='/ref/:refCode'
render={(props: any) => (
<UserGenPage match={props.match} theme={theme} robot={robot} setRobot={setRobot} />
)}
/>
<Route
path='/make'
render={() => (
<MakerPage
orders={book.orders}
limits={limits}
fetchLimits={fetchLimits}
maker={maker}
setMaker={setMaker}
fav={fav}
setFav={setFav}
windowSize={windowSize}
/>
)}
/>
<Route
path='/book'
render={() => (
<BookPage
book={book}
fetchBook={fetchBook}
limits={limits}
fetchLimits={fetchLimits}
fav={fav}
setFav={setFav}
maker={maker}
setMaker={setMaker}
lastDayPremium={info.last_day_nonkyc_btc_premium}
windowSize={windowSize}
/>
)}
/>
<Route
path='/order/:orderId'
render={(props: any) => <OrderPage theme={theme} history={history} {...props} />}
/>
</Switch>
</div>
<div
style={{
height: '2.5em',
position: 'fixed',
bottom: 0,
}}
>
<BottomBar
theme={theme}
windowSize={windowSize}
redirectTo={(location: string) => history.push(location)}
robot={robot}
setRobot={setRobot}
info={info}
setInfo={setInfo}
fetchInfo={fetchInfo}
/>
</div>
</Router>
);
};
export default Main;

View File

@ -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 (
<div {...other}>
<div style={{ position: 'relative', left: '-5px', top: '4px' }}>
<PaymentIcon width={22} height={22} icon={icon} />
<div style={{ position: 'relative', left: '-5px', top: '0.28em' }}>
<PaymentIcon width={iconSize} height={iconSize} icon={icon} />
</div>
<span style={{ position: 'relative', left: '2px' }}>{label}</span>
<CloseIcon onClick={onDelete} />
@ -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' }}
>
<div style={{ padding: '0.286em', position: 'relative', top: '0.35em' }}>
<PaymentIcon width={22} height={22} icon={option.icon} />
<PaymentIcon width={iconSize} height={iconSize} icon={option.icon} />
</div>
<Typography variant='inherit' align='left'>
{t(option.name)}

View File

@ -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,

View File

@ -0,0 +1,7 @@
import { baseSettings, Settings } from './Settings.model';
export const defaultSettings: Settings = {
...baseSettings,
};
export default defaultSettings;

View File

@ -0,0 +1,8 @@
import { baseSettings, Settings } from './Settings.model';
export const defaultSettings: Settings = {
...baseSettings,
fontSize: 12,
};
export default defaultSettings;

View File

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

View File

@ -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';

View File

@ -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 (
<Dialog fullWidth maxWidth={'md'} open={open} onClose={onClose}>
<DialogTitle>{t('Oh... a robot technician has arrived...')}</DialogTitle>
<DialogContent sx={{ height: '30em' }}>
<Grid container sx={{ width: '100%', height: '100%' }}>
<Grid item xs={6} sx={{ padding: '1em', width: '100%', height: '100%' }}>
<Box
sx={{
width: '100%',
height: '100%',
backgroundColor: theme.palette.background.paper,
justifyContent: 'center',
alignContent: 'center',
}}
>
{t('Indeed, but it is my first time. Generate a new workspace and extended token.')}
</Box>
</Grid>
<Grid item xs={6} sx={{ padding: '1em', width: '100%', height: '100%' }}>
<Box
sx={{
width: '100%',
height: '100%',
backgroundColor: theme.palette.background.paper,
justifyContent: 'center',
alignContent: 'center',
}}
>
{t('Yup, here are my robots. Drag and drop workspace.json')}
</Box>
</Grid>
</Grid>
</DialogContent>
</Dialog>
);
};
export default LandingDialog;

View File

@ -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 <span> "Robosats PRO" </span>;
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<Book>({ orders: [], loading: true });
const [limits, setLimits] = useState<{ list: LimitList; loading: boolean }>({
list: [],
loading: true,
});
const [robot, setRobot] = useState<Robot>(defaultRobot);
const [maker, setMaker] = useState<Maker>(defaultMaker);
const [info, setInfo] = useState<Info>(defaultInfo);
const [fav, setFav] = useState<Favorites>({ type: null, currency: 0 });
const [layout, setLayout] = useState<Layout>(defaultLayout);
const [openLanding, setOpenLanding] = useState<boolean>(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<HTMLInputElement>(null);
console.log(bookRef);
return (
<Grid
container
direction='column'
sx={{ width: `${windowSize.width * theme.typography.fontSize}px` }}
>
<Grid item>
<ToolBar settings={settings} setSettings={setSettings} />
<LandingDialog open={openLanding} onClose={() => setOpenLanding(!openLanding)} />
</Grid>
<Grid item sx={{ height: `${(windowSize.height / 16) * 14 - 3}em` }}>
<GridLayout
className='layout'
layout={layout}
cols={48}
margin={[theme.typography.fontSize / 2, theme.typography.fontSize / 2]}
isDraggable={!settings.freezeViewports}
isResizable={!settings.freezeViewports}
rowHeight={theme.typography.fontSize * 2.4}
width={windowSize.width * theme.typography.fontSize}
autoSize={true}
onLayoutChange={(layout: Layout) => setLayout(layout)}
>
<div key='MakerWidget'>
<MakerWidget
ref={bookRef}
limits={limits}
fetchLimits={fetchLimits}
fav={fav}
setFav={setFav}
maker={maker}
setMaker={setMaker}
/>
</div>
<div key='BookWidget'>
<BookWidget
book={book}
layoutBook={layout[1]}
fetchBook={fetchBook}
fav={fav}
setFav={setFav}
windowSize={windowSize}
/>
</div>
<PlaceholderWidget key='robots'>Robot Table</PlaceholderWidget>
<PlaceholderWidget key='history'>Workspace History</PlaceholderWidget>
<PlaceholderWidget key='trade'>
Trade Box (for selected order in Robot Table)
</PlaceholderWidget>
<PlaceholderWidget key='depth'>Depth Chart</PlaceholderWidget>
<PlaceholderWidget key='settings'>Settings</PlaceholderWidget>
<PlaceholderWidget key='other'>Other</PlaceholderWidget>
</GridLayout>
</Grid>
</Grid>
);
};
export default Main;

View File

@ -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 (
<Paper
elevation={12}
sx={{
width: `100%`,
height: '3em',
textAlign: 'center',
padding: '1em',
borderRadius: 0,
}}
>
<Grid container>
<Grid item>ToolBar Goes here!</Grid>
<Grid item>
<Tooltip
title={settings.freezeViewports ? t('Customize viewports') : t('Freeze viewports')}
placement='bottom'
enterTouchDelay={500}
enterDelay={700}
enterNextDelay={2000}
>
<IconButton
onClick={() =>
setSettings({ ...settings, freezeViewports: !settings.freezeViewports })
}
sx={{ position: 'fixed', right: '1em', top: '0em', color: 'text.secondary' }}
>
{settings.freezeViewports ? <Lock color='primary' /> : <LockOpen color='secondary' />}
</IconButton>
</Tooltip>
</Grid>
</Grid>
</Paper>
);
};
export default ToolBar;

View File

@ -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 (
<Paper elevation={6} style={{ width: '100%', height: '100%' }}>
<BookTable
elevation={0}
clickRefresh={() => 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 })}
/>
</Paper>
);
}, [book, layoutBook, windowSize, fav]);
},
);
export default BookWidget;

View File

@ -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 (
<Paper
elevation={6}
style={{ padding: 8, overflow: 'auto', width: '100%', height: '100%' }}
>
<MakerForm
limits={limits}
fetchLimits={fetchLimits}
maker={maker}
setMaker={setMaker}
fav={fav}
setFav={setFav}
/>
</Paper>
);
}, [maker, limits, fav]);
},
);
export default MakerWidget;

View File

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

View File

@ -0,0 +1,3 @@
export { default as MakerWidget } from './Maker';
export { default as BookWidget } from './Book';
export { default as PlaceholderWidget } from './Placeholder';

View File

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

View File

@ -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);
} */

View File

@ -55,6 +55,6 @@
</div>
</div>
<script src="{% static "frontend/basic.js" %}"></script>
<script src="{% static "frontend/main.js" %}"></script>
</body>
</html>

View File

@ -16,6 +16,8 @@
<link rel="stylesheet" href="{% static "css/fonts.css" %}"/>
<link rel="stylesheet" type="text/css" href="{% static "css/loader.css" %}"/>
<link rel="stylesheet" type="text/css" href="{% static "css/index.css" %}"/>
<link rel="stylesheet" type="text/css" href="{% static "css_pro/react-grid-layout.css" %}"/>
<link rel="stylesheet" type="text/css" href="{% static "css_pro/react-resizable.css" %}"/>
</head>
<body>
<noscript>

View File

@ -27,7 +27,7 @@ const configWeb: Configuration = {
...config,
output: {
path: path.resolve(__dirname, 'static/frontend'),
filename: 'basic.js',
filename: 'main.js',
},
};
@ -79,6 +79,15 @@ const configWebPro: Configuration = {
async: true,
},
},
{
test: path.resolve(__dirname, 'src/models/Settings.default.basic.ts'),
loader: 'file-replace-loader',
options: {
condition: 'if-replacement-exists',
replacement: path.resolve(__dirname, 'src/models/Settings.default.pro.ts'),
async: true,
},
},
],
},
output: {