import React from 'react'; import { Theme, withStyles, StyleRules } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; import FormControl from '@material-ui/core/FormControl'; import FormGroup from '@material-ui/core/FormGroup'; import RefreshIcon from '@material-ui/icons/Refresh'; import AddCircleIcon from '@material-ui/icons/AddCircle'; import IconButton from '@material-ui/core/IconButton'; 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 List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; import Checkbox from '@material-ui/core/Checkbox'; import TextField from '@material-ui/core/TextField'; import MenuItem from '@material-ui/core/MenuItem'; import Select from '@material-ui/core/Select'; import PatternTable from './PatternTable'; import Snackbar from './Snackbar'; import AlertDialog from './Dialog'; import * as gapi from './gapi'; import { MsgType, MsgClient } from './msg'; import { Pattern, PatternEntry, PatternEntryFlat } from './pattern'; import { Duration, TrackPeriod, TrackPeriodFlat } from './duration'; const styles = (theme: Theme): StyleRules => ({ tableHead: { verticalAlign: 'top', textAlign: 'right', lineHeight: '3em', }, tableContent: { textAlign: 'left', maxWidth: 600, }, calendarList: { maxHeight: 400, overflowY: 'auto' }, }); const STableCell = withStyles(theme => ({ body: { fontSize: 16, }, }))(TableCell); const CompactListItem = withStyles(theme => ({ dense: { paddingTop: 0, paddingBottom: 0 }, }))(ListItem); type TrackedPeriodProps = { name: string fromDuration: Duration, toDuration: Duration, nameOnChange: (name: string) => void, fromOnChange: (d: Duration) => void, toOnChange: (d: Duration) => void }; class TrackedPeriod extends React.Component { valueOnChange = (old: Duration, onChange: (d: Duration) => void) => (event: any) => { onChange(new Duration(event.target.value, old.unit)); } unitOnChange = (old: Duration, onChange: (d: Duration) => void) => (event: any) => { onChange(new Duration(old.value, event.target.value)); } static styles = { periodName: { textAlign: 'right' }, periodValue: { width: 30, textAlign: 'center' } }; static toValue(value: any) { if (isNaN(value)) return null; let v = parseInt(value, 10); if (v < 0 || v > 999) return null; return v; } render() { let { fromDuration, toDuration, nameOnChange, name, fromOnChange, toOnChange } = this.props; let units = [ Day(s), Week(s), Month(s) ]; return ( nameOnChange(event.target.value)}/>: from ago to ago ); } } type SettingsProps = { classes: { tableHead: string, tableContent: string, calendarList: string } }; class Settings extends React.Component { msgClient: MsgClient; dialogPromiseResolver: (r: boolean) => void; state = { isLoggedIn: false, patterns: [] as PatternEntry[], calendars: {} as {[id: string]: gapi.GCalendarMeta}, config: {} as { trackedPeriods: TrackPeriod[] }, snackBarOpen: false, snackBarMsg: 'unknown', dialogOpen: false, dialogMsg: {title: '', message: ''}, calendarsLoading: false, }; constructor(props: SettingsProps) { super(props); gapi.getLoggedIn().then(b => this.setState({ isLoggedIn: b })); this.msgClient = new MsgClient('main'); this.msgClient.sendMsg({ opt: MsgType.getPatterns, data: { id: 'main' } }).then(msg => { this.setState({ patterns: msg.data.map((p: PatternEntryFlat) => PatternEntry.inflate(p)) }); }); this.msgClient.sendMsg({ opt: MsgType.getCalendars, data: { enabledOnly: false } }).then(msg => { this.setState({ calendars: msg.data }); }); this.msgClient.sendMsg({ opt: MsgType.getConfig, data: ['trackedPeriods'] }).then(msg => { let config = { trackedPeriods: msg.data.trackedPeriods.map((p: TrackPeriodFlat) => ( TrackPeriod.inflate(p) )) }; console.log(msg.data.trackedPeriods); this.setState({ config }); }); this.dialogPromiseResolver = null; } handleLogin = async () => { try { await gapi.login(); this.setState({ isLoggedIn: true }); this.loadAll(true); } catch (_) { this.handleSnackbarOpen("Failed to login!"); } } handleLogout = async () => { let ans = await this.handleDialogOpen("Logout", "Are you sure to logout?"); if (!ans) return; try { await gapi.logout(); this.setState({ isLoggedIn: false }); } catch (_) { this.handleSnackbarOpen("Failed to logout!"); } } 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 })); } async loadAll(loadPatterns = false) { await new Promise(resolver => (this.setState({ calendarsLoading: true }, resolver))); let pm_colors = gapi.getAuthToken().then(gapi.getColors).then(color => color.calendar); let pm_cals = gapi.getAuthToken().then(gapi.getCalendars); let [colors, _cals] = await Promise.all([pm_colors, pm_cals]); var cals: { [id: string]: gapi.GCalendarMeta } = {}; _cals.forEach((cal: any) => { cals[cal.id] = { name: cal.summary, color: colors[cal.colorId], enabled: true }; }); let pms = [this.loadCalendars(cals)]; if (loadPatterns) pms.push(this.loadDefaultPatterns()); await Promise.all(pms); this.setState({ calendarsLoading: false }); }; loadDefaultPatterns() { let patterns = []; let idx = 0; for (let id in this.state.calendars) { let cal = this.state.calendars[id]; if (!cal.enabled) continue; patterns.push(new PatternEntry(cal.name, idx++, new Pattern(id, false, cal.name, cal.name), Pattern.anyPattern(), cal.color)); } 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 })); } loadPatterns(patterns: PatternEntry[], id: string) { this.msgClient.sendMsg({ opt: MsgType.updatePatterns, data: { id, patterns: patterns.map(p => p.deflate()) } }).then(() => this.setState({ patterns })); } updatePattern = (field: string, idx: number, value: any) => { let patterns = this.state.patterns; (patterns[idx] as {[key: string]: any})[field] = value; this.loadPatterns(patterns, 'main'); }; removePattern = (idx: number) => { let patterns = this.state.patterns; patterns.splice(idx, 1); for (let i = 0; i < patterns.length; i++) patterns[i].idx = i; this.loadPatterns(patterns, 'main'); }; newPattern = () => { let patterns = [PatternEntry.defaultPatternEntry(0), ...this.state.patterns]; for (let i = 1; i < patterns.length; i++) patterns[i].idx = i; this.loadPatterns(patterns, 'main'); }; 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) => { let pm = new Promise(resolver => { this.dialogPromiseResolver = resolver }); this.setState({ dialogOpen: true, dialogMsg: {title, message} }); return pm; } handleDialogClose = (result: boolean) => { this.dialogPromiseResolver(result); 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 })); } handlePeriodNameChange = (idx: number) => (name: string) => { let trackedPeriods = [...this.state.config.trackedPeriods]; trackedPeriods[idx].name = name; this.updateTrackedPeriods(trackedPeriods); } handlePeriodFromChange = (idx: number) => (duration: Duration) => { let trackedPeriods = [...this.state.config.trackedPeriods]; trackedPeriods[idx].start = duration; this.updateTrackedPeriods(trackedPeriods); } handlePeriodToChange = (idx: number) => (duration: Duration) => { let trackedPeriods = [...this.state.config.trackedPeriods]; trackedPeriods[idx].end = duration; this.updateTrackedPeriods(trackedPeriods); } render() { const { classes } = this.props; return (
General Account { (this.state.isLoggedIn && ) || } this.loadAll(false)} disabled={this.state.calendarsLoading || !this.state.isLoggedIn}> Calendars {(this.state.isLoggedIn && {Object.keys(this.state.calendars).map(id => this.toggleCalendar(id)} disableGutters dense button > )} ) || 'Please Login.'} this.newPattern()} disabled={!this.state.isLoggedIn}> Tracked Events
{(this.state.isLoggedIn && ) || 'Please Login.'}
Tracked Time Range {this.state.config.trackedPeriods && this.state.config.trackedPeriods.map((p, idx) => )}
); } } const StyledSettings = withStyles(styles)(Settings); export default StyledSettings;