Add ESLint to maintain code style. Add pre-commit hook to lint prior.

ESLint is used with the standard react plugin. It detects all kinds of
issues ranging from misspells, indentation, variable-naming, etc.
A pre-commit hook is added to git. Prior commiting, ESlint will run
to validate that everything is OK and the user will have the option
to fix it.

Signed-off-by: Sudhanshu Gautam <me@sudhanshug.com>
This commit is contained in:
Sudhanshu Gautam 2019-07-21 21:27:38 +05:30
parent d30cf925b1
commit 9475f4092a
13 changed files with 1116 additions and 1036 deletions

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
src/serviceWorker.js

34
.eslintrc.yml Normal file
View file

@ -0,0 +1,34 @@
env:
browser: true
es6: true
jest: true
extends:
- 'eslint:recommended'
- 'plugin:react/recommended'
globals:
Atomics: readonly
SharedArrayBuffer: readonly
parser: 'babel-eslint'
parserOptions:
ecmaFeatures:
jsx: true
ecmaVersion: 2018
sourceType: module
plugins:
- react
settings:
react:
version: 'detect'
rules:
indent:
- error
- 2
linebreak-style:
- error
- unix
quotes:
- error
- single
semi:
- error
- always

View file

@ -24,7 +24,8 @@
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"predeploy": "yarn run build", "predeploy": "yarn run build",
"deploy": "gh-pages -d build" "deploy": "gh-pages -d build",
"lint": "eslint src/ --ext .js --fix"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"
@ -40,5 +41,12 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
},
"devDependencies": {
"babel-eslint": "^10.0.2",
"eslint": "^6.1.0",
"eslint-plugin-react": "^7.14.2",
"prop-types": "latest",
"typescript": "^3.5.3"
} }
} }

View file

@ -0,0 +1,50 @@
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
} from '@material-ui/core';
import React from 'react';
import PropTypes from 'prop-types';
function AlertDialog({open, cancelHandler, acceptHandler, text, title, cancelComponent, acceptComponent}) {
return (
<Dialog
open={open}
onClose={cancelHandler}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle
id="alert-dialog-title">{title}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{text}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={acceptHandler} color="primary">
{acceptComponent}
</Button>
<Button onClick={cancelHandler} color="secondary"
variant="contained" autoFocus>
{cancelComponent}
</Button>
</DialogActions>
</Dialog>
);
}
AlertDialog.propTypes = {
open: PropTypes.boolean,
cancelHandler: PropTypes.func,
acceptHandler: PropTypes.func,
text: PropTypes.string,
title: PropTypes.string,
cancelComponent: PropTypes.elementType,
acceptComponent: PropTypes.elementType,
};
export default AlertDialog;

View file

@ -0,0 +1,54 @@
import {InputAdornment, makeStyles, TextField} from '@material-ui/core';
import {fade} from '@material-ui/core/styles';
import SearchIcon from '@material-ui/icons/Search';
import React from 'react';
import PropTypes from 'prop-types';
const useStylesSearch = makeStyles(theme => ({
root: {
borderColor: '#e2e2e1',
overflow: 'hidden',
margin: 0,
borderRadius: 4,
transition: theme.transitions.create(['border-color', 'box-shadow']),
'&:hover': {
borderColor: fade(theme.palette.primary.main, 0.25),
},
'&$focused': {
backgroundColor: '#fff',
boxShadow: `${fade(theme.palette.primary.main, 0.25)} 0 0 0 2px`,
borderColor: theme.palette.primary.main,
},
},
focused: {},
}));
function SearchTextField(props) {
const classes = useStylesSearch();
return (
<TextField
variant="outlined"
label={
<div className="search-label">
{props.labeltext}
</div>
}
InputProps={
{
classes,
endAdornment: (
<InputAdornment position="start">
<SearchIcon className={classes.label}/>
</InputAdornment>
),
}
} {...props} />
);
}
SearchTextField.propTypes = {
labeltext: PropTypes.string,
};
export default SearchTextField;

View file

@ -0,0 +1,68 @@
import {
IconButton,
makeStyles,
Snackbar,
SnackbarContent,
} from '@material-ui/core';
import ErrorIcon from '@material-ui/icons/Error';
import CloseIcon from '@material-ui/icons/Close';
import React from 'react';
import PropTypes from 'prop-types';
const SnackBarStyles = makeStyles(theme => ({
error: {
backgroundColor: theme.palette.error.dark,
},
message: {
display: 'flex',
alignItems: 'center',
},
icon: {
marginRight: '20px',
fontSize: 20,
},
}));
function ErrorSnackBar({open, closeHandle, errorMessage}) {
const classes = SnackBarStyles();
return (
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={open}
autoHideDuration={6000}
onClose={closeHandle}
ContentProps={{
'aria-describedby': 'message-id',
}}
>
<SnackbarContent
className={classes.error}
aria-describedby="client-snackbar"
message={
<span id="client-snackbar" className={classes.message}>
<ErrorIcon className={classes.icon}/>
{errorMessage ||
'An unexpected error occurred. Please try again'}
</span>
}
action={[
<IconButton key="close" aria-label="Close" color="inherit"
onClick={closeHandle}>
<CloseIcon/>
</IconButton>,
]}
/>
</Snackbar>
);
}
ErrorSnackBar.propTypes = {
open: PropTypes.boolean,
closeHandle: PropTypes.func,
errorMessage: PropTypes.string,
};
export default ErrorSnackBar;

View file

@ -6,16 +6,9 @@ import {
CircularProgress, CircularProgress,
ClickAwayListener, ClickAwayListener,
Container, Container,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
FormControl, FormControl,
Grid, Grid,
IconButton,
Input, Input,
InputAdornment,
InputLabel, InputLabel,
List, List,
ListItem, ListItem,
@ -24,123 +17,27 @@ import {
OutlinedInput, OutlinedInput,
Paper, Paper,
Select, Select,
Snackbar,
SnackbarContent,
Tab, Tab,
Tabs, Tabs,
TextField,
Tooltip, Tooltip,
Typography, Typography,
} from '@material-ui/core'; } from '@material-ui/core';
import {fade, makeStyles} from '@material-ui/core/styles';
import SearchIcon from '@material-ui/icons/Search';
import CloudDownloadIcon from '@material-ui/icons/CloudDownload'; import CloudDownloadIcon from '@material-ui/icons/CloudDownload';
import WarningIcon from '@material-ui/icons/Warning'; import WarningIcon from '@material-ui/icons/Warning';
import BuildIcon from '@material-ui/icons/Build'; import BuildIcon from '@material-ui/icons/Build';
import CloseIcon from '@material-ui/icons/Close';
import ErrorIcon from '@material-ui/icons/Error';
import './home.scss'; import './home.scss';
import {withTranslation} from 'react-i18next'; import {withTranslation} from 'react-i18next';
import FuzzySet from 'fuzzyset.js'; import FuzzySet from 'fuzzyset.js';
import DataService from '../../services/data'; import DataService from '../../services/data';
import AlertDialog from '../../components/alert-dialog';
import ErrorSnackBar from '../../components/error-snackbar';
import SearchTextField from '../../components/device-search';
import PropTypes from 'prop-types';
const buildStatusCheckInterval = 5000; const buildStatusCheckInterval = 5000;
const useStylesSearch = makeStyles(theme => ({
root: {
borderColor: '#e2e2e1',
overflow: 'hidden',
margin: 0,
borderRadius: 4,
transition: theme.transitions.create(['border-color', 'box-shadow']),
'&:hover': {
borderColor: fade(theme.palette.primary.main, 0.25),
},
'&$focused': {
backgroundColor: '#fff',
boxShadow: `${fade(theme.palette.primary.main, 0.25)} 0 0 0 2px`,
borderColor: theme.palette.primary.main,
},
},
focused: {},
}));
const SnackBarStyles = makeStyles(theme => ({
error: {
backgroundColor: theme.palette.error.dark,
},
message: {
display: 'flex',
alignItems: 'center',
},
icon: {
marginRight: '20px',
fontSize: 20,
},
}));
function ErrorSnackBar({open, closeHandle, errorMessage}) {
const classes = SnackBarStyles();
return (
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={open}
autoHideDuration={6000}
onClose={closeHandle}
ContentProps={{
'aria-describedby': 'message-id',
}}
>
<SnackbarContent
className={classes.error}
aria-describedby="client-snackbar"
message={
<span id="client-snackbar" className={classes.message}>
<ErrorIcon className={classes.icon}/>
{errorMessage ||
'An unexpected error occurred. Please try again'}
</span>
}
action={[
<IconButton key="close" aria-label="Close" color="inherit"
onClick={closeHandle}>
<CloseIcon/>
</IconButton>,
]}
/>
</Snackbar>
);
}
function SearchTextField(props) {
const classes = useStylesSearch();
return (
<TextField
variant="outlined"
label={
<div className="search-label">
{props.labeltext}
</div>
}
InputProps={
{
classes,
endAdornment: (
<InputAdornment position="start">
<SearchIcon className={classes.label}/>
</InputAdornment>
),
}
} {...props} />
);
}
function TabContainer({children, dir}) { function TabContainer({children, dir}) {
return ( return (
<Typography component="div" dir={dir} style={{padding: '20px 0 0'}}> <Typography component="div" dir={dir} style={{padding: '20px 0 0'}}>
@ -149,33 +46,10 @@ function TabContainer({children, dir}) {
); );
} }
function AlertDialog({open, cancelHandler, acceptHandler, text, title, cancelComponent, acceptComponent}) { TabContainer.propTypes = {
return ( children: PropTypes.elementType,
<Dialog dir: PropTypes.any,
open={open} };
onClose={cancelHandler}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle
id="alert-dialog-title">{title}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{text}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={acceptHandler} color="primary">
{acceptComponent}
</Button>
<Button onClick={cancelHandler} color="secondary"
variant="contained" autoFocus>
{cancelComponent}
</Button>
</DialogActions>
</Dialog>
);
}
class Home extends React.Component { class Home extends React.Component {
@ -323,7 +197,7 @@ class Home extends React.Component {
this.basicInterface = val; this.basicInterface = val;
}; };
downloadingImageIndicatorShow = (i) => { downloadingImageIndicatorShow = () => {
this.setState({ this.setState({
downloading: true, downloading: true,
}); });
@ -367,7 +241,7 @@ class Home extends React.Component {
} }
}; };
closeConfirmBuildDialog = (v) => { closeConfirmBuildDialog = () => {
this.confirmingBuild = false; this.confirmingBuild = false;
}; };
@ -405,8 +279,8 @@ class Home extends React.Component {
request_hash); request_hash);
if (buildStatusResponse.status === 202) { if (buildStatusResponse.status === 202) {
this.checkBuildStatus = setTimeout( this.checkBuildStatus = setTimeout(
() => { async () => {
this.buildImageCheck(request_hash); await this.buildImageCheck(request_hash);
}, buildStatusCheckInterval, }, buildStatusCheckInterval,
); );
} else if (buildStatusResponse.status === 200) { } else if (buildStatusResponse.status === 200) {
@ -423,7 +297,7 @@ class Home extends React.Component {
this.closeConfirmBuildDialog(); this.closeConfirmBuildDialog();
const board = this.state.device.id; const board = this.state.device.id;
const packages = this.state.packages; const packages = this.state.packages;
const target = this.state.device.target + '/' + this.state.device.subtarget; const target = this.state.device['target'] + '/' + this.state.device['subtarget'];
const version = this.state.release; const version = this.state.release;
this.setState({ this.setState({
isBuilding: true, isBuilding: true,
@ -524,7 +398,7 @@ class Home extends React.Component {
<Paper elevation={4} className="search-results"> <Paper elevation={4} className="search-results">
<List> <List>
{ {
this.state.searchResults.map((res, index) => { this.state.searchResults.map((res) => {
return ( return (
<ListItem <ListItem
key={res} key={res}
@ -734,4 +608,8 @@ class Home extends React.Component {
} }
} }
Home.propTypes = {
t: PropTypes.func
};
export default withTranslation()(Home); export default withTranslation()(Home);

View file

@ -3,6 +3,7 @@ import axios from 'axios';
const base = 'https://cors-anywhere.herokuapp.com/https://mwarning.de/misc/json/bin'; const base = 'https://cors-anywhere.herokuapp.com/https://mwarning.de/misc/json/bin';
class DataService { class DataService {
getDevicesData = axios.get( getDevicesData = axios.get(
`${base}/overview.json`) `${base}/overview.json`)
.then(res => res.data); .then(res => res.data);
@ -34,7 +35,7 @@ class DataService {
response.isBuilt = res.status === 202 && res.data.files !== undefined; response.isBuilt = res.status === 202 && res.data.files !== undefined;
response.status = res.status; response.status = res.status;
if (response.isBuilt) { if (response.isBuilt) {
response = {...response, data: res.data} response = {...response, data: res.data};
} }
}); });
return response; return response;

1236
yarn.lock

File diff suppressed because it is too large Load diff