From 3429b49bb9ff70db11f3239c7fbbc03ac7c2e460 Mon Sep 17 00:00:00 2001 From: Pawel Kowalczyk Date: Tue, 10 Jul 2018 12:30:43 +0200 Subject: [PATCH] validation-for-dialog Feature #13694 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- .../dialog-create/dialog-project-create.tsx | 69 --------- src/utils/dialog-validator.tsx | 72 ++++++++++ .../dialog-create/dialog-project-create.tsx | 135 ++++++++++++++++++ src/views/project-panel/project-panel.tsx | 2 +- src/views/workbench/workbench.tsx | 2 +- 5 files changed, 209 insertions(+), 71 deletions(-) delete mode 100644 src/components/dialog-create/dialog-project-create.tsx create mode 100644 src/utils/dialog-validator.tsx create mode 100644 src/views-components/dialog-create/dialog-project-create.tsx diff --git a/src/components/dialog-create/dialog-project-create.tsx b/src/components/dialog-create/dialog-project-create.tsx deleted file mode 100644 index dd8c7d1f..00000000 --- a/src/components/dialog-create/dialog-project-create.tsx +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import * as React from 'react'; -import TextField from '@material-ui/core/TextField'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import { Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core'; - -interface ProjectCreateProps { - open: boolean; - handleClose: () => void; -} - -const DialogProjectCreate: React.SFC> = ({ classes, open, handleClose }) => { - return ( - -
- Create a project - - - - - - - - -
-
- ); -}; - -type CssRules = "button" | "lastButton" | "dialogContent" | "textField" | "dialog"; - -const styles: StyleRulesCallback = theme => ({ - button: { - marginLeft: theme.spacing.unit - }, - lastButton: { - marginLeft: theme.spacing.unit, - marginRight: "20px", - }, - dialogContent: { - marginTop: "20px", - }, - textField: { - marginBottom: "32px", - }, - dialog: { - minWidth: "550px", - minHeight: "320px" - } -}); - -export default withStyles(styles)(DialogProjectCreate); \ No newline at end of file diff --git a/src/utils/dialog-validator.tsx b/src/utils/dialog-validator.tsx new file mode 100644 index 00000000..1d1a9214 --- /dev/null +++ b/src/utils/dialog-validator.tsx @@ -0,0 +1,72 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from 'react'; +import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core'; + +type ValidatorProps = { + value: string, + onChange: (isValid: boolean | string) => void; + render: (hasError: boolean) => React.ReactElement; + isRequired: boolean; +}; + +interface ValidatorState { + isPatternValid: boolean; + isLengthValid: boolean; +} + +const nameRegEx = /^[a-zA-Z0-9-_ ]+$/; +const maxInputLength = 60; + +class Validator extends React.Component> { + state: ValidatorState = { + isPatternValid: true, + isLengthValid: true + }; + + componentWillReceiveProps(nextProps: ValidatorProps) { + const { value } = nextProps; + + if (this.props.value !== value) { + this.setState({ + isPatternValid: value.match(nameRegEx), + isLengthValid: value.length < maxInputLength + }, () => this.onChange()); + } + } + + onChange() { + const { value, onChange, isRequired } = this.props; + const { isPatternValid, isLengthValid } = this.state; + const isValid = value && isPatternValid && isLengthValid && (isRequired || (!isRequired && value.length > 0)); + + onChange(isValid); + } + + render() { + const { classes, isRequired, value } = this.props; + const { isPatternValid, isLengthValid } = this.state; + + return ( + + {this.props.render(!(isPatternValid && isLengthValid) && (isRequired || (!isRequired && value.length > 0)))} + {!isPatternValid && (isRequired || (!isRequired && value.length > 0)) ? This field allow only alphanumeric characters, dashes, spaces and underscores.
: null} + {!isLengthValid ? This field should have max 60 characters. : null} +
+ ); + } +} + +type CssRules = "formInputError"; + +const styles: StyleRulesCallback = theme => ({ + formInputError: { + color: "#ff0000", + marginLeft: "5px", + fontSize: "11px", + } +}); + +export default withStyles(styles)(Validator); \ No newline at end of file diff --git a/src/views-components/dialog-create/dialog-project-create.tsx b/src/views-components/dialog-create/dialog-project-create.tsx new file mode 100644 index 00000000..4cdf7468 --- /dev/null +++ b/src/views-components/dialog-create/dialog-project-create.tsx @@ -0,0 +1,135 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from 'react'; +import TextField from '@material-ui/core/TextField'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import { Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core'; + +import Validator from '../../utils/dialog-validator'; + +interface ProjectCreateProps { + open: boolean; + handleClose: () => void; +} + +interface DialogState { + name: string; + description: string; + isNameValid: boolean; + isDescriptionValid: boolean; +} + +class DialogProjectCreate extends React.Component> { + state: DialogState = { + name: '', + description: '', + isNameValid: true, + isDescriptionValid: true + }; + + render() { + const { name, description } = this.state; + const { classes, open, handleClose } = this.props; + + return ( + +
+ Create a project + + this.isNameValid(e)} + isRequired={true} + render={hasError => + this.handleProjectName(e)} + label="Project name" + error={hasError} + fullWidth />} /> + this.isDescriptionValid(e)} + isRequired={false} + render={hasError => + this.handleDescriptionValue(e)} + label="Description - optional" + error={hasError} + fullWidth />} /> + + + + + +
+
+ ); + } + + handleProjectName(e: any) { + this.setState({ + name: e.target.value, + }); + } + + handleDescriptionValue(e: any) { + this.setState({ + description: e.target.value, + }); + } + + isNameValid(value: boolean | string) { + this.setState({ + isNameValid: value, + }); + } + + isDescriptionValid(value: boolean | string) { + this.setState({ + isDescriptionValid: value, + }); + } +} + +type CssRules = "button" | "lastButton" | "dialogContent" | "textField" | "dialog" | "dialogTitle" | "dialogActions"; + +const styles: StyleRulesCallback = theme => ({ + button: { + marginLeft: theme.spacing.unit + }, + lastButton: { + marginLeft: theme.spacing.unit, + marginRight: "20px", + }, + dialogContent: { + marginTop: "20px", + }, + dialogTitle: { + paddingBottom: "0" + }, + dialogActions: { + marginBottom: "5px" + }, + textField: { + marginTop: "32px", + }, + dialog: { + minWidth: "600px", + minHeight: "320px" + } +}); + +export default withStyles(styles)(DialogProjectCreate); \ No newline at end of file diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx index f1b82357..b7c8b954 100644 --- a/src/views/project-panel/project-panel.tsx +++ b/src/views/project-panel/project-panel.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { ProjectPanelItem } from './project-panel-item'; -import { Grid, Typography, Button, Toolbar, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core'; +import { Grid, Typography, Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core'; import { formatDate, formatFileSize } from '../../common/formatters'; import DataExplorer from "../../views-components/data-explorer/data-explorer"; import { DispatchProp, connect } from 'react-redux'; diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index 9c1336c0..3ca9acc2 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -29,7 +29,7 @@ import DetailsPanel from '../../views-components/details-panel/details-panel'; import { ArvadosTheme } from '../../common/custom-theme'; import ContextMenu, { ContextMenuAction } from '../../components/context-menu/context-menu'; import { mockAnchorFromMouseEvent } from '../../components/popover/helpers'; -import DialogProjectCreate from '../../components/dialog-create/dialog-project-create'; +import DialogProjectCreate from '../../views-components/dialog-create/dialog-project-create'; const drawerWidth = 240; const appBarHeight = 100; -- 2.30.2