mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-01-26 16:49:11 +03:00
eslint
This commit is contained in:
@@ -1,27 +1,25 @@
|
||||
import React, { FC } from 'react';
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
import { Paper, Typography, Box, CssBaseline } from "@material-ui/core";
|
||||
import WarningIcon from "@material-ui/icons/Warning"
|
||||
import { Paper, Typography, Box, CssBaseline } from '@material-ui/core';
|
||||
import WarningIcon from '@material-ui/icons/Warning';
|
||||
|
||||
const styles = makeStyles(
|
||||
{
|
||||
siteErrorPage: {
|
||||
display: "flex",
|
||||
height: "100vh",
|
||||
justifyContent: "center",
|
||||
flexDirection: "column"
|
||||
},
|
||||
siteErrorPagePanel: {
|
||||
textAlign: "center",
|
||||
padding: "280px 0 40px 0",
|
||||
backgroundImage: 'url("/app/icon.png")',
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundPosition: "50% 40px",
|
||||
backgroundSize: "200px auto",
|
||||
width: "100%",
|
||||
}
|
||||
const styles = makeStyles({
|
||||
siteErrorPage: {
|
||||
display: 'flex',
|
||||
height: '100vh',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
siteErrorPagePanel: {
|
||||
textAlign: 'center',
|
||||
padding: '280px 0 40px 0',
|
||||
backgroundImage: 'url("/app/icon.png")',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: '50% 40px',
|
||||
backgroundSize: '200px auto',
|
||||
width: '100%'
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
interface ApplicationErrorProps {
|
||||
error?: string;
|
||||
@@ -33,27 +31,29 @@ const ApplicationError: FC<ApplicationErrorProps> = ({ error }) => {
|
||||
<div className={classes.siteErrorPage}>
|
||||
<CssBaseline />
|
||||
<Paper className={classes.siteErrorPagePanel} elevation={10}>
|
||||
<Box display="flex" flexDirection="row" justifyContent="center" alignItems="center" mb={2}>
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="row"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
mb={2}
|
||||
>
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Box ml={2}>
|
||||
<Typography variant="h4">
|
||||
Application error
|
||||
</Typography>
|
||||
<Typography variant="h4">Application error</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Failed to configure the application, please refresh to try again.
|
||||
</Typography>
|
||||
{error &&
|
||||
(
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
Error: {error}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
{error && (
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
Error: {error}
|
||||
</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ApplicationError;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { FC } from "react";
|
||||
import { FormControlLabel, FormControlLabelProps } from "@material-ui/core";
|
||||
import { FC } from 'react';
|
||||
import { FormControlLabel, FormControlLabelProps } from '@material-ui/core';
|
||||
|
||||
const BlockFormControlLabel: FC<FormControlLabelProps> = (props) => (
|
||||
<div>
|
||||
<FormControlLabel {...props} />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
export default BlockFormControlLabel;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Button, styled } from "@material-ui/core";
|
||||
import { Button, styled } from '@material-ui/core';
|
||||
|
||||
const ErrorButton = styled(Button)(({ theme }) => ({
|
||||
color: theme.palette.getContrastText(theme.palette.error.main),
|
||||
backgroundColor: theme.palette.error.main,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.error.dark,
|
||||
backgroundColor: theme.palette.error.dark
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { styled, Box } from "@material-ui/core";
|
||||
import { styled, Box } from '@material-ui/core';
|
||||
|
||||
const FormActions = styled(Box)(({ theme }) => ({
|
||||
marginTop: theme.spacing(1)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Button, styled } from "@material-ui/core";
|
||||
import { Button, styled } from '@material-ui/core';
|
||||
|
||||
const FormButton = styled(Button)(({ theme }) => ({
|
||||
margin: theme.spacing(0, 1),
|
||||
'&:last-child': {
|
||||
marginRight: 0,
|
||||
marginRight: 0
|
||||
},
|
||||
'&:first-child': {
|
||||
marginLeft: 0,
|
||||
marginLeft: 0
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -3,30 +3,30 @@ import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import { Typography, Theme } from '@material-ui/core';
|
||||
import { makeStyles, createStyles } from '@material-ui/styles';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => createStyles({
|
||||
fullScreenLoading: {
|
||||
padding: theme.spacing(2),
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: "100vh",
|
||||
flexDirection: "column"
|
||||
},
|
||||
progress: {
|
||||
margin: theme.spacing(4),
|
||||
}
|
||||
}));
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
fullScreenLoading: {
|
||||
padding: theme.spacing(2),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100vh',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
progress: {
|
||||
margin: theme.spacing(4)
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const FullScreenLoading = () => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<div className={classes.fullScreenLoading}>
|
||||
<CircularProgress className={classes.progress} size={100} />
|
||||
<Typography variant="h4">
|
||||
Loading…
|
||||
</Typography>
|
||||
<Typography variant="h4">Loading…</Typography>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default FullScreenLoading;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Avatar, makeStyles } from "@material-ui/core";
|
||||
import React, { FC } from "react";
|
||||
import { Avatar, makeStyles } from '@material-ui/core';
|
||||
import { FC } from 'react';
|
||||
|
||||
interface HighlightAvatarProps {
|
||||
color: string;
|
||||
@@ -13,11 +13,7 @@ const useStyles = makeStyles({
|
||||
|
||||
const HighlightAvatar: FC<HighlightAvatarProps> = (props) => {
|
||||
const classes = useStyles(props);
|
||||
return (
|
||||
<Avatar className={classes.root}>
|
||||
{props.children}
|
||||
</Avatar>
|
||||
);
|
||||
}
|
||||
return <Avatar className={classes.root}>{props.children}</Avatar>;
|
||||
};
|
||||
|
||||
export default HighlightAvatar;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import React from 'react';
|
||||
import { TextValidator, ValidatorComponentProps } from 'react-material-ui-form-validator';
|
||||
import {
|
||||
TextValidator,
|
||||
ValidatorComponentProps
|
||||
} from 'react-material-ui-form-validator';
|
||||
|
||||
import { withStyles, WithStyles, createStyles } from '@material-ui/core/styles';
|
||||
import { InputAdornment, IconButton } from '@material-ui/core';
|
||||
@@ -7,20 +10,23 @@ import { Visibility, VisibilityOff } from '@material-ui/icons';
|
||||
|
||||
const styles = createStyles({
|
||||
input: {
|
||||
"&::-ms-reveal": {
|
||||
display: "none"
|
||||
'&::-ms-reveal': {
|
||||
display: 'none'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
type PasswordValidatorProps = WithStyles<typeof styles> & Exclude<ValidatorComponentProps, "type" | "InputProps">;
|
||||
type PasswordValidatorProps = WithStyles<typeof styles> &
|
||||
Exclude<ValidatorComponentProps, 'type' | 'InputProps'>;
|
||||
|
||||
interface PasswordValidatorState {
|
||||
showPassword: boolean;
|
||||
}
|
||||
|
||||
class PasswordValidator extends React.Component<PasswordValidatorProps, PasswordValidatorState> {
|
||||
|
||||
class PasswordValidator extends React.Component<
|
||||
PasswordValidatorProps,
|
||||
PasswordValidatorState
|
||||
> {
|
||||
state = {
|
||||
showPassword: false
|
||||
};
|
||||
@@ -29,7 +35,7 @@ class PasswordValidator extends React.Component<PasswordValidatorProps, Password
|
||||
this.setState({
|
||||
showPassword: !this.state.showPassword
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, ...rest } = this.props;
|
||||
@@ -39,7 +45,7 @@ class PasswordValidator extends React.Component<PasswordValidatorProps, Password
|
||||
type={this.state.showPassword ? 'text' : 'password'}
|
||||
InputProps={{
|
||||
classes,
|
||||
endAdornment:
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="Toggle password visibility"
|
||||
@@ -48,11 +54,11 @@ class PasswordValidator extends React.Component<PasswordValidatorProps, Password
|
||||
{this.state.showPassword ? <Visibility /> : <VisibilityOff />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withStyles(styles)(PasswordValidator);
|
||||
|
||||
@@ -4,7 +4,9 @@ import { withSnackbar, WithSnackbarProps } from 'notistack';
|
||||
import { redirectingAuthorizedFetch } from '../authentication';
|
||||
|
||||
export interface RestControllerProps<D> extends WithSnackbarProps {
|
||||
handleValueChange: (name: keyof D) => (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
handleValueChange: (
|
||||
name: keyof D
|
||||
) => (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
|
||||
setData: (data: D, callback?: () => void) => void;
|
||||
saveData: () => void;
|
||||
@@ -15,16 +17,18 @@ export interface RestControllerProps<D> extends WithSnackbarProps {
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export const extractEventValue = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
export const extractEventValue = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
switch (event.target.type) {
|
||||
case "number":
|
||||
case 'number':
|
||||
return event.target.valueAsNumber;
|
||||
case "checkbox":
|
||||
case 'checkbox':
|
||||
return event.target.checked;
|
||||
default:
|
||||
return event.target.value
|
||||
return event.target.value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
interface RestControllerState<D> {
|
||||
data?: D;
|
||||
@@ -32,10 +36,15 @@ interface RestControllerState<D> {
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export function restController<D, P extends RestControllerProps<D>>(endpointUrl: string, RestController: React.ComponentType<P & RestControllerProps<D>>) {
|
||||
export function restController<D, P extends RestControllerProps<D>>(
|
||||
endpointUrl: string,
|
||||
RestController: React.ComponentType<P & RestControllerProps<D>>
|
||||
) {
|
||||
return withSnackbar(
|
||||
class extends React.Component<Omit<P, keyof RestControllerProps<D>> & WithSnackbarProps, RestControllerState<D>> {
|
||||
|
||||
class extends React.Component<
|
||||
Omit<P, keyof RestControllerProps<D>> & WithSnackbarProps,
|
||||
RestControllerState<D>
|
||||
> {
|
||||
state: RestControllerState<D> = {
|
||||
data: undefined,
|
||||
loading: false,
|
||||
@@ -43,12 +52,15 @@ export function restController<D, P extends RestControllerProps<D>>(endpointUrl:
|
||||
};
|
||||
|
||||
setData = (data: D, callback?: () => void) => {
|
||||
this.setState({
|
||||
data,
|
||||
loading: false,
|
||||
errorMessage: undefined
|
||||
}, callback);
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
data,
|
||||
loading: false,
|
||||
errorMessage: undefined
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
loadData = () => {
|
||||
this.setState({
|
||||
@@ -56,19 +68,24 @@ export function restController<D, P extends RestControllerProps<D>>(endpointUrl:
|
||||
loading: true,
|
||||
errorMessage: undefined
|
||||
});
|
||||
redirectingAuthorizedFetch(endpointUrl).then(response => {
|
||||
if (response.status === 200) {
|
||||
return response.json();
|
||||
}
|
||||
throw Error("Invalid status code: " + response.status);
|
||||
}).then(json => {
|
||||
this.setState({ data: json, loading: false })
|
||||
}).catch(error => {
|
||||
const errorMessage = error.message || "Unknown error";
|
||||
this.props.enqueueSnackbar("Problem fetching: " + errorMessage, { variant: 'error' });
|
||||
this.setState({ data: undefined, loading: false, errorMessage });
|
||||
});
|
||||
}
|
||||
redirectingAuthorizedFetch(endpointUrl)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
return response.json();
|
||||
}
|
||||
throw Error('Invalid status code: ' + response.status);
|
||||
})
|
||||
.then((json) => {
|
||||
this.setState({ data: json, loading: false });
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorMessage = error.message || 'Unknown error';
|
||||
this.props.enqueueSnackbar('Problem fetching: ' + errorMessage, {
|
||||
variant: 'error'
|
||||
});
|
||||
this.setState({ data: undefined, loading: false, errorMessage });
|
||||
});
|
||||
};
|
||||
|
||||
saveData = () => {
|
||||
this.setState({ loading: true });
|
||||
@@ -78,36 +95,47 @@ export function restController<D, P extends RestControllerProps<D>>(endpointUrl:
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}).then(response => {
|
||||
if (response.status === 200) {
|
||||
return response.json();
|
||||
}
|
||||
throw Error("Invalid status code: " + response.status);
|
||||
}).then(json => {
|
||||
this.props.enqueueSnackbar("Update successful.", { variant: 'success' });
|
||||
this.setState({ data: json, loading: false });
|
||||
}).catch(error => {
|
||||
const errorMessage = error.message || "Unknown error";
|
||||
this.props.enqueueSnackbar("Problem updating: " + errorMessage, { variant: 'error' });
|
||||
this.setState({ data: undefined, loading: false, errorMessage });
|
||||
});
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
return response.json();
|
||||
}
|
||||
throw Error('Invalid status code: ' + response.status);
|
||||
})
|
||||
.then((json) => {
|
||||
this.props.enqueueSnackbar('Update successful.', {
|
||||
variant: 'success'
|
||||
});
|
||||
this.setState({ data: json, loading: false });
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorMessage = error.message || 'Unknown error';
|
||||
this.props.enqueueSnackbar('Problem updating: ' + errorMessage, {
|
||||
variant: 'error'
|
||||
});
|
||||
this.setState({ data: undefined, loading: false, errorMessage });
|
||||
});
|
||||
};
|
||||
|
||||
handleValueChange = (name: keyof D) => (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
handleValueChange = (name: keyof D) => (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const data = { ...this.state.data!, [name]: extractEventValue(event) };
|
||||
this.setState({ data });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <RestController
|
||||
{...this.state}
|
||||
{...this.props as P}
|
||||
handleValueChange={this.handleValueChange}
|
||||
setData={this.setData}
|
||||
saveData={this.saveData}
|
||||
loadData={this.loadData}
|
||||
/>;
|
||||
return (
|
||||
<RestController
|
||||
{...this.state}
|
||||
{...(this.props as P)}
|
||||
handleValueChange={this.handleValueChange}
|
||||
setData={this.setData}
|
||||
saveData={this.saveData}
|
||||
loadData={this.loadData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,20 +8,23 @@ import { RestControllerProps } from '.';
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
loadingSettings: {
|
||||
margin: theme.spacing(0.5),
|
||||
margin: theme.spacing(0.5)
|
||||
},
|
||||
loadingSettingsDetails: {
|
||||
margin: theme.spacing(4),
|
||||
textAlign: "center"
|
||||
textAlign: 'center'
|
||||
},
|
||||
button: {
|
||||
marginRight: theme.spacing(2),
|
||||
marginTop: theme.spacing(2),
|
||||
marginTop: theme.spacing(2)
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
export type RestFormProps<D> = Omit<RestControllerProps<D>, "loading" | "errorMessage"> & { data: D };
|
||||
export type RestFormProps<D> = Omit<
|
||||
RestControllerProps<D>,
|
||||
'loading' | 'errorMessage'
|
||||
> & { data: D };
|
||||
|
||||
interface RestFormLoaderProps<D> extends RestControllerProps<D> {
|
||||
render: (props: RestFormProps<D>) => JSX.Element;
|
||||
@@ -46,7 +49,12 @@ export default function RestFormLoader<D>(props: RestFormLoaderProps<D>) {
|
||||
<Typography variant="h6" className={classes.loadingSettingsDetails}>
|
||||
{errorMessage}
|
||||
</Typography>
|
||||
<Button variant="contained" color="secondary" className={classes.button} onClick={loadData}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
className={classes.button}
|
||||
onClick={loadData}
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
content: {
|
||||
padding: theme.spacing(2),
|
||||
margin: theme.spacing(3),
|
||||
margin: theme.spacing(3)
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -4,13 +4,20 @@ import { useDropzone, DropzoneState } from 'react-dropzone';
|
||||
import { makeStyles, createStyles } from '@material-ui/styles';
|
||||
import CloudUploadIcon from '@material-ui/icons/CloudUpload';
|
||||
import CancelIcon from '@material-ui/icons/Cancel';
|
||||
import { Theme, Box, Typography, LinearProgress, Button } from '@material-ui/core';
|
||||
import {
|
||||
Theme,
|
||||
Box,
|
||||
Typography,
|
||||
LinearProgress,
|
||||
Button
|
||||
} from '@material-ui/core';
|
||||
|
||||
interface SingleUploadStyleProps extends DropzoneState {
|
||||
uploading: boolean;
|
||||
}
|
||||
|
||||
const progressPercentage = (progress: ProgressEvent) => Math.round((progress.loaded * 100) / progress.total);
|
||||
const progressPercentage = (progress: ProgressEvent) =>
|
||||
Math.round((progress.loaded * 100) / progress.total);
|
||||
|
||||
const getBorderColor = (theme: Theme, props: SingleUploadStyleProps) => {
|
||||
if (props.isDragAccept) {
|
||||
@@ -23,21 +30,25 @@ const getBorderColor = (theme: Theme, props: SingleUploadStyleProps) => {
|
||||
return theme.palette.info.main;
|
||||
}
|
||||
return theme.palette.grey[700];
|
||||
}
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => createStyles({
|
||||
dropzone: {
|
||||
padding: theme.spacing(8, 2),
|
||||
borderWidth: 2,
|
||||
borderRadius: 2,
|
||||
borderStyle: 'dashed',
|
||||
color: theme.palette.grey[700],
|
||||
transition: 'border .24s ease-in-out',
|
||||
cursor: (props: SingleUploadStyleProps) => props.uploading ? 'default' : 'pointer',
|
||||
width: '100%',
|
||||
borderColor: (props: SingleUploadStyleProps) => getBorderColor(theme, props)
|
||||
}
|
||||
}));
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
dropzone: {
|
||||
padding: theme.spacing(8, 2),
|
||||
borderWidth: 2,
|
||||
borderRadius: 2,
|
||||
borderStyle: 'dashed',
|
||||
color: theme.palette.grey[700],
|
||||
transition: 'border .24s ease-in-out',
|
||||
cursor: (props: SingleUploadStyleProps) =>
|
||||
props.uploading ? 'default' : 'pointer',
|
||||
width: '100%',
|
||||
borderColor: (props: SingleUploadStyleProps) =>
|
||||
getBorderColor(theme, props)
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
export interface SingleUploadProps {
|
||||
onDrop: (acceptedFiles: File[]) => void;
|
||||
@@ -47,26 +58,44 @@ export interface SingleUploadProps {
|
||||
progress?: ProgressEvent;
|
||||
}
|
||||
|
||||
const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, accept, uploading, progress }) => {
|
||||
const dropzoneState = useDropzone({ onDrop, accept, disabled: uploading, multiple: false });
|
||||
const SingleUpload: FC<SingleUploadProps> = ({
|
||||
onDrop,
|
||||
onCancel,
|
||||
accept,
|
||||
uploading,
|
||||
progress
|
||||
}) => {
|
||||
const dropzoneState = useDropzone({
|
||||
onDrop,
|
||||
accept,
|
||||
disabled: uploading,
|
||||
multiple: false
|
||||
});
|
||||
const { getRootProps, getInputProps } = dropzoneState;
|
||||
const classes = useStyles({ ...dropzoneState, uploading });
|
||||
|
||||
|
||||
const renderProgressText = () => {
|
||||
if (uploading) {
|
||||
if (progress?.lengthComputable) {
|
||||
return `Uploading: ${progressPercentage(progress)}%`;
|
||||
}
|
||||
return "Uploading\u2026";
|
||||
return 'Uploading\u2026';
|
||||
}
|
||||
return "Drop file or click here";
|
||||
}
|
||||
return 'Drop file or click here';
|
||||
};
|
||||
|
||||
const renderProgress = (progress?: ProgressEvent) => (
|
||||
<LinearProgress
|
||||
variant={!progress || progress.lengthComputable ? "determinate" : "indeterminate"}
|
||||
value={!progress ? 0 : progress.lengthComputable ? progressPercentage(progress) : 0}
|
||||
variant={
|
||||
!progress || progress.lengthComputable ? 'determinate' : 'indeterminate'
|
||||
}
|
||||
value={
|
||||
!progress
|
||||
? 0
|
||||
: progress.lengthComputable
|
||||
? progressPercentage(progress)
|
||||
: 0
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -74,16 +103,19 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, accept, uploadi
|
||||
<div {...getRootProps({ className: classes.dropzone })}>
|
||||
<input {...getInputProps()} />
|
||||
<Box flexDirection="column" display="flex" alignItems="center">
|
||||
<CloudUploadIcon fontSize='large' />
|
||||
<Typography variant="h6">
|
||||
{renderProgressText()}
|
||||
</Typography>
|
||||
<CloudUploadIcon fontSize="large" />
|
||||
<Typography variant="h6">{renderProgressText()}</Typography>
|
||||
{uploading && (
|
||||
<Fragment>
|
||||
<Box width="100%" p={2}>
|
||||
{renderProgress(progress)}
|
||||
</Box>
|
||||
<Button startIcon={<CancelIcon />} variant="contained" color="secondary" onClick={onCancel}>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={onCancel}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Fragment>
|
||||
@@ -91,6 +123,6 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, accept, uploadi
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default SingleUpload;
|
||||
|
||||
@@ -7,7 +7,9 @@ import { addAccessTokenParameter } from '../authentication';
|
||||
import { extractEventValue } from '.';
|
||||
|
||||
export interface WebSocketControllerProps<D> extends WithSnackbarProps {
|
||||
handleValueChange: (name: keyof D) => (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
handleValueChange: (
|
||||
name: keyof D
|
||||
) => (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
|
||||
setData: (data: D, callback?: () => void) => void;
|
||||
saveData: () => void;
|
||||
@@ -25,8 +27,8 @@ interface WebSocketControllerState<D> {
|
||||
}
|
||||
|
||||
enum WebSocketMessageType {
|
||||
ID = "id",
|
||||
PAYLOAD = "payload"
|
||||
ID = 'id',
|
||||
PAYLOAD = 'payload'
|
||||
}
|
||||
|
||||
interface WebSocketIdMessage {
|
||||
@@ -40,21 +42,32 @@ interface WebSocketPayloadMessage<D> {
|
||||
payload: D;
|
||||
}
|
||||
|
||||
export type WebSocketMessage<D> = WebSocketIdMessage | WebSocketPayloadMessage<D>;
|
||||
export type WebSocketMessage<D> =
|
||||
| WebSocketIdMessage
|
||||
| WebSocketPayloadMessage<D>;
|
||||
|
||||
export function webSocketController<D, P extends WebSocketControllerProps<D>>(wsUrl: string, wsThrottle: number, WebSocketController: React.ComponentType<P & WebSocketControllerProps<D>>) {
|
||||
export function webSocketController<D, P extends WebSocketControllerProps<D>>(
|
||||
wsUrl: string,
|
||||
wsThrottle: number,
|
||||
WebSocketController: React.ComponentType<P & WebSocketControllerProps<D>>
|
||||
) {
|
||||
return withSnackbar(
|
||||
class extends React.Component<Omit<P, keyof WebSocketControllerProps<D>> & WithSnackbarProps, WebSocketControllerState<D>> {
|
||||
constructor(props: Omit<P, keyof WebSocketControllerProps<D>> & WithSnackbarProps) {
|
||||
class extends React.Component<
|
||||
Omit<P, keyof WebSocketControllerProps<D>> & WithSnackbarProps,
|
||||
WebSocketControllerState<D>
|
||||
> {
|
||||
constructor(
|
||||
props: Omit<P, keyof WebSocketControllerProps<D>> & WithSnackbarProps
|
||||
) {
|
||||
super(props);
|
||||
this.state = {
|
||||
ws: new Sockette(addAccessTokenParameter(wsUrl), {
|
||||
onmessage: this.onMessage,
|
||||
onopen: this.onOpen,
|
||||
onclose: this.onClose,
|
||||
onclose: this.onClose
|
||||
}),
|
||||
connected: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -64,37 +77,42 @@ export function webSocketController<D, P extends WebSocketControllerProps<D>>(ws
|
||||
onMessage = (event: MessageEvent) => {
|
||||
const rawData = event.data;
|
||||
if (typeof rawData === 'string' || rawData instanceof String) {
|
||||
this.handleMessage(JSON.parse(rawData as string) as WebSocketMessage<D>);
|
||||
this.handleMessage(
|
||||
JSON.parse(rawData as string) as WebSocketMessage<D>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleMessage = (message: WebSocketMessage<D>) => {
|
||||
const { clientId, data } = this.state;
|
||||
|
||||
switch (message.type) {
|
||||
case WebSocketMessageType.ID:
|
||||
this.setState({ clientId: message.id });
|
||||
break;
|
||||
case WebSocketMessageType.PAYLOAD:
|
||||
const { clientId, data } = this.state;
|
||||
if (clientId && (!data || clientId !== message.origin_id)) {
|
||||
this.setState(
|
||||
{ data: message.payload }
|
||||
);
|
||||
this.setState({ data: message.payload });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onOpen = () => {
|
||||
this.setState({ connected: true });
|
||||
}
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
this.setState({ connected: false, clientId: undefined, data: undefined });
|
||||
}
|
||||
this.setState({
|
||||
connected: false,
|
||||
clientId: undefined,
|
||||
data: undefined
|
||||
});
|
||||
};
|
||||
|
||||
setData = (data: D, callback?: () => void) => {
|
||||
this.setState({ data }, callback);
|
||||
}
|
||||
};
|
||||
|
||||
saveData = throttle(() => {
|
||||
const { ws, connected, data } = this.state;
|
||||
@@ -106,28 +124,35 @@ export function webSocketController<D, P extends WebSocketControllerProps<D>>(ws
|
||||
saveDataAndClear = throttle(() => {
|
||||
const { ws, connected, data } = this.state;
|
||||
if (connected) {
|
||||
this.setState({
|
||||
data: undefined
|
||||
}, () => ws.json(data));
|
||||
this.setState(
|
||||
{
|
||||
data: undefined
|
||||
},
|
||||
() => ws.json(data)
|
||||
);
|
||||
}
|
||||
}, wsThrottle);
|
||||
|
||||
handleValueChange = (name: keyof D) => (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
handleValueChange = (name: keyof D) => (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const data = { ...this.state.data!, [name]: extractEventValue(event) };
|
||||
this.setState({ data });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <WebSocketController
|
||||
{...this.props as P}
|
||||
handleValueChange={this.handleValueChange}
|
||||
setData={this.setData}
|
||||
saveData={this.saveData}
|
||||
saveDataAndClear={this.saveDataAndClear}
|
||||
connected={this.state.connected}
|
||||
data={this.state.data}
|
||||
/>;
|
||||
return (
|
||||
<WebSocketController
|
||||
{...(this.props as P)}
|
||||
handleValueChange={this.handleValueChange}
|
||||
setData={this.setData}
|
||||
saveData={this.saveData}
|
||||
saveDataAndClear={this.saveDataAndClear}
|
||||
connected={this.state.connected}
|
||||
data={this.state.data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
|
||||
import { LinearProgress, Typography } from '@material-ui/core';
|
||||
|
||||
@@ -8,22 +6,27 @@ import { WebSocketControllerProps } from '.';
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
loadingSettings: {
|
||||
margin: theme.spacing(0.5),
|
||||
margin: theme.spacing(0.5)
|
||||
},
|
||||
loadingSettingsDetails: {
|
||||
margin: theme.spacing(4),
|
||||
textAlign: "center"
|
||||
textAlign: 'center'
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
export type WebSocketFormProps<D> = Omit<WebSocketControllerProps<D>, "connected"> & { data: D };
|
||||
export type WebSocketFormProps<D> = Omit<
|
||||
WebSocketControllerProps<D>,
|
||||
'connected'
|
||||
> & { data: D };
|
||||
|
||||
interface WebSocketFormLoaderProps<D> extends WebSocketControllerProps<D> {
|
||||
render: (props: WebSocketFormProps<D>) => JSX.Element;
|
||||
}
|
||||
|
||||
export default function WebSocketFormLoader<D>(props: WebSocketFormLoaderProps<D>) {
|
||||
export default function WebSocketFormLoader<D>(
|
||||
props: WebSocketFormLoaderProps<D>
|
||||
) {
|
||||
const { connected, render, data, ...rest } = props;
|
||||
const classes = useStyles();
|
||||
if (!connected || !data) {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
export { default as BlockFormControlLabel } from './BlockFormControlLabel'
|
||||
export { default as FormActions } from './FormActions'
|
||||
export { default as FormButton } from './FormButton'
|
||||
export { default as HighlightAvatar } from './HighlightAvatar'
|
||||
export { default as MenuAppBar } from './MenuAppBar'
|
||||
export { default as PasswordValidator } from './PasswordValidator'
|
||||
export { default as RestFormLoader } from './RestFormLoader'
|
||||
export { default as SectionContent } from './SectionContent'
|
||||
export { default as WebSocketFormLoader } from './WebSocketFormLoader'
|
||||
export { default as ErrorButton } from './ErrorButton'
|
||||
export { default as SingleUpload } from './SingleUpload'
|
||||
export { default as BlockFormControlLabel } from './BlockFormControlLabel';
|
||||
export { default as FormActions } from './FormActions';
|
||||
export { default as FormButton } from './FormButton';
|
||||
export { default as HighlightAvatar } from './HighlightAvatar';
|
||||
export { default as MenuAppBar } from './MenuAppBar';
|
||||
export { default as PasswordValidator } from './PasswordValidator';
|
||||
export { default as RestFormLoader } from './RestFormLoader';
|
||||
export { default as SectionContent } from './SectionContent';
|
||||
export { default as WebSocketFormLoader } from './WebSocketFormLoader';
|
||||
export { default as ErrorButton } from './ErrorButton';
|
||||
export { default as SingleUpload } from './SingleUpload';
|
||||
|
||||
export * from './RestFormLoader'
|
||||
export * from './RestController'
|
||||
export * from './RestFormLoader';
|
||||
export * from './RestController';
|
||||
|
||||
export * from './WebSocketFormLoader'
|
||||
export * from './WebSocketController'
|
||||
export * from './WebSocketFormLoader';
|
||||
export * from './WebSocketController';
|
||||
|
||||
Reference in New Issue
Block a user