diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/App.js | 259 | ||||
-rw-r--r-- | src/PatternTable.js | 121 | ||||
-rw-r--r-- | src/RegexField.js | 95 | ||||
-rw-r--r-- | src/theme.js | 18 |
4 files changed, 280 insertions, 213 deletions
@@ -4,51 +4,29 @@ import 'typeface-roboto'; import 'react-dates/initialize'; import 'react-dates/lib/css/_datepicker.css'; import { DateRangePicker } from 'react-dates'; -import { withStyles, withTheme } from '@material-ui/core/styles'; -import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'; -import orange from '@material-ui/core/colors/orange'; +import { withStyles } from '@material-ui/core/styles'; +import { MuiThemeProvider } from '@material-ui/core/styles'; import cyan from '@material-ui/core/colors/cyan'; import CssBaseline from '@material-ui/core/CssBaseline'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; -import TextField from '@material-ui/core/TextField'; import Typography from '@material-ui/core/Typography'; -import Table from '@material-ui/core/Table'; -import TableBody from '@material-ui/core/TableBody'; -import TableRow from '@material-ui/core/TableRow'; -import TableCell from '@material-ui/core/TableCell'; -import TableHead from '@material-ui/core/TableHead'; -import TablePagination from '@material-ui/core/TablePagination'; -import Paper from '@material-ui/core/Paper'; import Button from '@material-ui/core/Button'; import FormControl from '@material-ui/core/FormControl'; import FormGroup from '@material-ui/core/FormGroup'; import Grid from '@material-ui/core/Grid'; -import DeleteOutlinedIcon from '@material-ui/icons/DeleteOutlined'; import AddCircleIcon from '@material-ui/icons/AddCircle'; import IconButton from '@material-ui/core/IconButton'; import Logo from './Logo'; import * as gapi from './gapi'; import { Pattern, PatternEntry } from './pattern'; import PieChart from './Chart'; -import { CalendarField, EventField } from './RegexField'; +import PatternTable from './PatternTable'; +import theme from './theme'; -const default_chart_data = [{name: 'Work', value: 10, color: cyan[300]}, - {name: 'Wasted', value: 10, color: cyan[300]}]; - -const theme = createMuiTheme({ - palette: { - primary: { - light: orange[300], - main: orange[500], - dark: orange[700], - contrastText: "#fff" - } - }, - typography: { - useNextVariants: true, - } -}); +const default_chart_data = [ + {name: 'Work', value: 10, color: cyan[300]}, + {name: 'Wasted', value: 10, color: cyan[300]}]; function filterPatterns(patterns, calName) { return patterns.filter(p => { @@ -83,30 +61,14 @@ const styles = theme => ({ buttonSpacer: { marginBottom: theme.spacing.unit * 4, }, - patternTableWrapper: { - overflowX: 'auto', - overflowY: 'hidden' - }, - patternTable: { - minWidth: 600 - }, fab: { margin: theme.spacing.unit, }, - fieldNoRegex: { - width: 200 - }, - fieldRegex: { - marginRight: '0.5em' - } }); class Dashboard extends React.Component { state = { - open: true, patterns: [], - page: 0, - rowsPerPage: 5, timeRange: null, token: gapi.getAuthToken(), patternGraphData: default_chart_data, @@ -118,11 +80,6 @@ class Dashboard extends React.Component { calendars: {} }; - static patternHead = [ - {label: "Name", field: "name", elem: TextField}, - {label: "Calendar", field: "cal", elem: withTheme(theme)(CalendarField)}, - {label: "Event", field: 'event', elem: withTheme(theme)(EventField)}]; - handleChangePage = (event, page) => { this.setState({ page }); }; @@ -149,7 +106,7 @@ class Dashboard extends React.Component { let patterns = [PatternEntry.defaultPatternEntry(), ...this.state.patterns]; for (let i = 1; i < patterns.length; i++) patterns[i].idx = i; - this.setState({ patterns }); + this.setState({ patterns }); }; analyze = () => { @@ -219,140 +176,92 @@ class Dashboard extends React.Component { }); this.setState({ patterns: items.map((item, idx) => { return new PatternEntry(item.summary, idx, - new Pattern(item.id, false, item.summary, item.summary), - Pattern.anyPattern()); + new Pattern(item.id, false, item.summary, item.summary), + Pattern.anyPattern()); })}); }); }; render() { - const { classes } = this.props; - const { patterns, rows, rowsPerPage, page } = this.state; - const nDummy = rowsPerPage - Math.min(rowsPerPage, patterns.length - page * rowsPerPage); + const { classes } = this.props; - return ( - <MuiThemeProvider theme={theme}> - <div className={classes.root}> - <AppBar - position="absolute" - className={classes.appBar}> - <Toolbar disableGutters={!this.state.open} className={classes.toolbar}> - <Typography component="h1" variant="h6" color="inherit" noWrap className={classes.title}> - <Logo style={{width: '2em', verticalAlign: 'bottom', marginRight: '0.2em'}}/>Chromicle - </Typography> - </Toolbar> - </AppBar> - <main className={classes.content}> - <div className={classes.appBarSpacer} /> - <Grid container spacing={16}> - <CssBaseline /> - <Grid item md={6} xs={12}> - <FormControl fullWidth={true}> - <FormGroup> - <Typography variant="h6" component="h1" gutterBottom> - Event Patterns - <IconButton - style={{marginBottom: '0.12em', marginLeft: '0.5em'}} - onClick={() => this.newPattern()}><AddCircleIcon /></IconButton> - </Typography> - <div className={classes.patternTableWrapper}> - <Table className={classes.patternTable}> - <TableHead> - <TableRow>{Dashboard.patternHead.map((s, i) => (<TableCell key={i}>{s.label}</TableCell>))}</TableRow> - </TableHead> - <TableBody> - {patterns.slice(page * rowsPerPage, (page + 1) * rowsPerPage).map(p => ( - <TableRow - onMouseOver={() => this.setState({ activePattern: p.idx })} - onMouseOut={() => this.setState({ activePattern: null })}> - {Dashboard.patternHead.map(s => { - const CustomText = s.elem; - return ( - <TableCell> - <CustomText - value={p[s.field]} - cached={this.cached} - fieldStyles={{noRegex: classes.fieldNoRegex, regex: classes.fieldRegex}} - onChange={event => this.updatePattern(s.field, p.idx, event.target.value)}/> - </TableCell>)})} - <span style={(this.state.activePattern === p.idx && - { position: 'absolute', right: 0, height: 48 }) || - { display: 'none' }}> - <DeleteOutlinedIcon - style={{ height: '100%', cursor: 'pointer' }} - onClick={() => this.removePattern(p.idx)} /> - </span> - </TableRow>))} - {nDummy > 0 && ( - <TableRow style={{ height: 48 * nDummy }}> - <TableCell colSpan={Dashboard.patternHead.length} /> - </TableRow>)} - </TableBody> - </Table> - </div> - <TablePagination - rowsPerPageOptions={[5, 10, 25]} - component="div" - count={patterns.length} - rowsPerPage={rowsPerPage} - page={page} - backIconButtonProps={{'aria-label': 'Previous Page'}} - nextIconButtonProps={{'aria-label': 'Next Page'}} - onChangePage={this.handleChangePage} - onChangeRowsPerPage={this.handleChangeRowsPerPage}/> - </FormGroup> - <FormGroup> - <Typography variant="h6" component="h1" gutterBottom> - Time Range - </Typography> - <div style={{textAlign: 'center'}}> - <DateRangePicker - startDate={this.state.startDate} // momentPropTypes.momentObj or null, - startDateId="your_unique_start_date_id" // PropTypes.string.isRequired, - endDate={this.state.endDate} // momentPropTypes.momentObj or null, - endDateId="your_unique_end_date_id" // PropTypes.string.isRequired, - onDatesChange={({ startDate, endDate }) => { - //if (startDate && endDate) - // this.setState({ timeRange: [startDate.toISOString(), endDate.toISOString()]}); - this.setState({ startDate, endDate }); - }} // PropTypes.func.isRequired, - focusedInput={this.state.focusedInput} // PropTypes.oneOf([START_DATE, END_DATE]) or null, - onFocusChange={focusedInput => this.setState({ focusedInput })} // PropTypes.func.isRequired, - isOutsideRange={() => false}/> - </div> - </FormGroup> - <div className={classes.buttonSpacer} /> - <Grid container spacing={16}> - <Grid item md={6} xs={12}> - <FormGroup> - <Button variant="contained" color="primary" onClick={this.loadPatterns}>Load</Button> - </FormGroup> - </Grid> - <Grid item md={6} xs={12}> - <FormGroup> - <Button variant="contained" color="primary" onClick={this.analyze}>Analyze</Button> - </FormGroup> - </Grid> - </Grid> - </FormControl> - </Grid> - <Grid item md={6} xs={12}> - <Typography variant="h6" component="h1" gutterBottom> - Graph - </Typography> - <PieChart - patternGraphData={this.state.patternGraphData} - calendarGraphData={this.state.calendarGraphData}/> - </Grid> - </Grid> - </main> - </div> - </MuiThemeProvider>); + return ( + <MuiThemeProvider theme={theme}> + <div className={classes.root}> + <AppBar + position="absolute" + className={classes.appBar}> + <Toolbar className={classes.toolbar}> + <Typography component="h1" variant="h6" color="inherit" noWrap className={classes.title}> + <Logo style={{width: '2em', verticalAlign: 'bottom', marginRight: '0.2em'}}/>Chromicle + </Typography> + </Toolbar> + </AppBar> + <main className={classes.content}> + <div className={classes.appBarSpacer} /> + <Grid container spacing={16}> + <CssBaseline /> + <Grid item md={6} xs={12}> + <FormControl fullWidth={true}> + <FormGroup> + <Typography variant="h6" component="h1" gutterBottom> + Event Patterns + <IconButton + style={{marginBottom: '0.12em', marginLeft: '0.5em'}} + onClick={() => this.newPattern()}><AddCircleIcon /></IconButton> + </Typography> + <PatternTable patterns={this.state.patterns} cached={this.cached} /> + </FormGroup> + <FormGroup> + <Typography variant="h6" component="h1" gutterBottom> + Time Range + </Typography> + <div style={{textAlign: 'center'}}> + <DateRangePicker + startDate={this.state.startDate} + startDateId="start_date_id" + endDate={this.state.endDate} + endDateId="end_date_id" + onDatesChange={({ startDate, endDate }) => { + this.setState({ startDate, endDate }); + }} + focusedInput={this.state.focusedInput} + onFocusChange={focusedInput => this.setState({ focusedInput })} + isOutsideRange={() => false}/> + </div> + </FormGroup> + <div className={classes.buttonSpacer} /> + <Grid container spacing={16}> + <Grid item md={6} xs={12}> + <FormGroup> + <Button variant="contained" color="primary" onClick={this.loadPatterns}>Load</Button> + </FormGroup> + </Grid> + <Grid item md={6} xs={12}> + <FormGroup> + <Button variant="contained" color="primary" onClick={this.analyze}>Analyze</Button> + </FormGroup> + </Grid> + </Grid> + </FormControl> + </Grid> + <Grid item md={6} xs={12}> + <Typography variant="h6" component="h1" gutterBottom> + Graph + </Typography> + <PieChart + patternGraphData={this.state.patternGraphData} + calendarGraphData={this.state.calendarGraphData}/> + </Grid> + </Grid> + </main> + </div> + </MuiThemeProvider>); } } Dashboard.propTypes = { - classes: PropTypes.object.isRequired, + classes: PropTypes.object.isRequired, }; export default withStyles(styles)(Dashboard); diff --git a/src/PatternTable.js b/src/PatternTable.js new file mode 100644 index 0000000..001c468 --- /dev/null +++ b/src/PatternTable.js @@ -0,0 +1,121 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles, withTheme } from '@material-ui/core/styles'; +import TextField from '@material-ui/core/TextField'; +import Table from '@material-ui/core/Table'; +import TableBody from '@material-ui/core/TableBody'; +import TableRow from '@material-ui/core/TableRow'; +import TableCell from '@material-ui/core/TableCell'; +import TableHead from '@material-ui/core/TableHead'; +import TablePagination from '@material-ui/core/TablePagination'; +import DeleteOutlinedIcon from '@material-ui/icons/DeleteOutlined'; +import { CalendarField, EventField } from './RegexField'; +import theme from './theme'; + +const styles = theme => ({ + deleteButtonShow: { + position: 'absolute', + right: 0, + height: 48 + }, + deleteButtonHide: { + display: 'none' + }, + deleteIcon: { + height: '100%', + cursor: 'pointer' + }, + patternTableWrapper: { + overflowX: 'auto', + overflowY: 'hidden' + }, + patternTable: { + minWidth: 600 + } +}); + +const patternHead = [ + {label: "Name", field: "name", elem: TextField}, + {label: "Calendar", field: "cal", elem: withTheme(theme)(CalendarField)}, + {label: "Event", field: 'event', elem: withTheme(theme)(EventField)}]; + +class PatternTable extends React.Component { + state = { + page: 0, + rowsPerPage: 5, + }; + + handleChangePage(event, page) { + this.setState({ page }); + } + + handleChangeRowsPerPage(event) { + this.setState({ rowsPerPage: event.target.value }); + } + + render() { + const { classes, cached, patterns } = this.props; + const { rowsPerPage, page } = this.state; + const nDummy = rowsPerPage - Math.min(rowsPerPage, patterns.length - page * rowsPerPage); + let rows = patterns.slice(page * rowsPerPage, (page + 1) * rowsPerPage).map(p => ( + <TableRow + onMouseOver={() => this.setState({ activePattern: p.idx })} + onMouseOut={() => this.setState({ activePattern: null })}> + { + patternHead.map(s => { + const CustomText = s.elem; + return ( + <TableCell> + <CustomText + value={p[s.field]} + cached={cached} + onChange={event => this.updatePattern(s.field, p.idx, event.target.value)}/> + </TableCell>)}) + } + <span className={this.state.activePattern === p.idx ? classes.deleteButtonShow : classes.deleteButtonHide}> + <DeleteOutlinedIcon + className={classes.deleteIcon} + onClick={() => this.removePattern(p.idx)} /> + </span> + </TableRow>)); + + return ( + <div> + <div className={classes.patternTableWrapper}> + <Table className={classes.patternTable}> + <TableHead> + <TableRow>{patternHead.map((s, i) => (<TableCell key={i}>{s.label}</TableCell>))}</TableRow> + </TableHead> + <TableBody> + {rows} + { + nDummy > 0 && ( + <TableRow style={{ height: 48 * nDummy }}> + <TableCell colSpan={patternHead.length} /> + </TableRow>) + } + </TableBody> + </Table> + </div> + <TablePagination + rowsPerPageOptions={[5, 10, 25]} + component="div" + count={patterns.length} + rowsPerPage={rowsPerPage} + page={page} + backIconButtonProps={{'aria-label': 'Previous Page'}} + nextIconButtonProps={{'aria-label': 'Next Page'}} + onChangePage={this.handleChangePage} + onChangeRowsPerPage={this.handleChangeRowsPerPage} /> + </div>); + } +} + + +PatternTable.propTypes = { + classes: PropTypes.object.isRequired, + patterns: PropTypes.array.isRequired, + cached: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(PatternTable); diff --git a/src/RegexField.js b/src/RegexField.js index 104512e..926a3bc 100644 --- a/src/RegexField.js +++ b/src/RegexField.js @@ -1,54 +1,75 @@ import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; import Select from '@material-ui/core/Select'; import MenuItem from '@material-ui/core/MenuItem'; import TextField from '@material-ui/core/TextField'; import FormControl from '@material-ui/core/FormControl'; import { Pattern } from './pattern'; +const styles = theme => ({ + fieldNoRegex: { + width: 200 + }, + fieldRegex: { + marginRight: '0.5em' + } +}); + class RegexField extends React.Component { render() { - var pitems = this.props.options; - var p0 = new Pattern.emptyPattern(); + const { classes } = this.props; let items = []; + var pitems = this.props.options; + const p0 = new Pattern.emptyPattern(); pitems[p0.id] = p0; for (let id in pitems) - items.push( - <MenuItem key={id} value={id}> - {!pitems[id].isEmpty ? - pitems[id].label : - <span style={{color: this.props.theme.palette.primary.dark}}>Custom</span>} - </MenuItem>); + { + const label = !pitems[id].isEmpty ? pitems[id].label : + <span style={{color: this.props.theme.palette.primary.dark}}>Custom</span>; + items.push(<MenuItem key={id} value={id}>{label}</MenuItem>); + } + const selectOnClick = event => { + let value; + if (pitems[event.target.value].label == null) { + value = new Pattern(0, true, + this.props.value.isRegex ? + this.props.value.value : + `^${this.props.value.value}$`, null); + } else { + value = pitems[event.target.value]; + } + this.props.onChange({target: {value}}); + }; + + const regexTextOnChange = event => this.props.onChange({ + target: { value: new Pattern(0, true, event.target.value, null)}}); + + const className = this.props.value.isRegex ? classes.fieldRegex: classes.fieldNoRegex; return ( <FormControl> - <span> - <Select - value={this.props.value.id} - onChange={event => { - let value; - if (pitems[event.target.value].label == null) { - value = new Pattern(0, true, - this.props.value.isRegex ? - this.props.value.value : - `^${this.props.value.value}$`, null); - } else { - value = pitems[event.target.value]; - } - this.props.onChange({target: {value}}); - }} - className={this.props.value.isRegex ? - this.props.fieldStyles.regex : - this.props.fieldStyles.noRegex}>{items}</Select> - {this.props.value.label == null && ( - <TextField - value={this.props.value.value} - onChange={event => - this.props.onChange({target: { value: new Pattern(0, true, event.target.value, null)}})} /> - )} - </span> + <span> + <Select + value={this.props.value.id} + onChange={selectOnClick} + className={className}>{items} + </Select> + {this.props.value.label == null && ( + <TextField + value={this.props.value.value} + onChange={regexTextOnChange} /> + )} + </span> </FormControl>); } } +RegexField.propTypes = { + classes: PropTypes.object.isRequired, +}; + +const RegexFieldWithStyles = withStyles(styles)(RegexField); + export function CalendarField(props) { let options = {}; for (let id in props.cached.calendars) { @@ -57,10 +78,9 @@ export function CalendarField(props) { props.cached.calendars[id].name); } return ( - <RegexField + <RegexFieldWithStyles value={props.value} options={options} - fieldStyles={props.fieldStyles} onChange={props.onChange} theme={props.theme} />); } @@ -70,10 +90,9 @@ export function EventField(props) { let options = {}; options[any.id] = any; return ( - <RegexField + <RegexFieldWithStyles value={props.value} options={options} - fieldStyles={props.fieldStyles} onChange={props.onChange} - theme={props.theme}/>); + theme={props.theme} />); } diff --git a/src/theme.js b/src/theme.js new file mode 100644 index 0000000..3773e6e --- /dev/null +++ b/src/theme.js @@ -0,0 +1,18 @@ +import { createMuiTheme } from '@material-ui/core/styles'; +import orange from '@material-ui/core/colors/orange'; + +const theme = createMuiTheme({ + palette: { + primary: { + light: orange[300], + main: orange[500], + dark: orange[700], + contrastText: "#fff" + } + }, + typography: { + useNextVariants: true, + } +}); + +export default theme; |