aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDeterminant <ted.sybil@gmail.com>2019-02-16 23:37:17 -0500
committerDeterminant <ted.sybil@gmail.com>2019-02-16 23:37:17 -0500
commit8bc7527072d9d42efd90ae6a4847648c11d48def (patch)
treef55320a25a40e9cc31afbb811867cc6b8e4a7f36 /src
parentf44c7b95ec85ce10bf8f4dcb1f0d6cdbd2fd0e7f (diff)
improve Settings
Diffstat (limited to 'src')
-rw-r--r--src/Analyze.tsx20
-rw-r--r--src/Settings.tsx170
-rw-r--r--src/Snackbar.tsx9
-rw-r--r--src/background.ts24
-rw-r--r--src/duration.ts8
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))
);