diff options
author | Determinant <[email protected]> | 2019-02-04 02:06:59 -0500 |
---|---|---|
committer | Determinant <[email protected]> | 2019-02-04 02:06:59 -0500 |
commit | d3d4345a7ab82e9715b1cbf398d43206d43737f6 (patch) | |
tree | b2006c6dcb774e0e6799ba6c43713be68a695412 /src | |
parent | 0ea5a1140c52d15ebb7f37862c1fabce5b80547b (diff) |
add settings page
Diffstat (limited to 'src')
-rw-r--r-- | src/App.js | 287 | ||||
-rw-r--r-- | src/CustomAnalyzer.js | 255 | ||||
-rw-r--r-- | src/Settings.js | 80 | ||||
-rw-r--r-- | src/gapi.js | 66 | ||||
-rw-r--r-- | src/msg.js | 2 |
5 files changed, 434 insertions, 256 deletions
@@ -1,39 +1,22 @@ import React from 'react'; import PropTypes from 'prop-types'; import 'typeface-roboto'; -import 'react-dates/initialize'; -import 'react-dates/lib/css/_datepicker.css'; -import { DateRangePicker } from 'react-dates'; 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 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 Paper from '@material-ui/core/Paper'; +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; import Grid from '@material-ui/core/Grid'; -import AddCircleIcon from '@material-ui/icons/AddCircle'; -import IconButton from '@material-ui/core/IconButton'; +import { HashRouter as Router, Route, Link, Redirect, Switch } from "react-router-dom"; +import { hashHistory } from 'react-router'; import Logo from './Logo'; -import * as gapi from './gapi'; -import { msgType, Msg, MsgClient } from './msg'; -import { Pattern, PatternEntry } from './pattern'; -import PieChart from './Chart'; -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]}]; - -function filterPatterns(patterns, calName) { - return patterns.filter(p => { - return p.cal.regex.test(calName); - }); -} +import CustomAnalyzer from './CustomAnalyzer'; +import Settings from './Settings'; const styles = theme => ({ root: { @@ -49,9 +32,7 @@ const styles = theme => ({ }, title: { flexGrow: 1, - }, - sectionTitle: { - flex: '0 0 auto' + display: 'inline-block' }, appBarSpacer: theme.mixins.toolbar, content: { @@ -59,241 +40,51 @@ const styles = theme => ({ padding: theme.spacing.unit * 3, overflow: 'auto', }, - buttonSpacer: { - marginBottom: theme.spacing.unit * 4, - }, - fab: { - margin: theme.spacing.unit, - }, + indicator: { + backgroundColor: theme.palette.primary.contrastText + } }); class Dashboard extends React.Component { state = { - patterns: [], - calendars: [], - timeRange: null, - token: gapi.getAuthToken(), - patternGraphData: default_chart_data, - calendarGraphData: default_chart_data, - activePattern: null + currentTab: 0 }; - constructor(props) { - super(props); - this.msgClient = new MsgClient('main'); - this.msgClient.sendMsg({ type: msgType.getPatterns }).then(msg => { - this.setState({ patterns: msg.data.map(p => PatternEntry.revive(p)) }); - }); - this.msgClient.sendMsg({ type: msgType.getCalendars }).then(msg => { - this.setState({ calendars: msg.data }); - }); + handleChangeTab = (event, currentTab) => { + this.setState({ currentTab }); } - updatePattern = (field, idx, value) => { - let patterns = this.state.patterns; - patterns[idx][field] = value; - this.setState({ patterns }); - this.msgClient.sendMsg({ type: msgType.updatePatterns, data: patterns }); - }; - - removePattern = idx => { - let patterns = this.state.patterns; - patterns.splice(idx, 1); - for (let i = 0; i < patterns.length; i++) - patterns[i].idx = i; - this.setState({ patterns }); - this.msgClient.sendMsg({ type: msgType.updatePatterns, data: patterns }); - }; - - newPattern = () => { - let patterns = [PatternEntry.defaultPatternEntry(0), ...this.state.patterns]; - for (let i = 1; i < patterns.length; i++) - patterns[i].idx = i; - this.setState({ patterns }); - this.msgClient.sendMsg({ type: msgType.updatePatterns, data: patterns }); - }; - - loadPatterns = patterns => { - this.setState({ patterns }); - this.msgClient.sendMsg({ type: msgType.updatePatterns, data: patterns }); - }; - - loadCalendars = calendars => { - this.setState({ calendars }); - this.msgClient.sendMsg({ type: msgType.updateCalendars, data: calendars }); - }; - - getCalEvents = (id, start, end) => { - return this.msgClient.sendMsg({ type: msgType.getCalEvents, data: { id, - start: start.getTime(), - end: end.getTime() } }) - .then(({ data }) => data.map(e => { - return { - id: e.id, - start: new Date(e.start), - end: new Date(e.end) } - })); - } - - analyze = () => { - if (!(this.state.startDate && this.state.endDate)) { - alert("Please choose a valid time range."); - return; - } - let start = this.state.startDate.startOf('day').toDate(); - let end = this.state.endDate.startOf('day').toDate(); - let event_pms = []; - let cals = this.state.calendars; - for (let id in cals) - { - let patterns = filterPatterns(this.state.patterns, cals[id].name); - if (patterns.length > 0) - event_pms.push(this.getCalEvents(id, start, end) - .then(r => { return { id, events: r, patterns }; })); - } - Promise.all(event_pms).then(all_events => { - console.log(all_events); - let events = {}; - let patterns = {}; - let results = {}; // pattern idx => time - let cal_results = {}; // cal id => time - all_events.forEach(e => { - events[e.id] = e.events; - patterns[e.id] = e.patterns; - }); - for (let i = 0; i < this.state.patterns.length; i++) - results[i] = 0; - for (let id in cals) { - if (!events[id]) continue; - events[id].forEach(event => { - patterns[id].forEach(p => { - if (!p.event.regex.test(event.summary)) return; - if (!cal_results.hasOwnProperty(id)) { - cal_results[id] = 0; - } - let duration = (event.end - event.start) / 60000; - results[p.idx] += duration; - cal_results[id] += duration; - }); - }); - } - let patternGraphData = []; - let calendarGraphData = []; - for (let i = 0; i < this.state.patterns.length; i++) { - patternGraphData.push({ name: this.state.patterns[i].name, value: results[i] / 60.0 }); - } - for (let id in cal_results) { - calendarGraphData.push({ - name: cals[id].name, - value: (cal_results[id] / 60.0), - color: cals[id].color.background}); - } - console.log(patternGraphData, calendarGraphData); - this.setState({ patternGraphData, calendarGraphData }); - }); - }; - - load = () => { - let token = this.state.token; - let colors = token.then(gapi.getColors).then(color => { - return color.calendar; - }); - let cals = token.then(gapi.getCalendars); - Promise.all([colors, cals]).then(([colors, items]) => { - var cals = {}; - items.forEach(item => { - cals[item.id] = { - name: item.summary, - color: colors[item.colorId], - //cal: new gapi.GCalendar(item.id, item.summary) - }}); - this.loadCalendars(cals); - this.loadPatterns(items.map((item, idx) => { - return new PatternEntry(item.summary, idx, - new Pattern(item.id, false, item.summary, item.summary), - Pattern.anyPattern()); - })); - }); - }; - render() { const { classes } = this.props; 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} - calendars={this.state.calendars} - onRemovePattern={this.removePattern} - onUpdatePattern={this.updatePattern} /> - </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.load}>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 + <Router> + <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> - <PieChart - patternGraphData={this.state.patternGraphData} - calendarGraphData={this.state.calendarGraphData}/> - </Grid> - </Grid> - </main> - </div> + <Tabs styles={{ display: 'inline-block '}} + classes={{ indicator: classes.indicator }} + value={this.state.currentTab} + onChange={this.handleChangeTab}> + <Tab label="Settings" component={Link} to="/settings" /> + <Tab label="Analyze" component={Link} to="/analyze" /> + </Tabs> + </Toolbar> + </AppBar> + <CssBaseline /> + <main className={classes.content}> + <div className={classes.appBarSpacer} /> + <Route exact path="/settings" component={Settings} /> + <Route exact path="/analyze" component={CustomAnalyzer} /> + </main> + </div> + </Router> </MuiThemeProvider>); } } diff --git a/src/CustomAnalyzer.js b/src/CustomAnalyzer.js new file mode 100644 index 0000000..f76fa5e --- /dev/null +++ b/src/CustomAnalyzer.js @@ -0,0 +1,255 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import 'react-dates/initialize'; +import 'react-dates/lib/css/_datepicker.css'; +import { DateRangePicker } from 'react-dates'; +import { withStyles } from '@material-ui/core/styles'; +import cyan from '@material-ui/core/colors/cyan'; +import CssBaseline from '@material-ui/core/CssBaseline'; +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 Grid from '@material-ui/core/Grid'; +import AddCircleIcon from '@material-ui/icons/AddCircle'; +import IconButton from '@material-ui/core/IconButton'; +import * as gapi from './gapi'; +import { msgType, MsgClient } from './msg'; +import { Pattern, PatternEntry } from './pattern'; +import PieChart from './Chart'; +import PatternTable from './PatternTable'; + +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 => { + return p.cal.regex.test(calName); + }); +} + +const styles = theme => ({ + buttonSpacer: { + marginBottom: theme.spacing.unit * 4, + }, +}); + +class CustomAnalyzer extends React.Component { + state = { + patterns: [], + calendars: [], + timeRange: null, + patternGraphData: default_chart_data, + calendarGraphData: default_chart_data, + activePattern: null + }; + + constructor(props) { + super(props); + this.msgClient = new MsgClient('main'); + this.msgClient.sendMsg({ type: msgType.getPatterns }).then(msg => { + this.setState({ patterns: msg.data.map(p => PatternEntry.revive(p)) }); + }); + this.msgClient.sendMsg({ type: msgType.getCalendars }).then(msg => { + this.setState({ calendars: msg.data }); + }); + } + + updatePattern = (field, idx, value) => { + let patterns = this.state.patterns; + patterns[idx][field] = value; + this.setState({ patterns }); + this.msgClient.sendMsg({ type: msgType.updatePatterns, data: patterns }); + }; + + removePattern = idx => { + let patterns = this.state.patterns; + patterns.splice(idx, 1); + for (let i = 0; i < patterns.length; i++) + patterns[i].idx = i; + this.setState({ patterns }); + this.msgClient.sendMsg({ type: msgType.updatePatterns, data: patterns }); + }; + + newPattern = () => { + let patterns = [PatternEntry.defaultPatternEntry(0), ...this.state.patterns]; + for (let i = 1; i < patterns.length; i++) + patterns[i].idx = i; + this.setState({ patterns }); + this.msgClient.sendMsg({ type: msgType.updatePatterns, data: patterns }); + }; + + loadPatterns = patterns => { + this.setState({ patterns }); + this.msgClient.sendMsg({ type: msgType.updatePatterns, data: patterns }); + }; + + loadCalendars = calendars => { + this.setState({ calendars }); + this.msgClient.sendMsg({ type: msgType.updateCalendars, data: calendars }); + }; + + getCalEvents = (id, start, end) => { + return this.msgClient.sendMsg({ type: msgType.getCalEvents, data: { id, + start: start.getTime(), + end: end.getTime() } }) + .then(({ data }) => data.map(e => { + return { + id: e.id, + start: new Date(e.start), + end: new Date(e.end) } + })); + } + + analyze = () => { + if (!(this.state.startDate && this.state.endDate)) { + alert("Please choose a valid time range."); + return; + } + let start = this.state.startDate.startOf('day').toDate(); + let end = this.state.endDate.startOf('day').toDate(); + let event_pms = []; + let cals = this.state.calendars; + for (let id in cals) + { + let patterns = filterPatterns(this.state.patterns, cals[id].name); + if (patterns.length > 0) + event_pms.push(this.getCalEvents(id, start, end) + .then(r => { return { id, events: r, patterns }; })); + } + Promise.all(event_pms).then(all_events => { + console.log(all_events); + let events = {}; + let patterns = {}; + let results = {}; // pattern idx => time + let cal_results = {}; // cal id => time + all_events.forEach(e => { + events[e.id] = e.events; + patterns[e.id] = e.patterns; + }); + for (let i = 0; i < this.state.patterns.length; i++) + results[i] = 0; + for (let id in cals) { + if (!events[id]) continue; + events[id].forEach(event => { + patterns[id].forEach(p => { + if (!p.event.regex.test(event.summary)) return; + if (!cal_results.hasOwnProperty(id)) { + cal_results[id] = 0; + } + let duration = (event.end - event.start) / 60000; + results[p.idx] += duration; + cal_results[id] += duration; + }); + }); + } + let patternGraphData = []; + let calendarGraphData = []; + for (let i = 0; i < this.state.patterns.length; i++) { + patternGraphData.push({ name: this.state.patterns[i].name, value: results[i] / 60.0 }); + } + for (let id in cal_results) { + calendarGraphData.push({ + name: cals[id].name, + value: (cal_results[id] / 60.0), + color: cals[id].color.background}); + } + console.log(patternGraphData, calendarGraphData); + this.setState({ patternGraphData, calendarGraphData }); + }); + }; + + load = () => { + let colors = gapi.getAuthToken().then(gapi.getColors).then(color => { + return color.calendar; + }); + let cals = gapi.getAuthToken().then(gapi.getCalendars); + Promise.all([colors, cals]).then(([colors, items]) => { + var cals = {}; + items.forEach(item => { + cals[item.id] = { + name: item.summary, + color: colors[item.colorId], + //cal: new gapi.GCalendar(item.id, item.summary) + }}); + this.loadCalendars(cals); + this.loadPatterns(items.map((item, idx) => { + return new PatternEntry(item.summary, idx, + new Pattern(item.id, false, item.summary, item.summary), + Pattern.anyPattern()); + })); + }); + }; + + render() { + const { classes } = this.props; + + return ( + <Grid container spacing={16}> + <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} + calendars={this.state.calendars} + onRemovePattern={this.removePattern} + onUpdatePattern={this.updatePattern} /> + </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.load}>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> + ); + } +} + +CustomAnalyzer.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CustomAnalyzer); diff --git a/src/Settings.js b/src/Settings.js new file mode 100644 index 0000000..11167a0 --- /dev/null +++ b/src/Settings.js @@ -0,0 +1,80 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import CssBaseline from '@material-ui/core/CssBaseline'; +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 Grid from '@material-ui/core/Grid'; +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 * as gapi from './gapi'; +import { msgType, MsgClient } from './msg'; +import { Pattern, PatternEntry } from './pattern'; + +const styles = theme => ({ +}); + +const STableCell = withStyles(theme => ({ + body: { + fontSize: 16, + }, +}))(TableCell); + +class Settings extends React.Component { + state = { + isLoggedIn: false + }; + + constructor(props) { + super(props); + gapi.getLoggedIn().then(b => this.setState({ isLoggedIn: b })); + } + + handleLogin = () => { + gapi.login().then(() => this.setState({ isLoggedIn: true })); + } + + handleLogout = () => { + gapi.logout().then(() => this.setState({ isLoggedIn: false })); + } + + render() { + const { classes } = this.props; + return ( + <Grid container spacing={16}> + <Grid item md={6} xs={12}> + <Typography variant="h6" component="h1" gutterBottom> + General + </Typography> + <Table> + <TableBody> + <TableRow> + <STableCell align='right'>Account</STableCell> + <STableCell align='left'> + { + (this.state.isLoggedIn && + <Button variant="contained" color="primary" onClick={this.handleLogout}>Logout</Button>) || + <Button variant="contained" color="primary" onClick={this.handleLogin}>Login</Button> + } + </STableCell> + </TableRow> + </TableBody> + </Table> + </Grid> + </Grid> + ); + } +} + +Settings.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Settings); diff --git a/src/gapi.js b/src/gapi.js index 25d1cbf..60e9b1a 100644 --- a/src/gapi.js +++ b/src/gapi.js @@ -2,19 +2,68 @@ import LRU from "lru-cache"; const gapi_base = 'https://www.googleapis.com/calendar/v3'; -const GApiError = { - invalidSyncToken: 1, - otherError: 2, -}; +const GApiError = Object.freeze({ + invalidSyncToken: Symbol("invalidSyncToken"), + notLoggedIn: Symbol("notLoggedIn"), + notLoggedOut: Symbol("notLoggedOut"), + otherError: Symbol("otherError"), +}); function to_params(dict) { return Object.entries(dict).filter(([k, v]) => v).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&'); } -export function getAuthToken() { +let loggedIn = null; + +function _getAuthToken(interactive = false) { return new Promise(resolver => chrome.identity.getAuthToken( - {interactive: true}, token => resolver(token))); + { interactive }, token => resolver([token, !chrome.runtime.lastError]))) + .then(([token, ok]) => { + if (ok) return token; + else throw GApiError.notLoggedIn; + }); +} + +function _removeCachedAuthToken(token) { + return new Promise(resolver => + chrome.identity.removeCachedAuthToken({ token }, () => resolver())); +} + +export function getLoggedIn() { + if (loggedIn === null) + { + return _getAuthToken(false) + .then(() => {loggedIn = true}) + .catch(() => {loggedIn = false; console.log("here");}) + .then(() => loggedIn); + } + else return Promise.resolve(loggedIn); +} + +export function getAuthToken() { + return getLoggedIn().then(b => { + if (b) return _getAuthToken(false); + else throw GApiError.notLoggedIn; + }); +} + +export function login() { + return getLoggedIn().then(b => { + if (!b) return _getAuthToken(true).then(() => loggedIn = true); + else throw GApiError.notLoggedOut; + }); +} + +export function logout() { + return getAuthToken().then(token => { + return fetch(`https://accounts.google.com/o/oauth2/revoke?${to_params({ token })}`, + { method: 'GET', async: true }).then(response => { + if (response.status === 200) + return _removeCachedAuthToken(token); + else throw GApiError.otherError; + }); + }).then(() => loggedIn = false); } export function getCalendars(token) { @@ -51,7 +100,7 @@ function getEvents(calId, token, syncToken=null, timeMin=null, timeMax=null, res return response.json(); else if (response.status === 410) throw GApiError.invalidSyncToken; - else throw GApiError.otherErrors; + else throw GApiError.otherError; }) .then(data => { results.push(...data.items); @@ -72,7 +121,6 @@ export class GCalendar { constructor(calId, name, options={maxCachedItems: 100, nDaysPerSlot: 10, largeQuery: 10}) { this.calId = calId; this.name = name; - this.token = getAuthToken(); this.syncToken = ''; this.cache = new LRU({ max: options.maxCachedItems, @@ -83,6 +131,8 @@ export class GCalendar { this.divider = 8.64e7 * this.options.nDaysPerSlot; } + get token() { return getAuthToken(); } + dateToCacheKey(date) { return Math.floor(date / this.divider); } @@ -20,6 +20,7 @@ function stringifyMsgType(mt) { case msgType.updateCalendars: return _updateCalendars; case msgType.getCalendars: return _getCalendars; case msgType.getCalEvents: return _getCalEvents; + default: console.error("unreachable"); } } @@ -30,6 +31,7 @@ function parseMsgType(s) { case _updateCalendars: return msgType.updateCalendars; case _getCalendars: return msgType.getCalendars; case _getCalEvents: return msgType.getCalEvents; + default: console.error("unreachable"); } } |