Initial commit

This commit is contained in:
rasta5man 2024-04-15 19:28:54 +02:00
commit a589741ec6
33 changed files with 4388 additions and 0 deletions

26
.gitignore vendored Normal file
View file

@ -0,0 +1,26 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
test.js
package-lock.json
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

9
README.md Normal file
View file

@ -0,0 +1,9 @@
This is a terminal for citysys project.
Download the project. Install it with `npm install`.
Start the project: `npm start`
View the project in browser: http://localhost:3000
Run tests: `npm test`
Build project: `npm run build`

Binary file not shown.

6
jsconfig.json Normal file
View file

@ -0,0 +1,6 @@
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}

49
package.json Normal file
View file

@ -0,0 +1,49 @@
{
"name": "terminal-oms.app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@material-ui/core": "^4.11.3",
"@material-ui/icons": "^4.11.2",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^12.7.0",
"axios": "^0.21.1",
"bitwise": "^2.1.0",
"easy-crc": "0.0.2",
"lodash.clonedeep": "^4.5.0",
"material-ui-icons": "^1.0.0-beta.36",
"moment": "^2.29.1",
"react": "^17.0.1",
"react-animated-list": "^0.1.4",
"react-dom": "^17.0.1",
"react-draggable": "^4.4.4",
"react-redux": "^7.2.2",
"react-scripts": "4.0.2",
"redux": "^4.0.5",
"web-vitals": "^1.1.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

43
public/index.html Normal file
View file

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

38
src/App.css Normal file
View file

@ -0,0 +1,38 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

683
src/App.js Normal file
View file

@ -0,0 +1,683 @@
import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import Controls from './controls'
import Output from './output'
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import Box from '@material-ui/core/Box';
import CircularProgress from '@material-ui/core/CircularProgress';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import CloseIcon from '@material-ui/icons/Close';
import Typography from '@material-ui/core/Typography';
import IconButton from '@material-ui/core/IconButton';
import logo from './assets/img/business-manager.svg'
import axios from 'axios'
import Nodes from './components/nodes/nodes';
import Relays from './components/relays/relays';
import DB from './util/db/db'
import findGetParameterInUrl from './util/findGetParameterInUrl';
import FormGroup from '@material-ui/core/FormGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import Radio from '@material-ui/core/Radio';
import RadioGroup from '@material-ui/core/RadioGroup';
import FormControl from '@material-ui/core/FormControl';
import FormLabel from '@material-ui/core/FormLabel';
const divOutputStyle = {
backgroundColor: 'black',
overflow: "auto",
alignItems: "left",
display: "flex",
textAlign: "left",
height: "100vh",
};
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Box p={3}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired,
};
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
backgroundColor: theme.palette.background.paper,
},
}));
const App = (props) => {
document.title = 'IoT Terminal 2.0.4';
// ver 2.0.4 - pridany cct do config.js
const controlsRef = useRef(null);
const outputRef = useRef(null);
const [tabValue, setTabValue] = useState(0);
const [loggedIn, setLoggedIn] = useState(false);
const [username, setUserName] = useState("");
const [password, setPassword] = useState("");
const [baseUrl, setBaseUrl] = useState("");
const [error, setError] = useState("");
const [showprogress, setShowprogress] = useState(false);
const [showDialog, setShowDialog] = useState(false);
const [autoLogin, setAutoLogin] = useState(false);
const [formType, setFormType] = useState("");
// if inputError[key] == null, we disable button so we can not send new settings
const [settings, setSettings] = useState({});
const [disableButton, setDisableButton] = useState(false);
const [inputError, setInputError] = useState({});
const size = useWindowSize();
useEffect(() => {
if(username !== "" & password !== "")
{
login();
}
}, [autoLogin, username, password, baseUrl]);
const validate = (event) =>{
if(username === "" || password === "") return true;
return false;
};
const handleCloseDialog = () =>{
setShowDialog(false);
}
const handleCommand = (command) =>{
setFormType(command);
setShowDialog(true);
}
const handleSettingsChange = (event) =>
{
let { name, value } = event.target;
if(event.target.type == "number")
{
if(isNaN(parseInt(value)) || parseInt(value) < 0 )
{
setDisableButton(true);
setInputError({
...inputError,
[name]: null
});
}
else
{
setDisableButton(false);
setInputError({
...inputError,
[name]: value
});
}
}
if(event.target.type == "checkbox")
{
value = event.target.checked;
}
setSettings({
...settings,
[name]: value
});
}
const makeAxiosRequest = (url, table, action, body) =>
{
return new Promise((resolve, reject) => {
let instance = axios.create();
instance(url,
{
method: 'POST',
mode: 'no-cors',
data:{
hostname: "localhost",
table: table,
//where: ["error_type", "MANUAL"],
action: action,
body: body
},
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
//"Content-Type": "text/html;charset=UTF-8"
},
withCredentials: false,
credentials: 'same-origin',
})
.then(response => {resolve(response)})
.catch(error => {
const message = error + "";
console.error(message);
});
});
}
const loadSettings = () =>
{
let url = baseUrl + `/db`;
makeAxiosRequest(url, "settings", "read","")
.then(response => {
if(!response.data.success)
{
//throw response.data.message;
}
let a = response.data;
console.log("loadSettings", a);
setSettings(a[0]);
setInputError(a[0]);
})
.catch(error => {
const message = error + "";
console.error(message);
});
}
const handleSetNewSettings = () =>
{
let url = baseUrl + `/db`;
makeAxiosRequest(url, "settings", "update", settings)
.then(response => {
if(!response.data.sucess)
{
//throw response.data.message;
}
if(response.data === 1)
// if(response.data.data === 1)
{
alert("Nove nastavenia uspesne zapisane do databazy")
}
})
.catch(error => {
const message = error + "";
console.error(message);
});
}
const login = (event) =>{
axios(baseUrl + "/validate",
{
method: 'POST',
mode: 'no-cors',
data:{
username: username,
password: password,
},
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
},
withCredentials: false,
credentials: 'same-origin',
})
.then(response => {
console.log(response);
if(response.data.valid)
{
setLoggedIn(true);
setShowprogress(false);
setError("");
loadSettings();
}
else
{
setError("Nesprávne meno, alebo heslo");
setShowprogress(false);
}
})
.catch(error => {
const message = error + "";
setError(message);
setShowprogress(false);
});
};
const handleLogin = (event) =>{
if(username === "" || password === "")
{
setError("Nie je zadané meno a heslo");
return;
}
setShowprogress(true);
login();
};
const handleTabChange = (event, newValue) => {
setTabValue(newValue);
};
const handleChange = event => {
if(event.target.name === "username") setUserName(event.target.value);
if(event.target.name === "password") setPassword(event.target.value);
};
const buildUi = () =>
{
if(loggedIn)
{
let widgets = [];
let mainUi = (
<Grid container spacing={0}>
<Grid item xs={12} sm={4}>
<div ref={controlsRef} style={{backgroundColor: 'white', textAlign: "left"}}>
<Controls handleCommand = {handleCommand} username = {username} password = {password} title = {document.title}/>
</div>
</Grid>
<Grid item xs={12} sm={8}>
<div ref={outputRef} style={divOutputStyle}>
<div style ={{padding: "10px", width: "100%"}}>
<Output/>
</div>
</div>
</Grid>
</Grid>
)
widgets.push(mainUi);
if(showDialog)
{
// push all data from settings.table to grid
let allSettings = [];
if(settings !== undefined)
{
Object.keys(settings).forEach(function(key) {
// console.log(key, settings[key]);
if(key == "backup_on_failure" || key == "maintanace_mode")
{
allSettings.push(
(<Grid key={key} item>
<FormGroup>
<FormControlLabel
control={<Checkbox checked={settings[key]} onChange={handleSettingsChange}/>}
name={key}
label={key}
/>
</FormGroup>
</Grid>)
)
}
else if(key == "controller_type")
{
allSettings.push(
(<Grid key={key} item>
<FormControl>
<FormLabel>{key}</FormLabel>
<RadioGroup
onChange={handleSettingsChange}
row
aria-label={key}
value={settings[key]}
name="controller_type"
>
<FormControlLabel value="lm" control={<Radio />} label="LM" />
<FormControlLabel value="unipi" control={<Radio />} label="Unipi" />
</RadioGroup>
</FormControl>
</Grid>)
)
}
else if(['restore_from_backup', 'restore_backup_wait', 'mqtt_port', 'projects_id'].includes(key))
{
allSettings.push(
(<Grid key={key} item>
<TextField
id={key}
label={key}
name={key}
error={inputError[key] == null? true: false}
type="number"
value={settings[key]}
onChange={handleSettingsChange}
helperText={inputError[key] == null? 'Zadaj platné číslo': null}
/>
</Grid>)
)
}
else
{
allSettings.push(
(<Grid key={key} item>
<TextField
id={key}
name={key}
label={key}
onChange={handleSettingsChange}
value={settings[key]}
/>
</Grid>)
)
}
});
}
let dialog = (
<div>
<Dialog open={true} fullScreen aria-labelledby="form-dialog-title">
<AppBar>
<Toolbar>
<IconButton edge="start" color="inherit" onClick={handleCloseDialog} aria-label="close">
<CloseIcon />
</IconButton>
<Typography variant="h6">
Nastavenia FLOW
</Typography>
</Toolbar>
<Tabs value={tabValue} onChange={handleTabChange} aria-label="simple tabs example">
<Tab label="Nastavenia" />
<Tab label="Zoznam nodov" />
<Tab label="Zoznam linii" />
</Tabs>
</AppBar>
<DialogTitle id="customized-dialog-title" onClose={handleCloseDialog}>
Modal title
</DialogTitle>
<DialogContent>
<TabPanel style = {{marginTop: 50}} value={tabValue} index={0}>
{/* //! tato form robi tuto chybu v konzole: Warning: validateDOMNesting(...): <form> cannot appear as a descendant of <p>. */}
<form noValidate autoComplete="off">
<Grid container spacing={3}>
{allSettings}
</Grid>
<br></br>
<Button
onClick={handleSetNewSettings}
variant="contained"
disabled={disableButton}
color="primary"
>
Ulož nastavenia
</Button>
</form>
</TabPanel>
<TabPanel value={tabValue} index={1}>
<br/><Nodes/>
</TabPanel>
<TabPanel value={tabValue} index={2}>
<br/><Relays/>
</TabPanel>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleCloseDialog} color="primary">
Zatvor
</Button>
</DialogActions>
</Dialog>
</div>
)
widgets.push(dialog);
}
return widgets;
}
if(showprogress)
{
return(
<div>
<Dialog open={true} aria-labelledby="form-dialog-title">
<Box minWidth="90">
<img src={logo} className="App-logo" alt="logo" />
</Box>
<DialogContent>
<div>Prihlasujem...</div>
<br></br>
<CircularProgress />
</DialogContent>
</Dialog>
</div>
)
}
return loginDialog();
}
const loginDialog = () =>
{
return (
<div>
<Dialog open={true} aria-labelledby="form-dialog-title"
>
<br/><br/>
<img style = {{marginTop: "10px", height: "20vmin"}} src={logo} className="App-logo" alt="logo" />
<DialogContent style={{ overflow: "hidden" }}>
<Grid container spacing={3}>
<Grid item xs={12}>
<TextField
margin="dense"
id="username"
name="username"
label="Prihlasovacie meno"
type="text"
fullWidth
value= {username}
onChange={handleChange}
/>
<div style={{ fontSize: 12, color: "red", textAlign: "left"}}>
</div>
</Grid>
<Grid item xs={12}>
<TextField
margin="dense"
id="password"
name="password"
label="Heslo"
type="password"
fullWidth
value= {password}
onChange={handleChange}
/>
</Grid>
</Grid>
<div style={{ fontSize: 12, color: "red", textAlign: "left"}}>
{error}
</div>
<div style={{ fontSize: 12, color: "red", textAlign: "left"}}>
</div>
</DialogContent>
<DialogActions>
<Grid container alignItems="flex-end" spacing={2}>
<Grid item sm={12} xs={12}>
<Button onClick={handleLogin} color="primary">
Prihlásiť
</Button>
</Grid>
</Grid>
</DialogActions>
</Dialog>
</div>
);
}
return (
<div className="App">
{buildUi()}
</div>
);
// Hook
function useWindowSize() {
// Initialize state with undefined width/height so server and client renders match
// Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined,
});
useEffect(() => {
let baseUrl = '';
if(window.location.hostname === "localhost")
{
//baseUrl = "http://localhost:5000";
baseUrl = "http://10.0.0.5:5000";
}
setBaseUrl(baseUrl);
DB.backendUrl = baseUrl + `/db`;
let username = findGetParameterInUrl("username");
let password = findGetParameterInUrl("password");
if(username === undefined) username = "";
if(username === undefined) username = "";
if(username !== "" && password !== "")
{
setUserName(username);
setPassword(password);
setAutoLogin(true);
}
// Handler to call on window resize
function handleResize() {
// Set window width/height to state
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
// Add event listener
window.addEventListener("resize", handleResize);
// Call handler right away so state gets updated with initial window size
handleResize();
// Remove event listener on cleanup
return () => window.removeEventListener("resize", handleResize);
}, []); // Empty array ensures that effect is only run on mount
if(controlsRef.current == null || outputRef == null);
else
{
if(windowSize.width > 600)
{
outputRef.current.style.height = "100vh";
}
else
{
outputRef.current.style.height = (windowSize.height - controlsRef.current.clientHeight) + "px";
}
}
return windowSize;
}
}
export default App;

8
src/App.test.js Normal file
View file

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View file

@ -0,0 +1,17 @@
<?xml version="1.0" ?><svg id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style type="text/css">
.st0{fill:#00B8E9;}
.st1{fill:#DCC5A1;}
.st2{fill:#EDD9B4;}
.st3{fill-rule:evenodd;clip-rule:evenodd;fill:#BC9F82;}
.st4{fill:#FFFFFF;}
.st5{fill:#E5917A;}
.st6{fill-rule:evenodd;clip-rule:evenodd;fill:#422F18;}
.st7{fill:#342214;}
.st8{fill:#3E7EBC;}
.st9{fill:#89BCE5;}
.st10{fill:#BE1E2D;}
.st11{fill:#E6E6E5;}
.st12{fill:#1B75BC;}
.st13{fill:#314E67;}
.st14{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
</style><g><path class="st0" d="M83.2,374.5c10-8.9,34.1-27.7,108.8-61.8c0.5-0.2,1-0.5,1.5-0.7l16.7-7.5c0.3-0.1,0.6-0.3,0.9-0.4v20.3 l3.8,32.9c18.1-5.2,41.2-10.7,41.2-10.7l40.4,13.9l4.2-36.2l0-20.2c0.5,0.2,0.9,0.4,1.4,0.6l7.8,3.5c5,2.2,9.7,4.4,14.3,6.5 c9.6,4.4,18.3,8.5,26.2,12.4c51.6,25.3,69.7,39.8,78.3,47.4c3.4,3,6.6,7.8,9.4,13.8c21.2-33.4,33.5-73,33.5-115.5 C471.7,153.6,375.1,57,256,57S40.3,153.6,40.3,272.8c0,42.5,12.3,82.1,33.5,115.5C76.7,382.3,79.8,377.6,83.2,374.5z"/><path class="st1" d="M211.2,222.1v102.4c12.3,14.6,28.6,22.1,44.8,22.3V180.6C233.6,180.6,211.2,194.4,211.2,222.1z"/><path class="st2" d="M256,180.6v166.1c16.3,0.1,32.5-7.2,44.8-22.3V222.1C300.8,194.4,278.4,180.6,256,180.6z"/><path class="st3" d="M300.8,220.2v65.5c-17,11.8-33.3,18.4-44.8,18.4c-11.5,0-27.9-6.6-44.8-18.4v-65.5 C211.2,167.4,300.8,167.4,300.8,220.2"/><path class="st4" d="M204.4,219.2c0,0,43.6,14.7,98.4,0C302.8,219.2,262,268.6,204.4,219.2"/><path class="st5" d="M204.4,219.2c0,0,45.5,15.8,98.4,0C271.2,217.6,277.8,216,204.4,219.2"/><path class="st5" d="M204.4,219.2c0,0,48.6,47.1,98.4,0c0,0-10.7,34.1-46.7,34.2C220.1,253.6,204.4,219.2,204.4,219.2"/><g><path class="st1" d="M256,38.3c-91.4,0-107.3,67.1-103.5,123c-4-3.4-8.3-4.9-12.1-3.6c-7.9,2.6-10.7,15.8-6.2,29.4 c4.5,13.7,14.5,22.6,22.4,20c1-0.3,2-0.9,2.8-1.6c3.7,15.1,7.9,25.9,10,29c10.8,16,62.2,58.3,86.5,58.3h0L256,38.3L256,38.3z"/><path class="st2" d="M371.3,157.7c-3.7-1.2-7.9,0.1-11.7,3.3c3.7-55.9-12.3-122.7-103.6-122.7v254.6c24.3,0,75.7-42.3,86.5-58.3 c2.1-3.1,6.2-13.8,9.9-28.7c0.8,0.6,1.6,1,2.5,1.3c7.9,2.6,18-6.4,22.4-20C381.9,173.5,379.2,160.3,371.3,157.7z"/></g><path class="st6" d="M187.2,99.1c-8.7,47.7-30.6,43.2-28.9,99.2c-7-43.6-17.5-101.7,12.1-139.9c23.7-30.6,102.2-52.4,153-15.8 c56.1,12,38.6,126.3,31.1,147.9c-5.3-35-17-49.6-31.9-90.7C302.5,114.3,213.1,117.5,187.2,99.1"/><g><path class="st7" d="M212.9,158.6c5.8,0,10.6,5.4,10.6,12c0,6.6-4.7,12-10.6,12c-5.8,0-10.6-5.4-10.6-12 C202.3,163.9,207,158.6,212.9,158.6"/><path class="st7" d="M298.7,158.6c5.8,0,10.6,5.4,10.6,12c0,6.6-4.7,12-10.6,12c-5.8,0-10.6-5.4-10.6-12 C288.2,163.9,292.9,158.6,298.7,158.6"/><path class="st4" d="M234,234.5c3.8,8.3,12.1,14.1,21.8,14.1c9.7,0,18.1-5.8,21.8-14.1H234z"/></g><path class="st8" d="M193.5,312l16.7-7.5C204.5,307.1,198.9,309.6,193.5,312z"/><path class="st8" d="M302.2,304.8l7.8,3.5C307.5,307.1,304.9,306,302.2,304.8z"/><path class="st9" d="M192,312.7L192,312.7c0.5-0.2,1-0.5,1.5-0.7L192,312.7z"/><path class="st9" d="M211.2,304.1l-0.9,0.4C210.6,304.4,210.9,304.2,211.2,304.1L211.2,304.1z"/><path class="st9" d="M324.2,314.6l-14.2-6.3c5,2.2,9.7,4.4,14.3,6.5L324.2,314.6z"/><path class="st9" d="M300.8,304.2L300.8,304.2c0.5,0.2,0.9,0.4,1.4,0.6L300.8,304.2z"/><polygon class="st10" points="256,488.5 256,488.5 256,488.5 "/><path class="st8" d="M137.1,452.8c1.2-9.4,2-15.2,2-15.2s0,6.2,0,16.5c15.7,10.2,32.9,18.3,51.1,24.2l-29.9-60.7l35.2-23.5 l-43-20.1l39.5-61.3c-74.7,34.1-98.8,52.9-108.8,61.8c-3.4,3-6.6,7.8-9.4,13.8C90.1,414,111.7,436,137.1,452.8z"/><path class="st8" d="M428.8,374.5c-8.5-7.6-26.7-22.1-78.3-47.4c-7.9-3.9-16.6-8-26.2-12.4l35,59.3l-43,20.1l35.2,23.5l-29.9,60.8 c18.3-5.8,35.5-14,51.3-24.2c0-10.3,0-16.5,0-16.5s0.8,5.7,2,15.2c25.4-16.8,47-38.8,63.3-64.5 C435.3,382.3,432.2,377.6,428.8,374.5z"/><path class="st11" d="M230,486.9l0.1,1.1c0.9,0,1.7,0.1,2.6,0.1l0.1-0.8C231.9,487.2,231,487.1,230,486.9z"/><path class="st11" d="M230,486.9c0.9,0.1,1.9,0.2,2.8,0.3l14.7-103l-12-16.6l20.6-21c0,0-23.1,5.5-41.2,10.7L230,486.9z"/><path class="st11" d="M279.2,487.3l0.1,0.8c0.8,0,1.6-0.1,2.4-0.1l0.1-1C280.9,487.1,280,487.2,279.2,487.3z"/><path class="st11" d="M256.2,346.7l19.9,21l-11.8,16.6h0l14.7,103c0.9-0.1,1.8-0.2,2.7-0.3l14.8-126.4L256.2,346.7z"/><path class="st9" d="M215,357.4l-3.8-32.9v-20.3c-0.3,0.1-0.6,0.3-0.9,0.4l-16.7,7.5c-0.5,0.2-1,0.5-1.5,0.7L152.5,374l43,20.1 l-35.2,23.5l29.9,60.7c8.4,2.7,17,4.9,25.8,6.5c4.6,0.9,9.3,1.6,14,2.2L215,357.4z"/><path class="st9" d="M359.3,374l-35-59.3c-4.6-2.1-9.3-4.3-14.3-6.5l-7.8-3.5c-0.5-0.2-0.9-0.4-1.4-0.6l0,20.2l-4.2,36.2L281.8,487 c7.2-0.9,14.3-2.1,21.3-3.6c6.3-1.4,12.4-3.1,18.5-5l29.9-60.8l-35.2-23.5L359.3,374z"/><path class="st12" d="M264.4,384.3L264.4,384.3l11.8-16.6l-19.9-21l-20.6,21l12,16.6l-14.7,103c7.6,0.8,15.3,1.2,23.1,1.2 c0,0,0,0,0,0c7.8,0,15.5-0.4,23.1-1.2L264.4,384.3z"/><path class="st13" d="M137.1,452.8c0.7,0.4,1.3,0.9,2,1.3c0-10.3,0-16.5,0-16.5S138.3,443.3,137.1,452.8z"/><path class="st13" d="M372.9,454.1c0.7-0.4,1.3-0.9,2-1.3c-1.2-9.4-2-15.2-2-15.2S373,443.8,372.9,454.1z"/><polygon class="st14" points="211.2,290.6 207.2,305.8 218.2,385.2 256.2,346.7 "/><polygon class="st14" points="300.8,290.6 303.9,305.6 293.6,386 256.2,346.7 "/></g></svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -0,0 +1,110 @@
import React, { useEffect } from 'react';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import Paper from '@material-ui/core/Paper';
import Draggable from 'react-draggable';
import WarningIcon from '@material-ui/icons/Warning';
import { green } from '@material-ui/core/colors';
import { red } from '@material-ui/core/colors';
import { blue } from '@material-ui/core/colors';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import Typography from '@material-ui/core/Typography';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemText from '@material-ui/core/ListItemText';
import ListItem from '@material-ui/core/ListItem';
import List from '@material-ui/core/List';
import Divider from '@material-ui/core/Divider';
function PaperComponent(props) {
return (
<Draggable handle="#draggable-dialog-title" cancel={'[class*="MuiDialogContent-root"]'}>
<Paper {...props} />
</Draggable>
);
}
export default function AlertDialog(props) {
const [open, setOpen] = React.useState(props.open);
useEffect(() => {
setOpen(props.open);
}, [props.open])
const handleClickOpen = () => {
//setOpen(true);
};
const handleClose = () => {
//setOpen(false);
props.onConfirm();
};
return (
<div>
<Dialog
open={open}
onClose={handleClose}
PaperComponent={PaperComponent}
aria-labelledby="draggable-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle style={{ cursor: 'move' }} id="draggable-dialog-title">
<MenuItem>
<ListItemIcon>
<WarningIcon fontSize="large" style={{ color: red[500] }} />
</ListItemIcon>
<Typography variant="inherit">{props.title}</Typography>
</MenuItem>
</DialogTitle>
{
Array.isArray(props.content)
?
(
<List>
{
props.content.map((message) => (
<>
<ListItem button>
<ListItemText primary={message} secondary="" />
</ListItem>
<Divider />
</>
))
}
</List>
)
:
(
<DialogContent>
<DialogContentText id="alert-dialog-description">
{props.content}
</DialogContentText>
</DialogContent>)
}
<DialogActions>
<Button onClick={handleClose} color="primary">
OK
</Button>
<Button onClick={handleClose} color="primary" autoFocus>
Cancel
</Button>
</DialogActions>
</Dialog>
</div>
);
}

View file

@ -0,0 +1,106 @@
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import MenuList from '@material-ui/core/MenuList';
import { grey } from '@material-ui/core/colors';
import { green } from '@material-ui/core/colors';
import { red } from '@material-ui/core/colors';
import { blue } from '@material-ui/core/colors';
import AddCircleIcon from '@material-ui/icons/AddCircle';
import DeleteForeverIcon from '@material-ui/icons/DeleteForever';
import EditIcon from '@material-ui/icons/Edit';
import SearchIcon from '@material-ui/icons/Search';
import Typography from '@material-ui/core/Typography';
import ExitToAppIcon from '@material-ui/icons/ExitToApp';
const StyledMenu = withStyles({
paper: {
border: '1px solid #d3d4d5',
},
})((props) => (
<Menu
elevation={0}
getContentAnchorEl={null}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
{...props}
/>
));
/*
const StyledMenuItem = withStyles((theme) => ({
root: {
'&:focus': {
backgroundColor: theme.palette.primary.main,
'& .MuiListItemIcon-root, & .MuiListItemText-primary': {
color: theme.palette.common.white,
},
},
},
}))(MenuItem);
*/
export default function CustomizedMenus(props) {
const [anchorEl, setAnchorEl] = React.useState(props.anchorref);
const handleClick = (event, menu) => {
setAnchorEl(event.currentTarget);
props.menuClicked(menu);
};
const handleClose = () => {
setAnchorEl(null);
props.menuClosed();
};
return (
<div>
<StyledMenu
id="customized-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuList open={Boolean(anchorEl)} id="menu-list-grow" >
<MenuItem onClick={(event)=>handleClick(event, "add")}>
<ListItemIcon>
<AddCircleIcon style={{ color: green[500] }} fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">pridať</Typography>
</MenuItem>
<MenuItem onClick={(event)=>handleClick(event, "edit")}>
<ListItemIcon>
<EditIcon style={{ color: blue[300] }} fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">editovat</Typography>
</MenuItem>
<MenuItem onClick={(event)=>handleClick(event, "delete")}>
<ListItemIcon>
<DeleteForeverIcon style={{ color: red[500] }} fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">zmazať</Typography>
</MenuItem>
<MenuItem onClick={(event)=>handleClick(event, "exit")}>
<ListItemIcon>
<ExitToAppIcon style={{ color: grey[500] }} fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">logout</Typography>
</MenuItem>
</MenuList>
</StyledMenu>
</div>
);
}

View file

@ -0,0 +1,207 @@
import React from 'react';
import TableDb from 'components/table/tableDb';
//import TableDb from '../table/tableDb';
import DB from "util/db/db";
//import { LogicOperator } from '../../util/db/SqlQueryBuilder';
//import Find from './find';
//import Form from './form';
import CustomizedMenus from './menu'
import MoreVertIcon from '@material-ui/icons/MoreVert';
//import Card from "components/Card/Card.js";
//import CardBody from "components/Card/CardBody.js";
//import CardHeader from "components/Card/CardHeader.js";
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import Paper from '@material-ui/core/Paper';
import SearchIcon from '@material-ui/icons/Search';
import AddIcon from '@material-ui/icons/Add';
import EditIcon from '@material-ui/icons/Edit';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
//-----------------------------------------------------
class Nodes extends TableDb {
constructor(props) {
super(props);
this.title = "Zoznam nodov";
this.dbTable = "nodes";
this.state.columns = ["actions", "node", "tbname", "line", "status"];
this.state.visibleColumns = ["actions", "node", "tbname", "line", "status"];
this.renamedColumns = {
node: "node",
note: "poznámka",
num: "počet",
};
}
menuClicked(menu)
{
if(menu === "delete")
{
if(this.selectedRows.length === 0)
{
this.menuClosed();
this.showAlertDialog("Mazanie", "Nie sú označené žiadne riadky");
return;
}
var r = window.confirm("Naozaj chcete zmazať?");
if (r === false) return;
let db = new DB();
db.dbStartTransaction();
//default - odstranit z subject
let conditions = {"id": this.id};
db.dbDelete(this.getTableName(false), conditions);
db.dbCommitTransaction();
let index = this.index;
db.exec().then(
result => {
this.deleteRow(index);
},
error => {
console.log(error);
this.showAlertDialog("Chyba", error);
}
);
return;
}
else if(menu === "exit")
{
this.props.logout();
}
let processed = super.menuClicked(menu);
if(processed) return;
}
getTableName(withView = false)
{
//if(withView) return "view_subject";
//return this.dbTable;
return this.dbTable;
}
//add or edit
/*
renderForm()
{
let data = undefined;
if(this.state.data == null);
else data = this.state.data[this.index];
//getIndex
//console.log(data);alert();
let status = TableDb.TABLE_STATUS.EDIT;
if(data === undefined)
{
data = {};
status = TableDb.TABLE_STATUS.ADD;
}
return (
<Form
handleBack={this.handleBack.bind(this)}
data = {data}
status = {status}
parent_id = {this.state.parent_id}
ui = {this.state.ui}
/>
)
}
*/
renderUiTable()
{
return(
<Card>
<CardContent>
<Paper style = {{backgroundColor: "#7bd753", padding: 10}} elevation={3}>
<Grid container justify="space-between">
<Typography display="inline" variant="body1" align="left">
<Tooltip title="zobraz menu" arrow>
<Button
ref={this.anchorMenuRef}
aria-controls={this.state.openMenu ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={this.handleToggleMenu.bind(this)}
startIcon={<MoreVertIcon />}
>
{this.title}
</Button>
</Tooltip>
{this.renderCancelFilterIcon()}
<IconButton onClick={() => this.menuClicked("edit")} aria-label="search" color="inherit">
<EditIcon />
</IconButton>
</Typography>
<Typography display="inline" variant="body1" align="right">
<Tooltip title="pridaj" arrow>
<IconButton onClick={() => this.menuClicked("add")} aria-label="search" color="inherit">
<AddIcon />
</IconButton>
</Tooltip>
</Typography>
<Button>Csv</Button>
</Grid>
</Paper>
<div>
{this.renderTable()}
</div>
</CardContent>
</Card>
)
}
renderUiForm()
{
return this.renderForm();
}
renderMenu()
{
return (
<CustomizedMenus
anchorref={this.anchorMenuRef.current}
menuClicked = {this.menuClicked.bind(this)}
menuClosed = {this.menuClosed.bind(this)}
/>
);
}
}
export default Nodes;

View file

@ -0,0 +1,106 @@
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import MenuList from '@material-ui/core/MenuList';
import { grey } from '@material-ui/core/colors';
import { green } from '@material-ui/core/colors';
import { red } from '@material-ui/core/colors';
import { blue } from '@material-ui/core/colors';
import AddCircleIcon from '@material-ui/icons/AddCircle';
import DeleteForeverIcon from '@material-ui/icons/DeleteForever';
import EditIcon from '@material-ui/icons/Edit';
import SearchIcon from '@material-ui/icons/Search';
import Typography from '@material-ui/core/Typography';
import ExitToAppIcon from '@material-ui/icons/ExitToApp';
const StyledMenu = withStyles({
paper: {
border: '1px solid #d3d4d5',
},
})((props) => (
<Menu
elevation={0}
getContentAnchorEl={null}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
{...props}
/>
));
/*
const StyledMenuItem = withStyles((theme) => ({
root: {
'&:focus': {
backgroundColor: theme.palette.primary.main,
'& .MuiListItemIcon-root, & .MuiListItemText-primary': {
color: theme.palette.common.white,
},
},
},
}))(MenuItem);
*/
export default function CustomizedMenus(props) {
const [anchorEl, setAnchorEl] = React.useState(props.anchorref);
const handleClick = (event, menu) => {
setAnchorEl(event.currentTarget);
props.menuClicked(menu);
};
const handleClose = () => {
setAnchorEl(null);
props.menuClosed();
};
return (
<div>
<StyledMenu
id="customized-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuList open={Boolean(anchorEl)} id="menu-list-grow" >
<MenuItem onClick={(event)=>handleClick(event, "add")}>
<ListItemIcon>
<AddCircleIcon style={{ color: green[500] }} fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">pridať</Typography>
</MenuItem>
<MenuItem onClick={(event)=>handleClick(event, "edit")}>
<ListItemIcon>
<EditIcon style={{ color: blue[300] }} fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">editovat</Typography>
</MenuItem>
<MenuItem onClick={(event)=>handleClick(event, "delete")}>
<ListItemIcon>
<DeleteForeverIcon style={{ color: red[500] }} fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">zmazať</Typography>
</MenuItem>
<MenuItem onClick={(event)=>handleClick(event, "exit")}>
<ListItemIcon>
<ExitToAppIcon style={{ color: grey[500] }} fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">logout</Typography>
</MenuItem>
</MenuList>
</StyledMenu>
</div>
);
}

View file

@ -0,0 +1,208 @@
import React from 'react';
import TableDb from 'components/table/tableDb';
//import TableDb from '../table/tableDb';
import DB from "util/db/db";
//import { LogicOperator } from '../../util/db/SqlQueryBuilder';
//import Find from './find';
//import Form from './form';
import CustomizedMenus from './menu'
import MoreVertIcon from '@material-ui/icons/MoreVert';
//import Card from "components/Card/Card.js";
//import CardBody from "components/Card/CardBody.js";
//import CardHeader from "components/Card/CardHeader.js";
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import Paper from '@material-ui/core/Paper';
import SearchIcon from '@material-ui/icons/Search';
import AddIcon from '@material-ui/icons/Add';
import EditIcon from '@material-ui/icons/Edit';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
//-----------------------------------------------------
class Relays extends TableDb {
constructor(props) {
super(props);
this.title = "Zoznam línií";
this.dbTable = "relays";
//node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean
this.state.columns = ["actions", "line", "tbname", "contactor"];
this.state.visibleColumns = ["actions", "line", "tbname", "contactor"];
this.renamedColumns = {
line: "línia",
contactor: "spínač",
tbname: "tbname",
};
}
menuClicked(menu)
{
if(menu === "delete")
{
if(this.selectedRows.length === 0)
{
this.menuClosed();
this.showAlertDialog("Mazanie", "Nie sú označené žiadne riadky");
return;
}
var r = window.confirm("Naozaj chcete zmazať?");
if (r === false) return;
let db = new DB();
db.dbStartTransaction();
//default - odstranit z subject
let conditions = {"id": this.id};
db.dbDelete(this.getTableName(false), conditions);
db.dbCommitTransaction();
let index = this.index;
db.exec().then(
result => {
this.deleteRow(index);
},
error => {
console.log(error);
this.showAlertDialog("Chyba", error);
}
);
return;
}
else if(menu === "exit")
{
this.props.logout();
}
let processed = super.menuClicked(menu);
if(processed) return;
}
getTableName(withView = false)
{
//if(withView) return "view_subject";
//return this.dbTable;
return this.dbTable;
}
//add or edit
/*
renderForm()
{
let data = undefined;
if(this.state.data == null);
else data = this.state.data[this.index];
//getIndex
//console.log(data);alert();
let status = TableDb.TABLE_STATUS.EDIT;
if(data === undefined)
{
data = {};
status = TableDb.TABLE_STATUS.ADD;
}
return (
<Form
handleBack={this.handleBack.bind(this)}
data = {data}
status = {status}
parent_id = {this.state.parent_id}
ui = {this.state.ui}
/>
)
}
*/
renderUiTable()
{
return(
<Card>
<CardContent>
<Paper style = {{backgroundColor: "#7bd753", padding: 10}} elevation={3}>
<Grid container justify="space-between">
<Typography display='inline' variant="body1" align="left">
<Tooltip title="zobraz menu" arrow>
<Button
ref={this.anchorMenuRef}
aria-controls={this.state.openMenu ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={this.handleToggleMenu.bind(this)}
startIcon={<MoreVertIcon />}
>
{this.title}
</Button>
</Tooltip>
{this.renderCancelFilterIcon()}
<IconButton onClick={() => this.menuClicked("edit")} aria-label="search" color="inherit">
<EditIcon />
</IconButton>
</Typography>
<Typography display='inline' variant="body1" align="right">
<Tooltip title="pridaj" arrow>
<IconButton onClick={() => this.menuClicked("add")} aria-label="search" color="inherit">
<AddIcon />
</IconButton>
</Tooltip>
</Typography>
</Grid>
</Paper>
<div>
{this.renderTable()}
</div>
</CardContent>
</Card>
)
}
renderUiForm()
{
return this.renderForm();
}
renderMenu()
{
return (
<CustomizedMenus
anchorref={this.anchorMenuRef.current}
menuClicked = {this.menuClicked.bind(this)}
menuClosed = {this.menuClosed.bind(this)}
/>
);
}
}
export default Relays;

View file

@ -0,0 +1,106 @@
/* default menu */
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import MenuList from '@material-ui/core/MenuList';
import { green } from '@material-ui/core/colors';
import { red } from '@material-ui/core/colors';
import { blue } from '@material-ui/core/colors';
import AddCircleIcon from '@material-ui/icons/AddCircle';
import DeleteForeverIcon from '@material-ui/icons/DeleteForever';
import EditIcon from '@material-ui/icons/Edit';
import SearchIcon from '@material-ui/icons/Search';
import Typography from '@material-ui/core/Typography';
const StyledMenu = withStyles({
paper: {
border: '1px solid #d3d4d5',
},
})((props) => (
<Menu
elevation={0}
getContentAnchorEl={null}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
{...props}
/>
));
/*
const StyledMenuItem = withStyles((theme) => ({
root: {
'&:focus': {
backgroundColor: theme.palette.primary.main,
'& .MuiListItemIcon-root, & .MuiListItemText-primary': {
color: theme.palette.common.white,
},
},
},
}))(MenuItem);
*/
export default function CustomizedMenus(props) {
const [anchorEl, setAnchorEl] = React.useState(props.anchorref);
const handleClick = (event, menu) => {
setAnchorEl(event.currentTarget);
props.menuClicked(menu);
};
const handleClose = () => {
setAnchorEl(null);
props.menuClosed();
};
return (
<div>
<StyledMenu
id="customized-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuList open={Boolean(anchorEl)} id="menu-list-grow" >
<MenuItem onClick={(event)=>handleClick(event, "add")}>
<ListItemIcon>
<AddCircleIcon style={{ color: green[500] }} fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">pridať</Typography>
</MenuItem>
<MenuItem onClick={(event)=>handleClick(event, "edit")}>
<ListItemIcon>
<EditIcon style={{ color: blue[300] }} fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">editovať</Typography>
</MenuItem>
<MenuItem onClick={(event)=>handleClick(event, "delete")}>
<ListItemIcon>
<DeleteForeverIcon style={{ color: red[500] }} fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">zmazať</Typography>
</MenuItem>
<MenuItem onClick={(event)=>handleClick(event, "find")}>
<ListItemIcon>
<SearchIcon style={{ color: blue[200] }} fontSize="default" />
</ListItemIcon>
<Typography variant="inherit">hľadať</Typography>
</MenuItem>
</MenuList>
</StyledMenu>
</div>
);
}

View file

@ -0,0 +1,401 @@
import React from 'react';
import { withStyles, makeStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TablePagination from '@material-ui/core/TablePagination';
import TableRow from '@material-ui/core/TableRow';
import Grid from '@material-ui/core/Grid';
import CircularProgress from '@material-ui/core/CircularProgress';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Switch from '@material-ui/core/Switch';
import { blue } from '@material-ui/core/colors';
import TableSortLabel from '@material-ui/core/TableSortLabel';
import IconButton from '@material-ui/core/IconButton';
import EditIcon from '@material-ui/icons/Edit';
import DeleteIcon from '@material-ui/icons/Delete';
let sorted = [];
function getMaxHeight(isMobile = true)
{
let h = window.innerHeight - 250;
if(!isMobile) h = h - 40;//setDense widget
if(h < 200) h = 200;
return h;
}
const useStyles = makeStyles({
root: {
width: '100%'
},
container: {
maxHeight: getMaxHeight(),
},
});
const StyledTableCell = withStyles((theme) => ({
head: {
backgroundColor: '#6bb7fd',
color: theme.palette.common.white,
},
body: {
fontSize: 14,
},
}))(TableCell);
const StyledTableRow = withStyles((theme) => ({
root: {
'&:nth-of-type(even)': {
backgroundColor: 'gainsboro',
},
},
}))(TableRow);
function descendingComparator(a, b, orderBy) {
if (b[orderBy] < a[orderBy]) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
return 1;
}
return 0;
}
function getComparator(order, orderBy) {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
function stableSort(array, comparator) {
const stabilizedThis = array.map((el, index) => [el, index]);
stabilizedThis.sort((a, b) => {
const order = comparator(a[0], b[0]);
if (order !== 0) return order;
return a[1] - b[1];
});
return stabilizedThis.map((el) => el[0]);
}
export default function StickyHeadTable(props) {
const classes = useStyles();
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10);
const [order, setOrder] = React.useState('asc');
const [orderBy, setOrderBy] = React.useState('id');
const [dense, setDense] = React.useState(props.dense);
const [selectedRows, setSelectedRows] = React.useState([]);
const [lastIndex, setLastIndex] = React.useState(-1);
const [maxHeight, setMaxHeight] = React.useState(getMaxHeight(props.isMobile));
const [selectableText, setSelectableText] = React.useState("none");
const resizeFunction = () => {
setMaxHeight(getMaxHeight(props.isMobile));
};
React.useEffect(() => {
window.addEventListener("resize", resizeFunction);
window.addEventListener("orientationchange", resizeFunction);
// Specify how to clean up after this effect:
return function cleanup() {
window.removeEventListener("resize", resizeFunction);
window.removeEventListener("orientationchange", resizeFunction);
};
}, []);
const handleChangeDense = (event) => {
setDense(event.target.checked);
if(props.handleChangeDense !== undefined)
{
props.handleChangeDense(dense);
}
};
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(+event.target.value);
setPage(0);
};
const actionStyle={
whiteSpace: "nowrap",
textOverflow: "ellipsis",
width: "70px",
display: "block",
overflow: "hidden"
}
const showDataFromColumns = (row) => {
let result = [];
if(props.columns.length > 0){
for (let column of props.columns) {
if(!props.visibleColumns.includes(column)) continue;
if(column === "actions")
{
result.push(<TableCell style = {{maxWidth: "30px"}} key={row.id + "_" + column}>
<div style = {actionStyle}>
<IconButton aria-label="expand row" size="small" onClick={() => handleEditIconClicked(row)}>
<EditIcon/>
</IconButton>
<IconButton aria-label="expand row" size="small" onClick={() => handleDeleteIconClicked(row)}>
<DeleteIcon/>
</IconButton>
</div>
</TableCell>);
// <IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}>
//{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
//</IconButton>
}
else if(row.hasOwnProperty(column)){
result.push(<TableCell key={row.id + "_" + column}>
{row[column]}
</TableCell>);
} else {
result.push(<TableCell key={row.id + "_" + column}>
{null}
</TableCell>);
}
}
}
return result;
}
const createSortHandler = (property) => (event) => {
handleRequestSort(event, property);
};
const handleRequestSort = (event, property) => {
const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(property);
};
const renameTableColumn = (column) =>{
if(column in props.renamedColumns) return props.renamedColumns[column];
return column;
}
const isSelected = (_index) => selectedRows.indexOf(_index) !== -1;
const handleEditIconClicked = (row) => {
let newSelected = [];
newSelected.push(row._index);
setSelectedRows(newSelected);
props.handleTableClick(newSelected);
props.handleTableDoubleClick(null, row.id, row._index)
};
const handleDeleteIconClicked = (row) => {
let newSelected = [];
newSelected.push(row._index);
setSelectedRows(newSelected);
props.handleTableClick(newSelected);
props.handleTableDeleteClick(null, row.id, row._index);
};
const handleTableClick = (event, row, index) => {
let newSelected = [];
//console.log("table - handleClick", row, row._index, index);
if (event.ctrlKey) {
const indx = selectedRows.indexOf(row._index);
newSelected = [...selectedRows];
if (indx === -1) {
newSelected.push(row._index);
}
else
{
newSelected.splice(indx, 1);
}
setLastIndex(index);
}else if (event.shiftKey) {
if(selectedRows.length === 0)
{
newSelected.push(row._index);
}
let fromValue = lastIndex;
let toValue = index;
if( fromValue > toValue )
{
fromValue = index;
toValue = lastIndex;
}
//console.log(fromValue, toValue);
newSelected = [...selectedRows];
for(let indx = fromValue; indx <= toValue; indx++)
{
let row = sorted[indx];
const t = selectedRows.indexOf(row._index);
if (t === -1) {
newSelected.push(row._index);
}
}
}else {
const indx = selectedRows.indexOf(row._index);
if (indx === -1) {
newSelected.push(row._index);
}
else
{
selectedRows.splice(indx, 1);
}
setLastIndex(index);
}
if(newSelected.length === 0) setLastIndex(index);
setSelectedRows(newSelected);
props.handleTableClick(newSelected);
}
if(props.data === undefined || props.data === null)
{
return <CircularProgress />;
}
sorted = stableSort(props.data, getComparator(order, orderBy)).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
const handleSelectText = () =>
{
selectableText == 'none' ? setSelectableText("text"): setSelectableText("none");
}
return (
<Grid item xs={12}>
<Paper className={classes.root}>
<TableContainer className={classes.container} style = {{maxHeight: maxHeight}}>
<Table stickyHeader aria-label="sticky table" style={{ tableLayout: 'auto' }} size = {dense ? 'small' : 'medium'}>
<TableHead>
<TableRow >
{Object.keys(props.columns).map((key, index) => {
let column = props.columns[key];
//use filter?
if(!props.visibleColumns.includes(column)) return null;
if(column === "actions")
{
return (
<StyledTableCell style = {{width: "50px"}} key={column}>
</StyledTableCell>
)
}
return (
<StyledTableCell
key={column}
sortDirection={orderBy === column ? order : false}
>
<TableSortLabel
active = {orderBy === column}
direction={orderBy === column ? order : 'asc'}
onClick={createSortHandler(column)}
>
{renameTableColumn(column)}
</TableSortLabel>
</StyledTableCell>
)
})
}
</TableRow>
</TableHead>
<TableBody>
{sorted.map((row, index) => {
const isItemSelected = isSelected(row._index);
let backgroundColor;
if(isItemSelected){
backgroundColor = blue[200];
}
return (
<StyledTableRow
key={row.id}
hover
role="checkbox"
tabIndex={-1}
selected={isItemSelected}
style={{backgroundColor: backgroundColor, userSelect: selectableText}}
onClick={(event) => handleTableClick(event, row, index)}
onDoubleClick={(event) => props.handleTableDoubleClick(event, row.id, row._index)}
>
{showDataFromColumns(row)}
</StyledTableRow>
)
})}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[10, 25, 100]}
component="div"
count={props.data.length}
rowsPerPage={rowsPerPage}
labelRowsPerPage='Riadkov na stránke:'
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
style={{ display:"flex" }}
/>
</Paper>
{
props.isMobile ? null : <FormControlLabel style = {{marginLeft: "5px"}}
control={<Switch checked={dense} onChange={handleChangeDense} />}
label="Kompaktné zobrazenie"
/>
}
<FormControlLabel style = {{marginLeft: "5px"}}
control={<Switch onChange={handleSelectText} />}
label="Oznacovat text ?"
/>
</Grid>
);
}

View file

@ -0,0 +1,582 @@
import React, { Component } from 'react';
import DB from 'util/db/db';
import cloneDeep from 'lodash.clonedeep'
import SqlQueryBuilder, { LogicOperator } from 'util/db/SqlQueryBuilder';
import AlertDialog from 'components/AlertDialog/AlertDialog';
import StickyHeadTable from 'components/table/table';
import CancelOutlinedIcon from '@material-ui/icons/CancelOutlined';
import { red } from '@material-ui/core/colors';
import _ from 'lodash';
import isMobile from 'util/isMobile';
import Button from '@material-ui/core/Button';
import Tooltip from '@material-ui/core/Tooltip';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import Typography from '@material-ui/core/Typography';
// card core components
//import Card from './../Card/Card';
//import CardBody from './../Card/CardBody.js';
//import CardHeader from './../Card/CardHeader.js';
import Card from '@material-ui/core/Card';
import CardBody from '@material-ui/core/CardContent';
import CardHeader from '@material-ui/core/CardHeader';
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
//-----------------------------------------------------
const initialState = {
data: null,
columns: [],
visibleColumns: [],
usePagination: true,
index: null,
deleteConfirmed: false,
showFindDialog: false,
showFilter: false,
showAlert: false,
alertTitle: "",
alertType: "warning",
alertContent: "",
};
class TableDb extends React.Component {
static TABLE_STATUS = {
EDIT: 'EDIT',
ADD: 'ADD',
SHOWTABLE: 'SHOWTABLE',
}
constructor(props) {
super(props);
this.state = initialState;
this.state.status = TableDb.TABLE_STATUS.SHOWTABLE;
this.db = new DB();
this.dbTable = "";
this.selectedRows = [];//list of indexes
this.showFormInDialog = false;
this.fullscreen = false;
this.id = null;
this.index = null;
this.anchorMenuRef = React.createRef();
if(props.isMobile !== undefined) this.setDense(!props.isMobile);
else this.setDense(!isMobile());
}
display(type)
{
//to prevent rendering when add/edit is finished
if(type === "EDIT")
{
//if(this.state.rowID > 0) return null;
//if(this.state.status === "EDIT") return null;
if(this.state.status === TableDb.TABLE_STATUS.ADD) return null;
if(this.state.status === TableDb.TABLE_STATUS.EDIT) return null;
return "none";
}
if(type === "TABLE")
{
//if(this.state.status === "EDIT") return "none";
if(this.state.status === TableDb.TABLE_STATUS.ADD) return "none";
if(this.state.status === TableDb.TABLE_STATUS.EDIT) return "none";
return null;
//if(this.state.rowID > 0) return "none";
//return null;
}
}
setDense(dense)
{
this.dense = dense;
}
setStatus(status)
{
this.setState({"status": status});
}
getStatus()
{
if(this.state.status === TableDb.TABLE_STATUS.EDIT || this.state.status === TableDb.TABLE_STATUS.ADD) return "EDIT";
else return "SHOWTABLE";
}
renderForm()
{
return <h1>Editacia riadku</h1>;
}
renderTable()
{
//console.log(this.state.data);
//alert("render table");
return(
<StickyHeadTable
data={this.state.data}
columns={this.state.columns}
visibleColumns={this.state.visibleColumns}
renamedColumns={this.renamedColumns}
isMobile = {this.state.isMobile}
pagination = {this.state.usePagination}
handleTableDoubleClick = {this.handleTableDoubleClick.bind(this)}
handleTableDeleteClick = {this.handleTableDeleteClick.bind(this)}
handleBack={this.handleBack}
handleChangeDense = {this.setDense.bind(this)}
dense = {this.dense}
handleTableClick={this.handleTableClick.bind(this)}
/>
)
}
renderUiForm(status)
{
let form = this.renderForm();
let txt = "editácia";
if(this.state.status === TableDb.TABLE_STATUS.ADD) txt = "nový záznam";
//TODO set vytvorenie
return (
<Card>
<CardHeader color="success">
<Typography align="left">
<Tooltip title="návrat na hlavnú stránku" arrow>
<Button
onClick = {event => this.handleBack(event)}
startIcon={<ArrowBackIcon />}
>
{this.title}: {txt}
</Button>
</Tooltip>
</Typography>
</CardHeader>
<CardBody>
<div>
{form}
</div>
</CardBody>
</Card>
)
}
renderUiTable()
{
return(
<Card>
<CardHeader color="success">
<Tooltip title="zobraz menu" arrow>
<Button
ref={this.anchorMenuRef}
aria-controls={this.state.openMenu ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={this.handleToggleMenu.bind(this)}
startIcon={<MoreVertIcon />}
>
{this.title}
</Button>
</Tooltip>
{this.renderCancelFilterIcon()}
</CardHeader>
<CardBody>
<div>
{this.renderTable()}
</div>
</CardBody>
</Card>
)
}
renderUi()
{
let status = this.getStatus();
let form = null;
//add or edit record
if(status !== TableDb.TABLE_STATUS.SHOWTABLE)
{
form = this.renderUiForm();
}
return(
<>
<div style={{ display: this.display("EDIT") }}>
{form}
</div>
<div style={{ display: this.display("TABLE") }}>
{this.renderUiTable()}
</div>
</>
)
}
render() {
let findDialog = null;
if(this.state.showFindDialog)
{
findDialog = this.renderFindDialog();
}
return (
<div style={{ width: '100%'}}>
{this.renderUi()}
{this.renderMainMenu()}
{findDialog}
{this.renderAlertDialog()}
</div>
)
}
componentDidMount()
{
this.showTable();
}
renderCancelFilterIcon()
{
if (this.state.showFilter) return <Tooltip title="zrušiť filter" arrow><Button size="large" style={{ color: red[500] }} onClick={this.handleCancelFilter.bind(this)} startIcon={<CancelOutlinedIcon />}></Button></Tooltip>;
return null;
}
handleCancelFilter()
{
//let newState = {...this.state};
//newState.showFilter = false;
//this.setState(newState);
//this.state.showFilter = false;
this.setState(prevstate => ({ showFilter: false}));
this.showTable();
}
setRow(row)
{
}
showTable(sqlQueryBuilder)
{
if(sqlQueryBuilder === undefined) sqlQueryBuilder = this.getDefaultQueryBuilder();
//!force to show progress
this.setState(prevstate => ({ showFilter: prevstate.showFilter, data: null}));
this.db.dbSelect(sqlQueryBuilder, true).then(
result => {
result = this.db.updateJsonTypes(result, this.getTableName(true));
//console.log(result);
//modify data
for(let i = 0; i < result.length; ++i)
{
this.setRow(result[i]);
}
let newState = {...this.state};
newState.data = result;
this.setState(newState);
//this.showAlertDialog("Info", "Data boli načítané")
},
error => {
//alert(error);
this.showAlertDialog("Chyba", error);
}
);
}
getTableName(withView = false)
{
return this.dbTable;
}
getDefaultQueryBuilder()
{
let table = this.getTableName(true);
if(table === "") new Error("table name or view is not specified");
let sqlQueryBuilder = new SqlQueryBuilder();
sqlQueryBuilder.from( table );
return sqlQueryBuilder;
}
buildFindSqlQuery(findDialogParams)
{
if(_.isEmpty(findDialogParams))
{
this.setState(prevstate => ({ showFindDialog: false, showFilter: false}));
return null;
}
this.setState(prevstate => ({ showFindDialog: false, showFilter: true}));
return this.getDefaultQueryBuilder();
}
handleToggleMenu()
{
this.setState(prevstate => ({ openMenu: true}));
}
//run edit mode
handleTableDoubleClick(event, id, index){
console.log("handleTableDoubleClick:", id, index);
this.id = id;
this.index = index;
this.setStatus(TableDb.TABLE_STATUS.EDIT);
//this.setState(prevstate => ({ rowID: id, index: index}));
}
handleTableClick(selectedRows){
//console.log("tableDb::handleTableClick", selectedRows);
this.selectedRows = selectedRows;
};
handleTableDeleteClick(event, id, index){
this.id = id;
this.index = index;
this.menuClicked("delete");
}
//end of AddRecord/EditRecord
handleBack(params){
//TODO refactor it
//console.log(params, this.selectedRows);
if(params === undefined)
{
this.setState({"status": TableDb.TABLE_STATUS.SHOWTABLE});
return;
}
let index = this.index;
//if(this.selectedRows.length === 1) index = this.selectedRows[0];
if(index === undefined || index === null)
{
for(let i = 0; i < this.state.data.length; i++)
{
if(this.state.data[i]["id"] === this.id)
{
index = i;
break;
}
}
}
//TODO clone???
let newState = this.state;//{...this.state};
//let newState = {};
newState.status = TableDb.TABLE_STATUS.SHOWTABLE;
//
//update current row;
if(index !== null)
{
//newState.data = {...this.state.data[index]};
for (var prop in params) {
newState.data[index][prop] = params[prop];
}
}
else
{
//console.log("handle back - index is null");
//TODO fix get last element and it's _index!!!!!
var last_element = this.state.data[this.state.data.length - 1];
if(last_element === undefined)
{
//WHAT
//alert("tableDb.js fix");
}
else{
params["_index"] = last_element._index + 1;
newState.data.push(params);
}
newState.status = TableDb.TABLE_STATUS.SHOWTABLE;
}
this.setState(newState);
}
//reimplement it
renderMenu()
{
return null;
}
renderMainMenu()
{
if(this.anchorMenuRef == null) return null;
if(this.anchorMenuRef.current == null) return null;
if(!this.state.openMenu) return null;
return this.renderMenu();
}
renderFindDialog()
{
return null;
}
renderAlertDialog()
{
return (
<AlertDialog
open = {this.state.showAlert}
title = {this.state.alertTitle}
content = {this.state.alertContent}
onConfirm ={this.hideAlertDialog.bind(this)}
/>
)
}
hideAlertDialog()
{
this.setState({showAlert: false});
}
showAlertDialog(title, message, type)
{
if(type === undefined) type = "warning";
this.setState(prevstate => ({showAlert: true, alertTitle: title, alertContent: message, alertType: type}));
}
menuClicked(menu)
{
this.menuClosed();
if(menu === "add")
{
this.index = null;
this.id = null;
this.setState(prevstate => ({
openMenu: false,
index: null,
status: TableDb.TABLE_STATUS.ADD
}
));
}
else if(menu === "find")
{
this.setState(prevstate => ({ showFindDialog: true}));
return true;
}
else if(menu === "edit")
{
if(this.selectedRows.length === 0)
{
this.showAlertDialog("Editácia", "Nie sú označené žiadne riadky");
return;
}
else if(this.selectedRows.length > 1)
{
this.showAlertDialog("Editácia", "Označte jeden riadkok");
return;
}
let index = this.selectedRows[0];
if(index === undefined)
{
alert("menuClicked index is undefined");
return;
}
this.id = this.state.data[index].id;
this.index = index;
this.setStatus(TableDb.TABLE_STATUS.EDIT);
}
else if(menu === "delete")
{
if(this.selectedRows.length === 0)
{
this.showAlertDialog("Mazanie", "Nie sú označené žiadne riadky");
}
let data = cloneDeep(this.state.data);
data.splice(this.index, 1)
//update _index!!!
for (let i = 0; i < data.length; i++) {
data[i]._index = i;
}
//this.setState(prevstate => ({ data: this.state.data.splice(this.index, 1)}));
this.setState({data: data});
//this.showAlertDialog("Mazanie", "pripravujeme...");
return true;
}
return false;
}
deleteRow(index)
{
let data = cloneDeep(this.state.data);
data.splice(index, 1)
//update _index!!!
for (let i = 0; i < data.length; i++) {
data[i]._index = i;
}
this.setState({data: data});
}
menuClosed()
{
let newState = this.state;
newState.openMenu = false;
this.setState(newState);
//this.setState(prevstate => ({ openMenu: false}));
};
}
export default TableDb;

138
src/config/config.js Normal file
View file

@ -0,0 +1,138 @@
/*
0 - cislo registra / prikaz
1 - recepient - 0 = master, 1 = slave (slave je automaticky group a broadcast)
2 - r/rw - read/write
3- register name - nazov registra / prikazu (len pre info - zobrazenie v aplikacii)
4,5,6,7, - jednotlive byte-y - nazov byte-u
4-7 - RES. a prazdny string "" sa nezobrazia!!!
*/
//124 zaznamov
export const config = [
["0","1","R","Status","","",""],
["1","1","RW","Dimming","R-Channel ","G-Channel","B-Channel ","W - Channel"],
["2","1","RW","Device types","","","",""],
["3","1","RW","Group addresses 1-4","Groups Add. 4","Groups Add. 3","Groups Add. 2","Groups Add. 1"],
["4","1","RW","Group addresses 5-8","Groups Add. 8","Groups Add. 7","Groups Add. 6","Groups Add. 5"],
["5","1","RW","Serial number (MAC)","","","",""],
["6","1","RW","Time of dusk","HH","MM","SS","EXTRA"],
["7","1","RW","Time of dawn","HH","MM","SS","EXTRA"],
["8","1","RW","Time Schedule settings","TBD","TBD","Movement sensor","Time Schedule"],
["9","1","RW","TS1 Time point 1","HH","MM","SS","Ext. Device"],
["10","1","RW","TS1 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["11","1","RW","TS1 Time point 2","HH","MM","SS","Ext. Device"],
["12","1","RW","TS1 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["13","1","RW","TS1 Time point 3","HH","MM","SS","Ext. Device"],
["14","1","RW","TS1 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["15","1","RW","TS1 Time point 4","HH","MM","SS","Ext. Device"],
["16","1","RW","TS1 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["17","1","RW","TS1 Time point 5","HH","MM","SS","Ext. Device"],
["18","1","RW","TS1 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["19","1","RW","TS1 Time point 6","HH","MM","SS","Ext. Device"],
["20","1","RW","TS1 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["21","1","RW","TS1 Time point 7","HH","MM","SS","Ext. Device"],
["22","1","RW","TS1 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["23","1","RW","TS1 Time point 8","HH","MM","SS","Ext. Device"],
["24","1","RW","TS1 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["25","1","RW","TS1 Time point 9","HH","MM","SS","Ext. Device"],
["26","1","RW","TS1 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["27","1","RW","TS1 Time point 10","HH","MM","SS","Ext. Device"],
["28","1","RW","TS1 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["29","1","RW","TS1 Time point 11","HH","MM","SS","Ext. Device"],
["30","1","RW","TS1 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["31","1","RW","TS1 Time point 12","HH","MM","SS","Ext. Device"],
["32","1","RW","TS1 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["33","1","RW","TS1 Time point 13","HH","MM","SS","Ext. Device"],
["34","1","RW","TS1 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["35","1","RW","TS1 Time point 14","HH","MM","SS","Ext. Device"],
["36","1","RW","TS1 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["37","1","RW","TS1 Time point 15","HH","MM","SS","Ext. Device"],
["38","1","RW","TS1 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["39","1","RW","TS1 Time point 16","HH","MM","SS","Ext. Device"],
["40","1","RW","TS1 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["41","1","RW","TS2 Time point 1","HH","MM","SS","Ext. Device"],
["42","1","RW","TS2 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["43","1","RW","TS2 Time point 2","HH","MM","SS","Ext. Device"],
["44","1","RW","TS2 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["45","1","RW","TS2 Time point 3","HH","MM","SS","Ext. Device"],
["46","1","RW","TS2 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["47","1","RW","TS2 Time point 4","HH","MM","SS","Ext. Device"],
["48","1","RW","TS2 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["49","1","RW","TS2 Time point 5","HH","MM","SS","Ext. Device"],
["50","1","RW","TS2 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["51","1","RW","TS2 Time point 6","HH","MM","SS","Ext. Device"],
["52","1","RW","TS2 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["53","1","RW","TS2 Time point 7","HH","MM","SS","Ext. Device"],
["54","1","RW","TS2 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["55","1","RW","TS2 Time point 8","HH","MM","SS","Ext. Device"],
["56","1","RW","TS2 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["57","1","RW","TS2 Time point 9","HH","MM","SS","Ext. Device"],
["58","1","RW","TS2 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["59","1","RW","TS2 Time point 10","HH","MM","SS","Ext. Device"],
["60","1","RW","TS2 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["61","1","RW","TS2 Time point 11","HH","MM","SS","Ext. Device"],
["62","1","RW","TS2 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["63","1","RW","TS2 Time point 12","HH","MM","SS","Ext. Device"],
["64","1","RW","TS2 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["65","1","RW","TS2 Time point 13","HH","MM","SS","Ext. Device"],
["66","1","RW","TS2 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["67","1","RW","TS2 Time point 14","HH","MM","SS","Ext. Device"],
["68","1","RW","TS2 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["69","1","RW","TS2 Time point 15","HH","MM","SS","Ext. Device"],
["70","1","RW","TS2 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["71","1","RW","TS2 Time point 16","HH","MM","SS","Ext. Device"],
["72","1","RW","TS2 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["73","1","RW","Power meter status","TBD","","",""],
["74","1","R","Input Voltage","","","",""],
["75","1","R","Input Current","","","",""],
["76","1","R","Input Power","","","",""],
["77","1","R","Cos phi","","","",""],
["78","1","R","Frequency","","","",""],
["79","1","RW","Energy","","","",""],
["80","1","RW","Lifetime","","","",""],
["81","1","RW","Power on cycles (input)","","","",""],
["82","1","RW","Power on cycles (relay)","","","",""],
["83","1","R","Time since last power on","","","",""],
["84","1","R","Accelerometer data","","","",""],
["85","1","RW","GPS latitude","pos/neg","deg","min ","sec"],
["86","1","RW","GPS longitude","pos/neg","deg","min ","sec"],
["87","1","RW","Actual time","HH","MM","SS","RES."],
["88","1","RW","Actual date","Day of week","Day","Month","Year"],
["89","1","R","Production data 1","","","",""],
["90","1","R","Production data 2","","","",""],
["91","1","RW","Network ID","NID3","NID2","NID1","NID0"],
["95","1","RW","Actual Lux level from cabinet","RES.","RES.","HB","LB"],
["96","1","RW","Threshold lux level","Dusk HB","Dusk LB","Dawn HB","Dawn LB"],
["97","1","RW","Adjust period","Dusk HB","Dusk LB","Dawn HB","Dawn LB"],
["98","1","RW","Offset","RES.","RES.","Dusk","Dawn"],
["99","1","RW","CCT min/max range","max-H","max-L","min-H","min-L"],
["100","1","RW","DALI interface","Cmd ID","Add","Cmd","Resp"],
["101","1","RW","Module FW ver","v1","v2","v3","v4"],
["102","1","RW","Module MAC-H","unused","unused","M1","M2"],
["103","1","RW","Module MAC-L","M3","M4","M5","M6"],
["104","1","RW","Sensor timeout","","","Timeout H","Timeout L"],
["122","1","R","FW update status/control register","Byte3","Byte2","Byte1","Byte0"],
["123","1","R","FW update - data index","Byte3","Byte2","Byte1","Byte0"],
["124","1","R","FW update - data","Byte3","Byte2","Byte1","Byte0"],
["0","0","R","Status","","","",""],
["1","0","RW","Control register","RES.","RES.","RES.","init mode enable"],
["2","0","R","Device types","","","",""],
["3","0","R","Serial number (MAC)","","","",""],
["4","0","R","Production data 1","","","",""],
["5","0","R","Production data 2","","","",""],
["6","0","RW","Network ID","NID3","NID2","NID1","NID0"],
["7","0","RW","RS232 settings","param.","param.","Baudrate H","Baudrate L"],
["8","0","R","Accelerometer data","","","",""],
["9","0","RW","Module FW ver","v1","v2","v3","v4"],
["10","0","RW","Module MAC-H","unused","unused","M1","M2"],
["11","0","RW","Module MAC-L","M3","M4","M5","M6"],
["32","0","RW","FW update status/control register","Byte3","Byte2","Byte1","Byte0"],
["33","0","RW","FW update - data index","Byte3","Byte2","Byte1","Byte0"],
["34","0","RW","FW update - data","Byte3","Byte2","Byte1","Byte0"],
["125","0","RW","Debug Register","Byte3","Byte2","Byte1","Byte0"],
["126","0","RW","Network Control Register","Byte3","Byte2","Byte1","Byte0"],
["127","0","R","Network Status Register","Byte3","Byte2","Byte1","Byte0"],
["128","0","RW","Node XX Serial Number Register","SER3","SER2","SER1","SER0"],
["256","0","R","Node XX Network Status Register","","","",""]
];

770
src/controls.js vendored Normal file
View file

@ -0,0 +1,770 @@
import React, {Component} from 'react'
import Grid from '@material-ui/core/Grid';
import Select from '@material-ui/core/Select';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import Button from '@material-ui/core/Button';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import Typography from '@material-ui/core/Typography';
import TextField from '@material-ui/core/TextField';
import IconButton from '@material-ui/core/IconButton';
import Menu from '@material-ui/core/Menu';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import axios from 'axios';
import { crc8, crc16, crc32 } from 'easy-crc';
import {connect} from 'react-redux'
import * as actionTypes from './store/actions';
import * as data from './config/config';
//axios.defaults.baseURL = 'http://localhost:5000/';
function getParams()
{
let params = {};
//core rpc values
params.address = 0;//if(recipient === 0) address = 0;
params.byte1 = 0;
params.byte2 = 0;
params.byte3 = 0;
params.byte4 = 0;
params.recipient = 0;//0: Master, 1: Slave, 2: Broadcast
params.register = 0;//register number
params.rw = 0;//0: read, 1: write
//other values
//params.type = "rpc";
//params.tbname = tbname;
//params.timestamp = 0;//execution time
//params.addMinutesToTimestamp = 0;//repeat if > 0,
//params.debug = "";
return params;
}
class Controls extends Component {
constructor(props) {
super(props);
//default values
let tasks = this.buildTasks("R", 0);
let registerNumber = tasks[0].registerNumber;
this.state = {
rw: "R",
recipient: 0,
registerNumber: registerNumber,
index: 0,
anchorEl: null,
requestToHost: "",
responsefromHost: "",
tasks: tasks,
showAddress: false,
address: "",
byte1Text: "",
byte2Text: "",
byte3Text: "",
byte4Text: "",
byte1: "",
byte2: "",
byte3: "",
byte4: ""
}
}
checkForm = () =>{
let errors = [];
if(this.state.showAddress)
{
if(this.state.address === "")
{
errors.push("Address can not be empty");
}
else if (!Number.isInteger( parseInt(this.state.address) )) {
errors.push("Address is not integer");
}
}
if(errors.length > 0)
{
alert(errors.join("\n"));
return false;
}
return true;
}
//test
handleResponse = (bytes) =>
{
//local add = bit.bor(bit.lshift(bytes[1], 24), bit.lshift(bytes[2], 16), bit.lshift(bytes[3], 8), bytes[4]);
//https://www.w3schools.com/js/js_bitwise.asp
let type = "RESPONSE";
if(bytes[4] === 0) type = "RESPONSE";
else if(bytes[4] === 1) type = "ERROR";
else if(bytes[4] === 2) type = "EVENT";
else type = "UNKNOWN";
let crc = crc16('ARC', bytes.slice(0, 9));
let c1 = (crc >> 8) & 0xFF;
let c2 = crc & 0xFF;
console.log("crc", bytes, c1, c2);
//let add = (bytes[1] >> 24)
}
//test
com_generic = (adresa, rec, rw, register, name, byte1, byte2, byte3, byte4) =>{
let resp = [];
let cmd = register;
if (rw === 0)
{
cmd = cmd + 0x8000;
}
if (rec === 3)
{
resp.push(0xFF);
resp.push(0xFF);
resp.push(0xFF);
resp.push(0xFF);
resp.push( adresa & 0xFF );//band
}
else
{
resp.push( (adresa >> 24) & 0xFF);//rshift
resp.push( (adresa >> 16) & 0xFF);
resp.push( (adresa >> 8) & 0xFF);
resp.push( adresa & 0xFF );
if (rec === 2)
{
resp.push(0xFF);
}
else resp.push(0);
}
resp.push( (cmd >> 8) & 0xFF);//rshift
resp.push( cmd & 0xFF );//band
resp.push( byte1 & 0xFF );//band
resp.push( byte2 & 0xFF );//band
resp.push( byte3 & 0xFF );//band
resp.push( byte4 & 0xFF );//band
//let data = '12345';
let crc = crc16('ARC', resp);
let c1 = (crc >> 8) & 0xFF;
let c2 = crc & 0xFF;
resp.push(c1);
resp.push(c2);
console.log("checksum", crc);
console.log("resp", resp);
return resp;
}
//send DATA
handleClick = event => {
//this.com_generic(100, 1, 80, 'Control register', 0, 0, 0, 0);return;
//let bytes = [0,0,0,0,0,128,1,0,0,0,0];
//this.handleResponse(bytes);
//return;
if(!this.checkForm())
{
return;
}
const found = this.state.tasks.find(element => element.registerNumber === this.state.registerNumber);
if(found === undefined)
{
alert("Invalid register number: " + this.state.registerNumber);
return;
}
let address = parseInt(this.state.address);
if (isNaN(address)) address = 0;
let recipient = parseInt(this.state.recipient);
if (isNaN(recipient)) recipient = 0;
//master
if(recipient === 0) address = 0;
if(recipient === 2)
{
address = 0xffffffff;//Broadcast
}
let byte1 = parseInt(this.state.byte1);
if (isNaN(byte1)) byte1 = 0;
let byte2 = parseInt(this.state.byte2);
if (isNaN(byte2)) byte2 = 0;
let byte3 = parseInt(this.state.byte3);
if (isNaN(byte3)) byte3 = 0;
let byte4 = parseInt(this.state.byte4);
if (isNaN(byte4)) byte4 = 0;
let register = parseInt(this.state.registerNumber);
//Zadaný index potom prirátaš k registru 128. Takže napríklad pri indexe 1 sa vyčítava z registra 129. Pri indexe 20, by sa vyčítalo z registra 148..
if(this.state.rw === "R" && this.state.registerNumber === "128")
{
register = 128 + parseInt(this.state.index);
}
let rw = 0;//read
if(this.state.rw === "W") rw = 1;
let cmd = register;
if (rw === 0)
{
//cmd = cmd + 0x8000;
}
let paramsToSend = {
command: {
register: cmd,
name: found.registerName,
recipient: recipient,
rw: rw,
address: address,
byte1: byte1,
byte2: byte2,
byte3: byte3,
byte4: byte4,
},
username: this.props.username,
password: this.props.password,
};
//adresa, rec, cmd, name, byte3, byte2, byte1, byte0
console.log(paramsToSend.command);
//com generic is modifying cmd!!! use only for debug / info
let result = this.com_generic(
paramsToSend.command.address,
paramsToSend.command.recipient,
paramsToSend.command.rw,
paramsToSend.command.register,
'Control register',
byte1,
byte2,
byte3,
byte4
);
let registerNames = [];// = + " from " ;// + " (WRITE: " + result.join(", ") + ")";
registerNames.push(found.registerName);
if (recipient === 0)
{
registerNames.push("from");
registerNames.push("Master");
}
if (recipient === 1)
{
registerNames.push("from");
//let slave = 2*256 + result[3];
let slave = address;
registerNames.push( slave );
}
registerNames.push( "(WRITE: " + result.join(", ") + ")" );
let registerName = registerNames.join(" ");
let params = {
command: {
//registerNumber: this.state.registerNumber,
//registerName: found.registerName + " (WRITE: " + result.join(", ") + ")",
registerName: registerName,
direction: "in",
type: "command",
date: new Date()
}
}
//co zapisujeme?
this.props.addCommand(params);
let url = '/cmd';
//if(window.location.hostname === "localhost") url = "http://localhost:5000/cmd";
if(window.location.hostname === "localhost")
{
//url = "http://localhost:5000/cmd";
url = "http://10.0.0.5:5000/cmd";
}
this.setState({
requestToHost: url
});
console.log(url);
//make http request
axios(url,
{
method: 'POST',
mode: 'no-cors',
data: paramsToSend,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
//"Content-Type": "text/html;charset=UTF-8"
},
withCredentials: false,
credentials: 'same-origin',
})
.then(response => {
//HANDLE response
console.log("RESPONSE", response.data.messages);
//alert(response.data.hostname);
//multiple messages
if(Array.isArray(response.data.messages))
{
for(let i = 0; i < response.data.messages.length; i++)
{
let date = null;
let m = response.data.messages[i];
let message;
let type;
if(typeof m === 'object')
{
if ("date" in m)
{
date = new Date(m.date);
}
message = m.message;
type = m.type;
if(typeof message === 'object')
{
message = message.message;
type = message.type;
}
}
else
{
message = m;
}
let params = {
command: {
responsefromHost: response.data.hostname,
direction: "out",
type: type,
message: message,
date: date
}
}
this.props.addCommand(params);
let hostInfo = response.data.hostname;
if(hostInfo === "localhost") hostInfo = <span style = {{color: "green"}}>{hostInfo}</span>
else if(hostInfo !== "localhost") hostInfo = <span style = {{color: "red"}}>{hostInfo}</span>
this.setState({
responsefromHost: hostInfo
});
}
return;
}
let params = {
command: {
direction: "out",
type: response.data.type,
message: response.data.message,
date: new Date()
}
}
this.props.addCommand(params);
})
.catch(error => {
//http request err
const message = error + "";
let params = {
command: {
direction: "out",
type: "error",
message: message,
date: new Date()
}
}
this.props.addCommand(params);
});
};
handleClear = event => {
this.props.clearState();
};
buildTasks = (rw, recipient) =>{
let tasks = [];
for (let i = 0; i < data.config.length; i++) {
let registerNumber = data.config[i][0];
let masterSlaveFlag = data.config[i][1];
let rwFlag = data.config[i][2];
let registerName = data.config[i][3];
/*
if (rwFlag.includes(rw)) {
if (recipient === masterSlaveFlag){
tasks.push({registerNumber: registerNumber, registerName: registerName});
}
}
*/
if (rwFlag.includes(rw)) {
//master
if (recipient === 0){
if (masterSlaveFlag === "0"){
tasks.push({registerNumber: registerNumber, registerName: registerName});
}
}
else{
//slave / Broadcast 1, 2
if (masterSlaveFlag === "1"){
tasks.push({registerNumber: registerNumber, registerName: registerName});
}
}
}
}
return tasks;
}
menuItemSelected = item =>{
this.props.handleCommand(item);
this.handleMenuClose();
}
handleMenuClose = event =>{
this.setState({
anchorEl: null
});
}
handleMenuClick = event =>{
//this.anchorEl = event.currentTarget;
this.setState({
anchorEl: event.currentTarget
});
}
handleTextFieldChange = event =>{
this.setState({
[event.target.name]: event.target.value
});
}
handleChange = event => {
let name = event.target.name;
let value = event.target.value;
let rw = this.state.rw;
let recipient = this.state.recipient;
let registerNumber = this.state.registerNumber;
let obj = {
...this.state,
[name]: value,
}
if(name === "rw") rw = value;
if(name === "registerNumber") registerNumber = value;
if(name === "recipient")
{
//slave
if(value === 1) obj.showAddress = true;
else obj.showAddress = false;
recipient = value;
}
let tasks = this.buildTasks(rw, recipient);
if(name !== "registerNumber")
{
//tasks was rebuil, set first element
console.log(tasks);
if(tasks.length > 0)
{
registerNumber = tasks[0].registerNumber;
obj.registerNumber = registerNumber;
}
}
//clear bit values and set labels
if(this.state.registerNumber !== registerNumber)
{
for(let i = 0; i < 4; i++)
{
let key = "byte" + (i + 1);
obj[key] = "";
obj[key + "Text"] = "";
}
const found = data.config.find(element => element[0] === registerNumber);
if(found && rw === "W")
{
var bytes = found.slice(4, 8);
for(let i = 0; i < bytes.length; i++)
{
let key = "byte" + (i + 1);
let value = bytes[i];
if(value === "RES.") value = "";
obj[key] = "";
obj[key + "Text"] = value;
}
}
}
//console.log(obj);
obj.tasks = tasks;
this.setState(obj);
};
render() {
let MenuItemTasks = [];
for (var i = 0; i < this.state.tasks.length; i++) {
MenuItemTasks.push( <MenuItem value={this.state.tasks[i].registerNumber}>{this.state.tasks[i].registerName}</MenuItem> );
}
let address;
if(this.state.showAddress)
{
address = <TextField onChange={this.handleTextFieldChange} name = "address" value={this.state.address} style = {{display: 'flex'}} label="Address" variant="outlined" />;
}
let bytes = [];
const found = this.state.tasks.find(element => element.registerNumber === this.state.registerNumber);
if(found !== undefined && this.state.rw === "W")
{
for(i = 0; i < 4; i++)
{
let key = "byte" + (i + 1);
//byte1 MSB = data3, byte2 = data2, byte3 = data1, byte4 = data0 LSB
let byteDescription = "byte" + (i + 1);
if(i === 0) byteDescription = byteDescription + " (MSB)";
if(i === 3) byteDescription = byteDescription + " (LSB)";
let str = this.state[key + "Text"] + ": " + byteDescription;
if(str !== "")
{
let byte = <TextField name = {key} onChange={this.handleTextFieldChange} value={this.state[key]} style = {{display: 'flex'}} id="outlined-basic" label={str} variant="outlined" />;
bytes.push(<Grid item xs={12} sm={12} md={12} lg={6}>{byte}</Grid>);
}
}
}
let index;
//Node XX Serial Number Register
//console.log(this.state.registerNumber);
if(this.state.rw === "R" && this.state.registerNumber === "128")
{
let key = "index";
let str = "Index";
//console.log(this.state.registerNumber);
index = <TextField name = {key} onChange={this.handleTextFieldChange} value={this.state[key]} style = {{display: 'flex'}} id="outlined-basic" label={str} variant="outlined" />;
index = <Grid item xs={12} sm={12} md={12} lg={12}>{index}</Grid>
}
return (
<div ref={ (divElement) => { this.divElement = divElement } }>
<Card variant="outlined">
<CardContent>
<IconButton
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
onClick={this.handleMenuClick}
>
<MoreVertIcon />
</IconButton>
<Menu
id="simple-menu"
anchorEl={this.state.anchorEl}
keepMounted
open={Boolean(this.state.anchorEl)}
onClose={this.handleMenuClose}
>
<MenuItem onClick={ () =>this.menuItemSelected('settings')}>Nastavenia</MenuItem>
<MenuItem onClick={this.handleMenuClose}>Logout</MenuItem>
</Menu>
<IconButton
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
>
{this.props.title}
</IconButton>
<Typography color="textSecondary">
</Typography>
<>
<Grid container spacing={1}>
<Grid item xs={12} sm={12} md={6} lg={4}>
<InputLabel id="rw-select-label">Read/Write</InputLabel>
<Select
style = {{display: 'flex'}}
labelId="rw-select-label"
id="rw"
name="rw"
value = {this.state.rw}
//value={this.state.doba_uveru_v_mesiacoch}
onChange={this.handleChange}
>
<MenuItem value={"R"}>Read</MenuItem>
<MenuItem value={"W"}>Write</MenuItem>
</Select>
</Grid>
<Grid item xs={12} sm={12} md={6} lg={4}>
<InputLabel id="recipient-select-label">Recip.</InputLabel>
<Select
labelId="recipient-select-label"
id="recipient"
name ="recipient"
value = {this.state.recipient}
style = {{display: 'flex'}}
onChange={this.handleChange}
>
<MenuItem value={0}>Master</MenuItem>
<MenuItem value={1}>Slave</MenuItem>
<MenuItem value={2}>Broadcast</MenuItem>
</Select>
</Grid>
<Grid item xs={12} sm={12} md={12} lg={4}>
<InputLabel id="demo-controlled-open-select-label">Task</InputLabel>
<Select
labelId="demo-controlled-open-select-label"
style = {{display: 'flex'}}
id="registerNumber"
name="registerNumber"
value = {this.state.registerNumber}
onChange={this.handleChange}
>
{MenuItemTasks}
</Select>
</Grid>
<Grid item xs={12} sm={12} md={12} lg={12}>
Request to: {this.state.requestToHost} <br/> response from: {this.state.responsefromHost}
</Grid>
{bytes}
{index}
<Grid item xs={12} sm={12} md={12} lg={12}>
{address}
</Grid>
</Grid>
</>
</CardContent>
<CardActions>
<Button onClick = {this.handleClick} variant="contained" color="primary" style = {{display: 'flex'}} component="span">
Send
</Button>
<Button onClick = {this.handleClear} variant="contained" color="primary" style = {{display: 'flex'}} component="span">
Clear
</Button>
</CardActions>
</Card>
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return {
addCommand: (params) => dispatch ({
type: actionTypes.ADD_COMMAND,
payload: {
command: params.command
}
}),
clearState: (params) => dispatch ({
type: actionTypes.CLEAR_STATE,
})
}
}
export default connect(null, mapDispatchToProps)(Controls);

13
src/index.css Normal file
View file

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

22
src/index.js Normal file
View file

@ -0,0 +1,22 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore } from 'redux';
import { Provider } from 'react-redux'
import reducer from './store/reducer'
const store = createStore(reducer);
ReactDOM.render(
<Provider store = {store}>
<App />
</Provider>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

1
src/logo.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

108
src/output.js Normal file
View file

@ -0,0 +1,108 @@
import React, {Component} from 'react'
import {connect} from 'react-redux'
import { AnimatedList } from 'react-animated-list';
import * as actionTypes from './store/actions'
import moment from 'moment';
class Output extends Component {
constructor(props) {
super(props);
this.state = { commands: [] }
this.messagesEndRef = React.createRef();
}
componentDidMount () {
this.scrollToBottom()
}
componentDidUpdate () {
this.scrollToBottom()
}
scrollToBottom = () => {
this.messagesEndRef.current.scrollIntoView({ behavior: 'smooth' })
}
render() {
let index = 0;
let responsefromHostWasShow = true;//obsolete
const items = this.props.commands.map(item => {
let dateString = "";
if("date" in item)
{
if(item.date != null) dateString = "[" + moment(item.date).format('DD.MM.YYYY hh.mm.ss') + "] ";
}
if(item.direction === "in")
{
const container = <div key={index} style={{color: "white", backgroundColor: '#484848', width: "100%", padding: "5px"}}>[{dateString}] COMMAND: {item.registerName}</div>;
index++;
return container;
}
else if (item.direction === "out")
{
//RESPONSE, ERROR, EVENT, UNKNOWN
let color = '#95FE35';//RESPONSE
if(item.type === "ERROR") color = 'red';
let responsefromHost;
if(!responsefromHostWasShow)
{
responsefromHost = (<div key={index} style={{color: "#0066ff", backgroundColor: '#00ffff', width: "100%", padding: "5px"}}><b>RESPONSE FROM HOST: {item.responsefromHost}</b></div>);
responsefromHostWasShow = true;
}
const container = (
<>
{responsefromHost}
<div key={index} style={{color: "yellow", backgroundColor: '#484848', width: "100%", padding: "5px"}}>
{dateString}
<span style = {{color: color}}>RESPONSE:</span> {item.message}
</div>
</>
);
index++;
return container;
}
})
return (
<>
<AnimatedList animation={"grow"}>
{items}
</AnimatedList>
<div ref={this.messagesEndRef} />
</>
);
}
}
const mapDispatchToProps = dispatch => {
return {
addCommand: (params) => dispatch ({
type: actionTypes.ADD_COMMAND,
payload: {
command: params.command
}
}),
clearState: (params) => dispatch ({
type: actionTypes.CLEAR_STATE,
})
}
}
const mapStateToProps = state => {
return{
commands: state.commands,
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Output);

13
src/reportWebVitals.js Normal file
View file

@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

5
src/setupTests.js Normal file
View file

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

2
src/store/actions.js Normal file
View file

@ -0,0 +1,2 @@
export const ADD_COMMAND = 'ADD_COMMAND';
export const CLEAR_STATE = 'CLEAR_STATE';

23
src/store/reducer.js Normal file
View file

@ -0,0 +1,23 @@
import * as actionTypes from './actions';
const initialState = {
commands: [],
}
const reducer = (state = initialState, action) =>{
//console.log(action.type, action.payload );
if(action.type === actionTypes.ADD_COMMAND){
return {
...state,
commands: state.commands.concat(action.payload.command)
}
}
if(action.type === actionTypes.CLEAR_STATE){
return {commands: []};
}
return state;
};
export default reducer;

View file

@ -0,0 +1,248 @@
export const LogicOperator = {
UndefOperator: '',
And: "AND",
Or: "OR"
}
export const Function = {
UndefFn: 'UndefFn',
Min: "Min",
Max: "Max",
Avg: "Avg"
}
class SqlQueryBuilder{
className = "SqlQueryBuilder";
type = "SELECT";
queryData = {
columns: [],
tables: [],
wheres: [],
variables: {}
};
select(column, alias, fn)
{
/*
SqlQueryData::Column col;
col.column = column;
col.alias = alias;
col.func = fn;
queryData.columns.append(col);
return this;
*/
if(Array.isArray(column))
{
for(let i = 0; i < column.length; i++)
{
let c = column[i];
let col;
let alias;
let fn;
if(typeof c == "string") col = c;
if(col === undefined) continue;
this.select(col, alias, fn);
}
return;
}
const SqlQueryData_Column = {
column: column,
alias: alias,
func: fn
};
this.queryData.columns.push(SqlQueryData_Column);
return this;
}
from(table, alias)
{
const SqlQueryData_Table = {
table: table,
alias: alias
};
this.queryData.tables.push(SqlQueryData_Table);
return this;
}
where(cond, op = LogicOperator.And)
{
//queryBuilderTmp->where("`date` >= :date1", SqlQueryBuilder::And);
if(LogicOperator === undefined) op = LogicOperator.And;
const SqlQueryData_Where = {
cond: cond,
operation: op
};
this.queryData.wheres.push(SqlQueryData_Where);
}
setParameter(key, value)
{
this.queryData.variables[key] = value;
}
getSql()
{
let sql = this.type + " ";
if(this.queryData.tables.length === 0)
{
throw "SqlQueryBuilder::queryData.tables.length === 0";
}
// blok stlpcov
if(this.type === "SELECT")
{
if(this.queryData.columns.length === 0) sql = sql + "*";
}
let params = [];
for(let i = 0; i < this.queryData.columns.length; i++)
{
let col = this.queryData.columns[i];
let column = col.column;
params.push("`" + column + "`");
}
sql = sql + params.join(',');
sql = sql + " FROM ";
params = [];
for(let i = 0; i < this.queryData.tables.length; i++)
{
let table = this.queryData.tables[i];
params.push("`" + table.table + "`");
}
sql = sql + params.join(',');
//WHERE
if(this.queryData.wheres.length > 0)
{
let size = this.queryData.wheres.length;
sql = sql + " WHERE ";
for(let i = 0; i < size; i++)
{
let where = this.queryData.wheres[i];
sql = sql + where.cond;
if (where.operation !== LogicOperator.UndefOperator)
{
if(i < (size - 1))
{
if (where.operation === LogicOperator.And)
{
sql = sql + " AND ";
}
else if (where.operation === LogicOperator.Or)
{
sql = sql + " OR ";
}
}
}
}
}
return sql;
}
serializeToJson()
{
return JSON.stringify({
className: "SqlQueryBuilder",
type: this.type,
queryData: this.queryData
});
}
//SqlQueryBuilder* select(const QString &column, const QString &alias = QString(), Function fn = UndefFn);
//SqlQueryBuilder* from(const QString &table, const QString &alias = QString());
//SqlQueryBuilder* where(const QString &cond, LogicOperator op = UndefOperator);
//SqlQueryBuilder* join(const QString &joinWhat, const QString &cond = QString(), JoinType type = LeftJoin);
//SqlQueryBuilder* orderBy(const QString &name);
//SqlQueryBuilder* groupBy(const QString &name);
//SqlQueryBuilder* limit(int count);
//SqlQueryBuilder* limit(int from, int to);
//SqlQueryBuilder* ascend(Ascend asc = Ascending);
//SqlQueryBuilder* setParameters(const QMap<QString, QVariant> &params);
//void clearParameters();
//QMap<QString, QVariant> getParameters();
//SqlQueryBuilder* setParameter(const QString &key, const QVariant &value);
//SqlQueryBuilder* removeParameter(const QString &key);
//void clear();
//void clear(SqlPart);
//QString getSql(bool showError = true) const;
/**
* \brief Query data storage,
*/
/*
struct SqlQueryData
{
SqlQueryData() = default;
SqlQueryData(const SqlQueryData& other)
{
hasLimit = other.hasLimit;
asc = other.asc;
columns = other.columns;
tables = other.tables;
wheres = other.wheres;
joins = other.joins;
orderByes = other.orderByes;
groupByes = other.groupByes;
connectionName = other.connectionName;
variables = other.variables;
}
struct Column { QString column, alias; SqlQueryBuilder::Function func; };
struct Table { QString table, alias; };
struct Join { SqlQueryBuilder::JoinType type; QString cond, what; };
struct Where { QString cond; SqlQueryBuilder::LogicOperator operation; };
struct Limit { bool range; int from, to, count; } limit;
bool hasLimit = false;
SqlQueryBuilder::Ascend asc;
QList<Column> columns;
QList<Table> tables;
QList<Where> wheres;
QList<Join> joins;
QStringList orderByes;
QStringList groupByes;
QString connectionName = "";
QMap<QString, QVariant> variables;
} queryData;
*/
}
export default SqlQueryBuilder;

300
src/util/db/db.js Normal file
View file

@ -0,0 +1,300 @@
import axios from 'axios'
//import moment from 'moment'
class DB{
static token = "";
static company = "";
static connectionName = "";
static db_structure = "";
static backendUrl = '/backend/db_connector.php';
data = [];
transaction = false;
updateJsonTypes(arr, table)
{
let table_structure;
if(DB.db_structure !== undefined)
{
table_structure = DB.db_structure[table];
}
let result = [];
let index = 0;
for(let i = 0; i < arr.length; ++i)
{
let record = arr[i];
if(table_structure !== undefined)
{
let keys = Object.keys(record);
for(let ii = 0; ii < keys.length; ii++)
{
let key = keys[ii];
if(table_structure[key] === undefined)
{
console.log("updateJsonTypes - undefined key", table, key);
continue;
}
if(table_structure[key]["type"] === "uint" || table_structure[key]["type"] === "int")
{
record[key] = parseInt(record[key]);
}
else if(table_structure[key]["type"] === "double")
{
record[key] = parseFloat(record[key]);
}
else if(table_structure[key]["type"] === "QDateTime")
{
}
else if(table_structure[key]["type"] === "char")
{
if(table_structure[key]["length"] === 1)
{
//boolean
let value = parseInt(record[key]);
//TODO use translation
if(value === 0) record[key] = "nie";
else record[key] = "áno";
}
}
else if(table_structure[key]["type"] === "QDate")
{
let d = new Date(record[key]);
record["_" + key] = record[key];//to preserve original value
//record[key] = moment(d).format('DD.MM.YYYY');
}
}
}
else
{
console.log("no table structure provided");
}
record._index = index;
result.push(record);
index++
}
return arr;
}
dbSelect(data, exec = false, error_message = "")
{
//
//let obj = {type: "select", error_message: error_message};
//this.data.push(obj);
//data can be string, or object SqlQueryBuilder!!!
//TODO push
//or sql as string
if((typeof data) == "object")
{
if(data.className === "SqlQueryBuilder")
{
//if(error_message === undefined) error_message = "update pre tabulku " + table + " zlyhal";
//run later
if(exec === false)
{
let obj = {type: "select", className: "SqlQueryBuilder", obj: data, error_message: error_message};
this.data.push(obj);
return;
}
}
}
if((typeof data) == "string")
{
//alert("str");
}
return new Promise((resolve, reject) => {
let obj = {
token: DB.token,
company: DB.company,
connectionName: DB.connectionName,
type: "select",
data: data,
error_message: error_message
}
let json = JSON.stringify(obj);
if(DB.backendUrl === undefined || DB.backendUrl === "")
{
reject("Nie je definovaný endpoint");
}
axios(DB.backendUrl,
{
method: 'POST',
mode: 'no-cors',
data: json,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
},
withCredentials: false,
credentials: 'same-origin',
})
.then(response => {
console.log(response);
let success = response.data.success;
if(!success)
{
//console.log(response.data);
let message = response.data.message;
if(message === undefined) message = "nedefinovaná chyba";
throw message;
}
if(success)
{
resolve( response.data.result );
}
})
.catch(error => {
const message = error + "";
reject(message);
});
})
}
dbStartTransaction()
{
let obj = {type: "START TRANSACTION"};
this.data.push(obj);
}
dbCommitTransaction()
{
let obj = {type: "COMMIT"};
this.data.push(obj);
}
dbDelete(table, conditions, error_message)
{
if(Number.isInteger(conditions))
{
conditions = {"id": conditions};
}
if(error_message === undefined) error_message = "zlyhalo zmazanie z tabulky " + table;
let obj = {type: "delete", table: table, conditions: conditions, error_message: error_message};
this.data.push(obj);
}
dbInsert(data, table, foreign_keys, error_message) {
//console.log(data, table, condition);
//foreign key: table: lastInsertedId
//foreign_keys: {"subject_id": {"subject": "id"}}
if(foreign_keys === undefined) foreign_keys = {};
//token
if(error_message === undefined) error_message = "insert pre tabulku " + table + " zlyhal";
let obj = {type: "insert", foreign_keys: foreign_keys, table: table, params: {...data}, error_message: error_message};
this.data.push(obj);
}
dbUpdate(data, table, condition, error_message) {
//console.log(data, table, condition);
//token
if(error_message === undefined) error_message = "update pre tabulku " + table + " zlyhal";
let obj = {type: "update", table: table, params: {...data}, condition: condition, error_message: error_message};
this.data.push(obj);
}
exec()
{
//build json and send to server
return new Promise((resolve, reject) => {
let obj = {
token: DB.token,
company: DB.company,
connectionName: DB.connectionName,
data: this.data
}
let json = JSON.stringify(obj);
if(DB.backendUrl === undefined)
{
reject("Nie je definovaný endpoint");
}
axios(DB.backendUrl,
{
method: 'POST',
mode: 'no-cors',
data: json,
headers: {
'Access-Control-Allow-Origin': '*',
"Content-Type": "text/html;charset=UTF-8"
},
withCredentials: false,
credentials: 'same-origin',
})
.then(response => {
//console.log(response);
if(!response.data.sucess)
{
//alert(response.data.error_message);
console.log(response);
let errors = [];
if("data" in response)
{
if(Array.isArray(response.data.result))
{
for(let i = 0; i < response.data.result.length; ++i)
{
if(!response.data.result[i].sucess) errors.push(response.data.result[i].error_message);
}
}
}
throw errors;
}
if(response.data.sucess)
{
resolve(response.data.result);
}
})
.catch(error => {
const message = error + "";
console.log("catsh", message, error);
reject(message);
});
})
}
}
export default DB;

View file

@ -0,0 +1,14 @@
function findGetParameterInUrl(parameterName) {
var result = null,
tmp = [];
window.location.search
.substr(1)
.split("&")
.forEach(function (item) {
tmp = item.split("=");
if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);
});
return result;
}
export default findGetParameterInUrl

11
src/util/isLocalhost.js Normal file
View file

@ -0,0 +1,11 @@
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export default isLocalhost;

15
src/util/isMobile.js Normal file
View file

@ -0,0 +1,15 @@
function isMobile()
{
var isMobile = false; //initiate as false
// device detection
if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent)
|| /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0,4))) {
isMobile = true;
return isMobile;
}
return isMobile;
}
export default isMobile;