aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Analyze.tsx5
-rw-r--r--src/Chart.tsx33
-rw-r--r--src/Dashboard.tsx40
-rw-r--r--src/Dialog.tsx8
-rw-r--r--src/Logo.tsx2
-rw-r--r--src/Settings.tsx16
-rw-r--r--src/background.ts85
-rw-r--r--src/duration.ts38
-rw-r--r--src/graph.ts25
-rw-r--r--src/popup.tsx26
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),