diff options
author | Determinant <[email protected]> | 2019-02-16 23:37:17 -0500 |
---|---|---|
committer | Determinant <[email protected]> | 2019-02-16 23:37:17 -0500 |
commit | 8bc7527072d9d42efd90ae6a4847648c11d48def (patch) | |
tree | f55320a25a40e9cc31afbb811867cc6b8e4a7f36 /src | |
parent | f44c7b95ec85ce10bf8f4dcb1f0d6cdbd2fd0e7f (diff) |
improve Settings
Diffstat (limited to 'src')
-rw-r--r-- | src/Analyze.tsx | 20 | ||||
-rw-r--r-- | src/Settings.tsx | 170 | ||||
-rw-r--r-- | src/Snackbar.tsx | 9 | ||||
-rw-r--r-- | src/background.ts | 24 | ||||
-rw-r--r-- | src/duration.ts | 8 |
5 files changed, 132 insertions, 99 deletions
diff --git a/src/Analyze.tsx b/src/Analyze.tsx index 9339289..4e2df49 100644 --- a/src/Analyze.tsx +++ b/src/Analyze.tsx @@ -141,12 +141,11 @@ class Analyze extends React.Component<AnalyzeProps> { calendarGraphData: r.calendarGraphData }); } - reset = () => { - this.openDialog("Reset", "Are you sure to reset the patterns?").then(ans => { - if (!ans) return; - this.loadPatterns([]); - this.setState({ startDate: null, endDate: null }); - }); + reset = async () => { + let ans = this.openDialog("Reset", "Are you sure to reset the patterns?"); + if (!ans) return; + this.loadPatterns([]); + this.setState({ startDate: null, endDate: null }); } loadDefaultPatterns() { @@ -163,11 +162,10 @@ class Analyze extends React.Component<AnalyzeProps> { this.loadPatterns(patterns); } - loadDefault = () => { - this.openDialog("Load Default", "Load the calendars as patterns?").then(ans => { - if (!ans) return; - this.loadDefaultPatterns(); - }); + loadDefault = async () => { + let ans = await this.openDialog("Load Default", "Load the calendars as patterns?"); + if (!ans) return; + this.loadDefaultPatterns(); } openSnackbar(msg: string, variant: SnackbarVariant) { diff --git a/src/Settings.tsx b/src/Settings.tsx index b02cd25..c6e5a00 100644 --- a/src/Settings.tsx +++ b/src/Settings.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import classNames from 'classnames'; import { Theme, withStyles, StyleRules } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; @@ -21,12 +22,12 @@ import MenuItem from '@material-ui/core/MenuItem'; import Select from '@material-ui/core/Select'; import PatternTable from './PatternTable'; -import Snackbar from './Snackbar'; +import Snackbar, { SnackbarVariant } from './Snackbar'; import AlertDialog from './Dialog'; import * as gapi from './gapi'; import { MsgType, MsgClient } from './msg'; import { Pattern, PatternEntry, PatternEntryFlat } from './pattern'; -import { Duration, TimeUnit, TrackPeriod, TrackPeriodFlat } from './duration'; +import { DurationFlat, TrackedPeriodFlat } from './duration'; const styles = (theme: Theme): StyleRules => ({ tableHead: { @@ -42,6 +43,14 @@ const styles = (theme: Theme): StyleRules => ({ maxHeight: 400, overflowY: 'auto' }, + bottomButtons: { + marginTop: 10, + textAlign: 'right' + }, + trackedPeriodInput: { + paddingTop: 10, + paddingBottom: 10 + } }); const STableCell = withStyles(theme => ({ @@ -57,25 +66,25 @@ const CompactListItem = withStyles(theme => ({ }, }))(ListItem); -type TrackedPeriodProps = { +type TrackedPeriodInputProps = { name: string - fromDuration: Duration, - toDuration: Duration, + fromDuration: DurationFlat, + toDuration: DurationFlat, nameOnChange: (name: string) => void, - fromOnChange: (d: Duration) => void, - toOnChange: (d: Duration) => void + fromOnChange: (d: DurationFlat) => void, + toOnChange: (d: DurationFlat) => void }; -class TrackedPeriod extends React.Component<TrackedPeriodProps> { - valueOnChange = (old: Duration, onChange: (d: Duration) => void) => ( +class TrackedPeriodInput extends React.Component<TrackedPeriodInputProps> { + valueOnChange = (old: DurationFlat, onChange: (d: DurationFlat) => void) => ( (event: React.ChangeEvent<HTMLSelectElement>) => { - onChange(new Duration(event.target.value, old.unit)); + onChange({ value: event.target.value, unit: old.unit}); } ); - unitOnChange = (old: Duration, onChange: (d: Duration) => void) => ( + unitOnChange = (old: DurationFlat, onChange: (d: DurationFlat) => void) => ( (event: React.ChangeEvent<HTMLSelectElement>) => { - onChange(new Duration(old.value, event.target.value as TimeUnit)); + onChange({ value: old.value, unit: event.target.value}); } ); @@ -112,19 +121,19 @@ class TrackedPeriod extends React.Component<TrackedPeriodProps> { return ( <span> <TextField - inputProps={{ style: TrackedPeriod.styles.periodName } as React.CSSProperties} + inputProps={{ style: TrackedPeriodInput.styles.periodName } as React.CSSProperties} value={name} onChange={event => nameOnChange(event.target.value)}/>: from <TextField - error={TrackedPeriod.toValue(fromDuration.value) === null} - inputProps={{ style: TrackedPeriod.styles.periodValue } as React.CSSProperties} + error={TrackedPeriodInput.toValue(fromDuration.value) === null} + inputProps={{ style: TrackedPeriodInput.styles.periodValue } as React.CSSProperties} value={fromDuration.value} onChange={this.valueOnChange(fromDuration, fromOnChange)} /> <Select value={fromDuration.unit} onChange={this.unitOnChange(fromDuration, fromOnChange)}>{units}</Select> ago to <TextField - error={TrackedPeriod.toValue(toDuration.value) === null} - inputProps={{style: TrackedPeriod.styles.periodValue} as React.CSSProperties} + error={TrackedPeriodInput.toValue(toDuration.value) === null} + inputProps={{style: TrackedPeriodInput.styles.periodValue} as React.CSSProperties} value={toDuration.value} onChange={this.valueOnChange(toDuration, toOnChange)} /> <Select value={toDuration.unit} @@ -138,7 +147,9 @@ type SettingsProps = { classes: { tableHead: string, tableContent: string, - calendarList: string + calendarList: string, + bottomButtons: string, + trackedPeriodInput: string } }; @@ -150,9 +161,10 @@ class Settings extends React.Component<SettingsProps> { isLoggedIn: false, patterns: [] as PatternEntry[], calendars: {} as {[id: string]: gapi.GCalendarMeta}, - config: {} as { trackedPeriods: TrackPeriod[] }, + config: {} as { trackedPeriods: TrackedPeriodFlat[] }, snackBarOpen: false, snackBarMsg: 'unknown', + snackBarVariant: 'error' as SnackbarVariant, dialogOpen: false, dialogMsg: {title: '', message: ''}, calendarsLoading: false, @@ -185,9 +197,7 @@ class Settings extends React.Component<SettingsProps> { data: ['trackedPeriods'] }).then(msg => { let config = { - trackedPeriods: msg.data.trackedPeriods.map((p: TrackPeriodFlat) => ( - TrackPeriod.inflate(p) - )) + trackedPeriods: msg.data.trackedPeriods }; console.log(msg.data.trackedPeriods); this.setState({ config }); @@ -202,31 +212,28 @@ class Settings extends React.Component<SettingsProps> { this.setState({ isLoggedIn: true }); this.loadAll(true); } catch (_) { - this.handleSnackbarOpen("Failed to login!"); + this.openSnackbar("Failed to login!", 'error' as SnackbarVariant); } } handleLogout = async () => { - let ans = await this.handleDialogOpen("Logout", "Are you sure to logout?"); + let ans = await this.openDialog("Logout", "Are you sure to logout?"); if (!ans) return; try { await gapi.logout(); this.setState({ isLoggedIn: false }); } catch (_) { - this.handleSnackbarOpen("Failed to logout!"); + this.openSnackbar("Failed to logout!", 'error' as SnackbarVariant); } } toggleCalendar(id: string) { var calendars = {...this.state.calendars}; calendars[id].enabled = !calendars[id].enabled; - this.msgClient.sendMsg({ - opt: MsgType.updateCalendars, - data: calendars - }).then(() => this.setState({ calendars })); + this.setState({ calendars }); } - async loadAll(loadPatterns = false) { + async loadAll(reloadAll = false) { await new Promise(resolver => (this.setState({ calendarsLoading: true }, resolver))); let pm_colors = gapi.getAuthToken().then(gapi.getColors).then(color => color.calendar); @@ -241,19 +248,19 @@ class Settings extends React.Component<SettingsProps> { }; }); - let pms = [this.loadCalendars(cals)]; - if (loadPatterns) - pms.push(this.loadDefaultPatterns()); + let pms = [this.loadCalendars(cals, reloadAll)]; + if (reloadAll) + pms.push(this.loadDefaultPatterns(cals)); await Promise.all(pms); this.setState({ calendarsLoading: false }); }; - loadDefaultPatterns() { + loadDefaultPatterns(calendars: {[ id: string ]: gapi.GCalendarMeta }) { let patterns = []; let idx = 0; - for (let id in this.state.calendars) { - let cal = this.state.calendars[id]; - if (!cal.enabled) continue; + for (let id in calendars) { + let cal = calendars[id]; + if (!calendars[id].enabled) continue; patterns.push(new PatternEntry(cal.name, idx++, new Pattern(id, false, cal.name, cal.name), Pattern.anyPattern(), @@ -262,22 +269,17 @@ class Settings extends React.Component<SettingsProps> { this.loadPatterns(patterns, 'main'); } - loadCalendars(calendars: {[ id: string ]: gapi.GCalendarMeta }) { - for (let id in this.state.calendars) { - if (calendars.hasOwnProperty(id)) - calendars[id].enabled = this.state.calendars[id].enabled; - } - this.msgClient.sendMsg({ - opt: MsgType.updateCalendars, - data: calendars - }).then(() => this.setState({ calendars })); + loadCalendars(calendars: {[ id: string ]: gapi.GCalendarMeta }, enabled = false) { + if (!enabled) + for (let id in this.state.calendars) { + if (calendars.hasOwnProperty(id)) + calendars[id].enabled = this.state.calendars[id].enabled; + } + this.setState({ calendars }); } loadPatterns(patterns: PatternEntry[], id: string) { - this.msgClient.sendMsg({ - opt: MsgType.updatePatterns, - data: { id, patterns: patterns.map(p => p.deflate()) } - }).then(() => this.setState({ patterns })); + this.setState({ patterns }); } updatePattern = (field: string, idx: number, value: any) => { @@ -301,16 +303,16 @@ class Settings extends React.Component<SettingsProps> { this.loadPatterns(patterns, 'main'); }; + openSnackbar(msg: string, variant: SnackbarVariant) { + this.setState({ snackBarOpen: true, snackBarMsg: msg, snackBarVariant: variant }); + } + handleSnackbarClose = (event: any, reason: string) => { if (reason === 'clickaway') return; this.setState({ snackBarOpen: false }); } - handleSnackbarOpen = (msg: string) => { - this.setState({ snackBarOpen: true, snackBarMsg: msg }); - } - - handleDialogOpen = (title: string, message: string) => { + openDialog(title: string, message: string) { let pm = new Promise(resolver => { this.dialogPromiseResolver = resolver }); @@ -323,11 +325,8 @@ class Settings extends React.Component<SettingsProps> { this.setState({ dialogOpen: false }); } - updateTrackedPeriods = (trackedPeriods: TrackPeriod[]) => { - this.msgClient.sendMsg({ - opt: MsgType.updateConfig, - data: { trackedPeriods: trackedPeriods.map(p => p.deflate()) } - }).then(() => this.setState({...this.state.config, trackedPeriods })); + updateTrackedPeriods = (trackedPeriods: TrackedPeriodFlat[]) => { + this.setState({...this.state.config, trackedPeriods }); } handlePeriodNameChange = (idx: number) => (name: string) => { @@ -336,18 +335,49 @@ class Settings extends React.Component<SettingsProps> { this.updateTrackedPeriods(trackedPeriods); } - handlePeriodFromChange = (idx: number) => (duration: Duration) => { + handlePeriodFromChange = (idx: number) => (duration: DurationFlat) => { let trackedPeriods = [...this.state.config.trackedPeriods]; trackedPeriods[idx].start = duration; this.updateTrackedPeriods(trackedPeriods); } - handlePeriodToChange = (idx: number) => (duration: Duration) => { + handlePeriodToChange = (idx: number) => (duration: DurationFlat) => { let trackedPeriods = [...this.state.config.trackedPeriods]; trackedPeriods[idx].end = duration; this.updateTrackedPeriods(trackedPeriods); } + handleApply = async () => { + let trackedPeriods = this.state.config.trackedPeriods; + if (trackedPeriods.some(p => ( + TrackedPeriodInput.toValue(p.start.value) === null || + TrackedPeriodInput.toValue(p.end.value) === null ))) { + this.openSnackbar("Invalid time range!", 'error' as SnackbarVariant); + return; + } + + let pm1 = this.msgClient.sendMsg({ + opt: MsgType.updateCalendars, + data: this.state.calendars + }); + let pm2 = this.msgClient.sendMsg({ + opt: MsgType.updatePatterns, + data: { id: 'main', patterns: this.state.patterns.map(p => p.deflate()) } + }); + let pm3 = this.msgClient.sendMsg({ + opt: MsgType.updateConfig, + data: { trackedPeriods } + }); + await Promise.all([pm1, pm2, pm3]); + this.openSnackbar("Saved changes.", 'success' as SnackbarVariant); + } + + handleLoadDefault = async () => { + let ans = await this.openDialog("Load Default", "Load the calendars as patterns?"); + if (!ans) return; + this.loadDefaultPatterns(this.state.calendars); + } + render() { const { classes } = this.props; return ( @@ -360,7 +390,7 @@ class Settings extends React.Component<SettingsProps> { <Snackbar message={this.state.snackBarMsg} open={this.state.snackBarOpen} - variant='error' + variant={this.state.snackBarVariant} onClose={this.handleSnackbarClose}/> <Typography variant="h6" component="h1" gutterBottom> General @@ -390,7 +420,7 @@ class Settings extends React.Component<SettingsProps> { <STableCell className={classes.tableContent}> {(this.state.isLoggedIn && <List className={classes.calendarList}> - {Object.keys(this.state.calendars).map(id => + {Object.keys(this.state.calendars).sort().map(id => <CompactListItem key={id} onClick={() => this.toggleCalendar(id)} @@ -415,7 +445,7 @@ class Settings extends React.Component<SettingsProps> { <Button variant="contained" color="primary" - onClick={() => this.loadDefaultPatterns()}>Load Default</Button> + onClick={this.handleLoadDefault}>Load Default</Button> </div> </STableCell> <STableCell className={classes.tableContent}> @@ -433,11 +463,11 @@ class Settings extends React.Component<SettingsProps> { <STableCell className={classes.tableHead}> Tracked Time Range </STableCell> - <STableCell className={classes.tableContent}> + <STableCell className={classNames(classes.tableContent, classes.trackedPeriodInput)}> {this.state.config.trackedPeriods && this.state.config.trackedPeriods.map((p, idx) => <FormGroup key={idx}> - <TrackedPeriod + <TrackedPeriodInput name={p.name} fromDuration={p.start} toDuration={p.end} @@ -449,6 +479,12 @@ class Settings extends React.Component<SettingsProps> { </TableRow> </TableBody> </Table> + <div className={classes.bottomButtons}> + <Button + variant="contained" + color="primary" + onClick={this.handleApply}>Apply</Button> + </div> </div> ); } diff --git a/src/Snackbar.tsx b/src/Snackbar.tsx index 33a47fe..dadee7c 100644 --- a/src/Snackbar.tsx +++ b/src/Snackbar.tsx @@ -2,10 +2,12 @@ import React from 'react'; import classNames from 'classnames'; import { Theme, withStyles } from '@material-ui/core/styles'; import amber from '@material-ui/core/colors/amber'; +import green from '@material-ui/core/colors/green'; import Snackbar from '@material-ui/core/Snackbar'; import SnackbarContent from '@material-ui/core/SnackbarContent'; import ErrorIcon from '@material-ui/icons/Error'; import WarningIcon from '@material-ui/icons/Warning'; +import CheckCircleIcon from '@material-ui/icons/CheckCircle'; import CloseIcon from '@material-ui/icons/Close'; import IconButton from '@material-ui/core/IconButton'; @@ -14,6 +16,7 @@ import IconButton from '@material-ui/core/IconButton'; const variantIcon = { error: ErrorIcon, warning: WarningIcon, + success: CheckCircleIcon }; const styles = (theme: Theme) => ({ @@ -23,6 +26,9 @@ const styles = (theme: Theme) => ({ warning: { backgroundColor: amber[700], }, + success: { + backgroundColor: green[600], + }, icon: { fontSize: 20, }, @@ -36,12 +42,13 @@ const styles = (theme: Theme) => ({ }, }); -export type SnackbarVariant = 'error' | 'warning'; +export type SnackbarVariant = 'error' | 'warning' | 'success'; type CustomSnackbarProps = { classes: { error: string, warning: string, + success: string, message: string, icon: string, iconVariant: string, diff --git a/src/background.ts b/src/background.ts index 1a0c1eb..b92509e 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,6 +1,6 @@ import * as gapi from './gapi'; import { MsgType, Msg } from './msg'; -import { Duration, TrackPeriod, TrackPeriodFlat } from './duration'; +import { Duration, TrackedPeriod, TrackedPeriodFlat } from './duration'; import moment from 'moment'; import { GraphData, getGraphData } from './graph'; import { PatternEntry, PatternEntryFlat } from './pattern'; @@ -11,10 +11,10 @@ let calendars: {[id: string]: gapi.GCalendarMeta} = {}; let calData: {[id: string]: gapi.GCalendar} = {}; let config = { trackedPeriods: [ - new TrackPeriod('Today', Duration.days(1), Duration.days(0)), - new TrackPeriod('Yesterday', Duration.days(2), Duration.days(1)), - new TrackPeriod('This Week', Duration.weeks(1), Duration.weeks(0)), - new TrackPeriod('This Month', Duration.months(1), Duration.months(0))] as TrackPeriod[] + new TrackedPeriod('Today', Duration.days(1), Duration.days(0)), + new TrackedPeriod('Yesterday', Duration.days(2), Duration.days(1)), + new TrackedPeriod('This Week', Duration.weeks(1), Duration.weeks(0)), + new TrackedPeriod('This Month', Duration.months(1), Duration.months(0))] as TrackedPeriod[] }; let mainGraphData: GraphData[] = []; let dirtyMetadata = false; @@ -31,7 +31,7 @@ function loadMetadata() { { console.log('metadata loaded'); config = { - trackedPeriods: items.config.trackedPeriods.map((p: TrackPeriodFlat) => TrackPeriod.inflate(p)) + trackedPeriods: items.config.trackedPeriods.map((p: TrackedPeriodFlat) => TrackedPeriod.inflate(p)) }; calendars = items.calendars; mainPatterns = items.mainPatterns.map((p: PatternEntryFlat) => PatternEntry.inflate(p)); @@ -45,11 +45,7 @@ function saveMetadata() { return new Promise(resolver => chrome.storage.local.set({ calendars, config: { - trackedPeriods: config.trackedPeriods.map(p => ({ - name: p.name, - start: p.start.deflate(), - end: p.end.deflate() - })) + trackedPeriods: config.trackedPeriods.map(p => p.deflate()) }, mainPatterns: mainPatterns.map(p => p.deflate()), analyzePatterns: analyzePatterns.map(p => p.deflate()) @@ -174,11 +170,7 @@ chrome.runtime.onConnect.addListener(function(port) { break; } case MsgType.updateConfig: { - config.trackedPeriods = msg.data.trackedPeriods.map((p: TrackPeriodFlat) => ({ - name: p.name, - start: Duration.inflate(p.start), - end: Duration.inflate(p.end) - })); + config.trackedPeriods = msg.data.trackedPeriods.map((p: TrackedPeriodFlat) => TrackedPeriod.inflate(p)); dirtyMetadata = true; port.postMessage(msg.genResp(null)); break; diff --git a/src/duration.ts b/src/duration.ts index 90a1d10..5c2ccea 100644 --- a/src/duration.ts +++ b/src/duration.ts @@ -31,13 +31,13 @@ export class Duration { } -export type TrackPeriodFlat = { +export type TrackedPeriodFlat = { name: string, start: DurationFlat, end: DurationFlat }; -export class TrackPeriod { +export class TrackedPeriod { name: string; start: Duration; end: Duration; @@ -56,8 +56,8 @@ export class TrackPeriod { }; } - static inflate = (obj: TrackPeriodFlat) => ( - new TrackPeriod(obj.name, + static inflate = (obj: TrackedPeriodFlat) => ( + new TrackedPeriod(obj.name, Duration.inflate(obj.start), Duration.inflate(obj.end)) ); |