diff options
author | Determinant <[email protected]> | 2019-02-14 13:38:33 -0500 |
---|---|---|
committer | Determinant <[email protected]> | 2019-02-14 13:38:33 -0500 |
commit | 513fc1e2faac736bcefe71aba64e74196524001d (patch) | |
tree | 1e3993cc6263156369c69a84bee77bb4f508749c /src | |
parent | b88894cd027d27b8fb7ab358128f52c6f24fbe0d (diff) |
...
Diffstat (limited to 'src')
-rw-r--r-- | src/Analyze.tsx | 5 | ||||
-rw-r--r-- | src/Chart.tsx | 33 | ||||
-rw-r--r-- | src/Dashboard.tsx | 40 | ||||
-rw-r--r-- | src/Dialog.tsx | 8 | ||||
-rw-r--r-- | src/Logo.tsx | 2 | ||||
-rw-r--r-- | src/Settings.tsx | 16 | ||||
-rw-r--r-- | src/background.ts | 85 | ||||
-rw-r--r-- | src/duration.ts | 38 | ||||
-rw-r--r-- | src/graph.ts | 25 | ||||
-rw-r--r-- | src/popup.tsx | 26 |
10 files changed, 169 insertions, 109 deletions
diff --git a/src/Analyze.tsx b/src/Analyze.tsx index e0e852d..8f55bd8 100644 --- a/src/Analyze.tsx +++ b/src/Analyze.tsx @@ -17,7 +17,8 @@ import IconButton from '@material-ui/core/IconButton'; import * as gapi from './gapi'; import { MsgType, MsgClient } from './msg'; import { Pattern, PatternEntry, PatternEntryFlat } from './pattern'; -import { AnalyzePieChart, getChartData } from './Chart'; +import { AnalyzePieChart } from './Chart'; +import { getGraphData } from './graph'; import PatternTable from './PatternTable'; import Snackbar from './Snackbar'; import AlertDialog from './Dialog'; @@ -127,7 +128,7 @@ class Analyze extends React.Component<{classes: {buttonSpacer: string}}> { } let start = this.state.startDate.startOf('day').toDate(); let end = this.state.endDate.startOf('day').toDate(); - getChartData(start, end, + getGraphData(start, end, this.state.patterns, this.state.calendars, this.getCalEvents).then(results => { diff --git a/src/Chart.tsx b/src/Chart.tsx index 3b541fa..c7b59c4 100644 --- a/src/Chart.tsx +++ b/src/Chart.tsx @@ -1,18 +1,21 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; +import { Theme, withStyles } from '@material-ui/core/styles'; import Grid from '@material-ui/core/Grid'; import cyan from '@material-ui/core/colors/cyan'; import { PieChart, Pie, Cell, Tooltip } from 'recharts'; import { defaultChartColor } from './theme'; +import { PatternGraphData } from './graph'; -const styles = theme => ({ +const styles = (theme: Theme) => ({ pieChart: { margin: '0 auto', } }); -function customizedLabel(props) { +function customizedLabel(props: {cx: number, cy: number, + x: number, y: number, + fill: string, name: string}) { const {cx, cy, x, y, fill, name} = props; let anchor = "middle"; const EPS = 2; @@ -35,7 +38,12 @@ function customizedLabel(props) { return (<text x={x} y={y} dx={dx} dy={dy} fill={fill} textAnchor={anchor}>{`${name}`}</text>); } -function PatternPieChart(props) { +function PatternPieChart(props: { + classes: { + patternTableWrapper: string, + pieChart: string + }, + data: PatternGraphData[] }) { return ( <Grid item xs={12} lg={6}> <div className={props.classes.patternTableWrapper}> @@ -50,7 +58,7 @@ function PatternPieChart(props) { label={customizedLabel}> {props.data.map((d, i) => <Cell key={i} fill={d.color ? d.color: defaultChartColor}/>)} </Pie> - <Tooltip formatter={(value) => `${value.toFixed(2)} hr`}/> + <Tooltip formatter={(value: number) => `${value.toFixed(2)} hr`}/> </PieChart> </div> </Grid> @@ -59,7 +67,13 @@ function PatternPieChart(props) { export const StyledPatternPieChart = withStyles(styles)(PatternPieChart); -function DoublePieChart(props) { +function DoublePieChart(props: { + classes: { + patternTableWrapper: string, + pieChart: string + }, + patternGraphData: PatternGraphData[], + calendarGraphData: PatternGraphData[] }) { return ( <Grid container spacing={0}> <StyledPatternPieChart data={props.patternGraphData} /> @@ -77,16 +91,11 @@ function DoublePieChart(props) { label={customizedLabel}> {props.calendarGraphData.map((d, i) => <Cell key={i} fill={d.color ? d.color : cyan[300]}/>)} </Pie> - <Tooltip formatter={(value) => `${value.toFixed(2)} hr`}/> + <Tooltip formatter={(value: number) => `${value.toFixed(2)} hr`}/> </PieChart> </div> </Grid> </Grid>); } -DoublePieChart.propTypes = { - patternGraphData: PropTypes.array.isRequired, - calendarGraphData: PropTypes.array.isRequired, -}; - export const AnalyzePieChart = withStyles(styles)(DoublePieChart); diff --git a/src/Dashboard.tsx b/src/Dashboard.tsx index 04ced46..10f727e 100644 --- a/src/Dashboard.tsx +++ b/src/Dashboard.tsx @@ -1,23 +1,23 @@ import React from 'react'; import PropTypes from 'prop-types'; import 'typeface-roboto'; -import { withStyles, MuiThemeProvider } from '@material-ui/core/styles'; +import { Theme, withStyles, MuiThemeProvider } from '@material-ui/core/styles'; 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 Paper from '@material-ui/core/Paper'; import Tabs from '@material-ui/core/Tabs'; -import Tab from '@material-ui/core/Tab'; +import Tab, { TabProps } from '@material-ui/core/Tab'; +import { LinkProps } from '@material-ui/core/Link'; import Grid from '@material-ui/core/Grid'; -import { HashRouter as Router, withRouter, Route, Link, Redirect, Switch } from "react-router-dom"; -import { hashHistory } from 'react-router'; +import { HashRouter as Router, RouteComponentProps, withRouter, Route, Link, Redirect, Switch } from "react-router-dom"; import Logo from './Logo'; import { theme } from './theme'; import Analyze from './Analyze'; import Settings from './Settings'; -const styles = theme => ({ +const styles = (theme: Theme) => ({ root: { display: 'flex', height: '100vh', @@ -44,8 +44,21 @@ const styles = theme => ({ } }); -class DashboardTabs extends React.Component { - handleChangeTab = (event, currentTab) => { +interface DashboardTabsProps extends RouteComponentProps { + classes: { + root: string, + appBar: string, + appBarSpacer: string, + toolbar: string, + title: string, + indicator: string, + content: string + }; +} + + +class DashboardTabs extends React.Component<DashboardTabsProps> { + handleChangeTab = (event: React.SyntheticEvent<{}>, currentTab: any) => { this.props.history.push(currentTab); } render() { @@ -59,12 +72,12 @@ class DashboardTabs extends React.Component { <Typography component="h1" variant="h6" color="inherit" noWrap className={classes.title}> <Logo style={{width: '2em', verticalAlign: 'bottom', marginRight: '0.2em'}}/>Chromicle </Typography> - <Tabs styles={{ display: 'inline-block '}} + <Tabs classes={{ indicator: classes.indicator }} value={this.props.history.location.pathname} onChange={this.handleChangeTab}> - <Tab label="Settings" component={Link} to="/settings" value="/settings" /> - <Tab label="Analyze" component={Link} to="/analyze" value="/analyze" /> + <Tab label="Settings" {...{component: Link, to: "/settings"} as any} value="/settings" /> + <Tab label="Analyze" {...{component: Link, to: "/analyze"} as any} value="/analyze" /> </Tabs> </Toolbar> </AppBar> @@ -80,13 +93,8 @@ class DashboardTabs extends React.Component { } } -DashboardTabs.propTypes = { - classes: PropTypes.object.isRequired, -}; - -class Dashboard extends React.Component { +class Dashboard extends React.Component<{}> { render() { - const { classes } = this.props; let Tabs = withRouter(withStyles(styles)(DashboardTabs)); return ( <MuiThemeProvider theme={theme}> diff --git a/src/Dialog.tsx b/src/Dialog.tsx index 7e24176..df72144 100644 --- a/src/Dialog.tsx +++ b/src/Dialog.tsx @@ -9,11 +9,15 @@ import Slide from '@material-ui/core/Slide'; // modified from https://material-ui.com/demos/dialogs/ -function Transition(props) { +function Transition(props: any) { return <Slide direction="up" {...props} />; } -function AlertDialog(props) { +function AlertDialog(props: { + open: boolean, + handleClose: (r: boolean) => any, + title: string, + message: string}) { return ( <Dialog open={props.open} diff --git a/src/Logo.tsx b/src/Logo.tsx index a4036a9..d6abb46 100644 --- a/src/Logo.tsx +++ b/src/Logo.tsx @@ -1,5 +1,5 @@ import React from 'react'; -export default (props) => +export default (props: {style: {[key: string]: string | number }}) => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98.905998 93.557997" diff --git a/src/Settings.tsx b/src/Settings.tsx index 83f1da6..1626a7a 100644 --- a/src/Settings.tsx +++ b/src/Settings.tsx @@ -155,13 +155,9 @@ class Settings extends React.Component { data: ['trackedPeriods'] }).then(msg => { let config = { - trackedPeriods: msg.data.trackedPeriods.map(p => { - return { - start: Duration.inflate(p.start), - end: Duration.inflate(p.end), - name: p.name - }; - }) + trackedPeriods: msg.data.trackedPeriods.map((p: TrackPeriodFlat) => ( + new TrackPeriod.inflate(p); + )) }; console.log(msg.data.trackedPeriods); this.setState({ config }); @@ -298,11 +294,7 @@ class Settings extends React.Component { updateTrackedPeriods = trackedPeriods => { this.msgClient.sendMsg({ type: MsgType.updateConfig, - data: { trackedPeriods: trackedPeriods.map(p => ({ - name: p.name, - start: p.start.deflate(), - end: p.end.deflate() - })) } + data: { trackedPeriods: trackedPeriods.map(p => p.deflate())) } }).then(() => this.setState({...this.state.config, trackedPeriods })); } diff --git a/src/background.ts b/src/background.ts index fbae2b6..6a3e3bc 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,20 +1,20 @@ import * as gapi from './gapi'; import { MsgType, Msg } from './msg'; -import { Duration, TrackPeriod } from './duration'; +import { Duration, TrackPeriod, TrackPeriodFlat } from './duration'; import moment from 'moment'; import { GraphData, getGraphData } from './graph'; -import { PatternEntry } from './pattern'; +import { PatternEntry, PatternEntryFlat } from './pattern'; let mainPatterns: PatternEntry[] = []; let analyzePatterns: PatternEntry[] = []; let calendars: {[id: string]: gapi.GCalendarMeta} = {}; let calData: {[id: string]: gapi.GCalendar} = {}; -let config: TrackPeriod[] = { +let config = { trackedPeriods: [ - {name: 'Today', start: Duration.days(1), end: Duration.days(0)}, - {name: 'Yesterday', start: Duration.days(2), end: Duration.days(1)}, - {name: 'This Week', start: Duration.weeks(1), end: Duration.weeks(0)}, - {name: 'This Month', start: Duration.months(1), end: Duration.months(0)}] + 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[] }; let mainGraphData: GraphData[] = []; let dirtyMetadata = false; @@ -31,15 +31,15 @@ function loadMetadata() { { console.log('metadata loaded'); config = { - trackedPeriods: items.config.trackedPeriods.map(p => ({ + trackedPeriods: items.config.trackedPeriods.map((p: TrackPeriod) => ({ name: p.name, start: Duration.inflate(p.start), end: Duration.inflate(p.end), })) }; calendars = items.calendars; - mainPatterns = items.mainPatterns.map(p => PatternEntry.inflate(p)); - analyzePatterns = items.analyzePatterns.map(p => PatternEntry.inflate(p)); + mainPatterns = items.mainPatterns.map((p: PatternEntryFlat) => PatternEntry.inflate(p)); + analyzePatterns = items.analyzePatterns.map((p: PatternEntryFlat) => PatternEntry.inflate(p)); } resolver(); })); @@ -63,15 +63,17 @@ function saveMetadata() { })); } -function getCalEvents(id, start, end) { +async function getCalEvents(id: string, start: Date, end: Date) { if (!calData.hasOwnProperty(id)) - calData[id] = new gapi.GCalendar(id, calendars[id].summary); - return calData[id].getEvents(new Date(start), new Date(end)) - .catch(e => { - console.log(`cannot load calendar ${id}`, e); - calendars[id].enabled = false; - return []; - }); + calData[id] = new gapi.GCalendar(id, calendars[id].name); + try { + let res = await calData[id].getEvents(new Date(start), new Date(end)); + return res; + } catch(err) { + console.log(`cannot load calendar ${id}`, err); + calendars[id].enabled = false; + return []; + } } function updateMainGraphData() { @@ -96,21 +98,15 @@ function updateMainGraphData() { let end = start.clone(); start.subtract(startD); end.subtract(endD); - pms.push(getChartData( - start.toDate(), - end.toDate(), - mainPatterns, - calendars, - (id, start,end) => getCalEvents(id, start, end).then(d => d.map(e => ({ - id: e.id, - start: e.start.getTime(), - end: e.end.getTime() - })))).then(results => { + pms.push(getGraphData( + start.toDate(), end.toDate(), mainPatterns, calendars, + getCalEvents + ).then(results => { mainGraphData[i] = { - name: p.name, - start: start.toDate(), - end: end.toDate(), - data: results.patternGraphData + name: p.name, + start: start.toDate(), + end: end.toDate(), + data: results.patternGraphData }; })); } @@ -131,12 +127,12 @@ loadMetadata().then(() => pollSync()); chrome.runtime.onConnect.addListener(function(port) { console.assert(port.name == 'main'); - port.onMessage.addListener(function(_msg) { - let msg = Msg.inflate(_msg); + port.onMessage.addListener(_msg => { + let msg = Msg.inflate<any>(_msg); console.log(msg); - switch (msg.type) { + switch (msg.opt) { case MsgType.updatePatterns: { - let patterns = msg.data.patterns.map(p => PatternEntry.inflate(p)); + let patterns = msg.data.patterns.map((p: PatternEntryFlat) => PatternEntry.inflate(p)); if (msg.data.id == 'analyze') analyzePatterns = patterns; else @@ -166,13 +162,13 @@ chrome.runtime.onConnect.addListener(function(port) { { cals = Object.keys(calendars) .filter(id => calendars[id].enabled) - .reduce((res, id) => (res[id] = calendars[id], res), {}); + .reduce((res, id) => (res[id] = calendars[id], res), {} as {[id: string]: gapi.GCalendarMeta}); } port.postMessage(msg.genResp(cals)); break; } case MsgType.getCalEvents: { - getCalEvents(msg.data.id, msg.data.start, msg.data.end).then(data => { + getCalEvents(msg.data.id, new Date(msg.data.start), new Date(msg.data.end)).then(data => { console.log(data); let resp = msg.genResp(data.map(e => { return { @@ -187,7 +183,7 @@ chrome.runtime.onConnect.addListener(function(port) { break; } case MsgType.updateConfig: { - config.trackedPeriods = msg.data.trackedPeriods.map(p => ({ + config.trackedPeriods = msg.data.trackedPeriods.map((p: TrackPeriodFlat) => ({ name: p.name, start: Duration.inflate(p.start), end: Duration.inflate(p.end) @@ -197,13 +193,16 @@ chrome.runtime.onConnect.addListener(function(port) { break; } case MsgType.getConfig: { - let res = {}; - msg.data.forEach(prop => res[prop] = config[prop]); + let res: {[prop: string]: any} = {}; + msg.data.forEach((prop: string) => { + if (prop == 'trackedPeriods') + res.trackedPeriods = config.trackedPeriods.map(p => p.deflate()) + }); port.postMessage(msg.genResp(res)); break; } case MsgType.getGraphData: { - (msg.data.sync ? updateMainGraphData() : Promise.resolve()).then(() => ( + (msg.data.sync ? updateMainGraphData().then(() => {}) : Promise.resolve()).then(() => ( port.postMessage(msg.genResp(mainGraphData.map(d => ({ name: d.name, start: d.start.toISOString(), @@ -213,7 +212,7 @@ chrome.runtime.onConnect.addListener(function(port) { )); break; } - default: console.error("unknown msg type"); + default: console.error("unknown msg opt"); } }); }); diff --git a/src/duration.ts b/src/duration.ts index b42b53b..34ccb91 100644 --- a/src/duration.ts +++ b/src/duration.ts @@ -2,6 +2,11 @@ import moment from 'moment'; export type TimeUnit = moment.unitOfTime.DurationConstructor; +export type DurationFlat = { + value: number, + unit: string +}; + export class Duration { value: number; unit: TimeUnit; @@ -22,11 +27,36 @@ export class Duration { static months(n: number) { return new Duration(n, 'months'); } deflate() { return { value: this.value, unit: this.unit }; } - static inflate = (obj: { value: number, unit: TimeUnit }) => new Duration(obj.value, obj.unit); + static inflate = (obj: DurationFlat) => new Duration(obj.value, obj.unit as TimeUnit); } -export type TrackPeriod = { + +export type TrackPeriodFlat = { name: string, - start: Duration, - end: Duration + start: DurationFlat, + end: DurationFlat }; + +export class TrackPeriod { + name: string; + start: Duration; + end: Duration; + + constructor(name: string, start: Duration, end: Duration) { + this.name = name; + this.start = start; + this.end = end; + } + + deflate() { + return { + name: this.name, + start: this.start.deflate(), + end: this.end.deflate() + }; + } + + static inflate = (obj: TrackPeriodFlat) => ( + new TrackPeriod(obj.name, Duration.inflate(obj.start), Duration.inflate(obj.end)) + ); +} diff --git a/src/graph.ts b/src/graph.ts index 507b671..828e07f 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -1,3 +1,7 @@ +import { GCalendarEvent, GCalendarMeta } from './gapi'; +import { PatternEntry } from './pattern'; +import { defaultChartColor} from './theme'; + export type PatternGraphData = { name: string, value: number, @@ -8,10 +12,13 @@ export type GraphData = { name: string, start: Date, end: Date, - data: PatternGraphData + data: PatternGraphData[] }; -export function getGraphData(start, end, patterns, calendars, calEventsGetter) { +export function getGraphData( + start: Date, end: Date, + patterns: PatternEntry[], calendars: { [id: string]: GCalendarMeta }, + calEventsGetter: (id: string, start: Date, end: Date) => Promise<GCalendarEvent[]>) { if (start >= end) return Promise.resolve({ patternGraphData: [], calendarGraphData: [] }); let event_pms = []; for (let id in calendars) @@ -23,14 +30,16 @@ export function getGraphData(start, end, patterns, calendars, calEventsGetter) { .then(r => { return { id, events: r, filtered }; })); } return Promise.all(event_pms).then(all_events => { - let events = {}; - let patternsByCal = {}; - let results = {}; // pattern idx => time - let cal_results = {}; // cal id => time + let events: {[id: string]: GCalendarEvent[]} = {}; + let patternsByCal: {[id: string]: PatternEntry[]} = {}; + let results: {[idx: number]: number} = {}; + let cal_results: {[id: string]: number} = {}; + all_events.forEach(e => { events[e.id] = e.events; patternsByCal[e.id] = e.filtered; }); + for (let i = 0; i < patterns.length; i++) results[i] = 0; for (let id in calendars) { @@ -41,7 +50,7 @@ export function getGraphData(start, end, patterns, calendars, calEventsGetter) { if (!cal_results.hasOwnProperty(id)) { cal_results[id] = 0; } - let duration = (event.end - event.start) / 60000; + let duration = (event.end.getTime() - event.start.getTime()) / 60000; results[p.idx] += duration; cal_results[id] += duration; }); @@ -49,7 +58,7 @@ export function getGraphData(start, end, patterns, calendars, calEventsGetter) { } let patternGraphData = []; let calendarGraphData = []; - const filterMarginal = data => { + const filterMarginal = (data: PatternGraphData[]) => { let sum = 0; let majorParts = []; let minorSum = 0; diff --git a/src/popup.tsx b/src/popup.tsx index 5474476..ce9f891 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { withStyles, MuiThemeProvider } from '@material-ui/core/styles'; +import { Theme, withStyles, MuiThemeProvider } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; import IconButton from '@material-ui/core/IconButton'; import RefreshIcon from '@material-ui/icons/Refresh'; @@ -12,6 +12,7 @@ import { PatternEntry } from './pattern'; import { Duration } from './duration'; import { MsgType, MsgClient } from './msg'; import { StyledPatternPieChart } from './Chart'; +import { GraphData } from './graph'; import Divider from '@material-ui/core/Divider'; import moment from 'moment'; @@ -19,7 +20,7 @@ function openOptions() { chrome.tabs.create({ url: "index.html" }); } -const styles = theme => ({ +const styles = (theme: Theme) => ({ content: { padding: theme.spacing.unit * 1, overflow: 'auto', @@ -34,24 +35,31 @@ const styles = theme => ({ }, }); -class Popup extends React.Component { +class Popup extends React.Component<{ + classes: { + content: string, + buttons: string, + buttonSpacer: string + } + }> { + msgClient: MsgClient; state = { - patternGraphData: [], + patternGraphData: [] as GraphData[], loading: false, }; - constructor(props) { + constructor(props: any) { super(props); this.msgClient = new MsgClient('main'); - this.loading = true; + this.state.loading = true; this.loadGraphData(false).then(() => this.setState({ loading: false })); } - loadGraphData(sync) { + loadGraphData(sync: boolean) { return this.msgClient.sendMsg({ - type: MsgType.getGraphData, + opt: MsgType.getGraphData, data: { sync } }).then(msg => { - this.setState({ patternGraphData: msg.data.map(d => ({ + this.setState({ patternGraphData: msg.data.map((d: GraphData) => ({ name: d.name, data: d.data, start: new Date(d.start), |