This commit is contained in:
proddy
2021-05-14 12:45:57 +02:00
parent 15df0c0552
commit fec5ff3132
108 changed files with 3508 additions and 2455 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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&hellip;
</Typography>
<Typography variant="h4">Loading&hellip;</Typography>
</div>
)
}
);
};
export default FullScreenLoading;

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ const useStyles = makeStyles((theme: Theme) =>
createStyles({
content: {
padding: theme.spacing(2),
margin: theme.spacing(3),
margin: theme.spacing(3)
}
})
);

View File

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

View File

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

View File

@@ -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) {

View File

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