/* global chrome */ 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 Grid from '@material-ui/core/Grid'; import AddCircleIcon from '@material-ui/icons/AddCircle'; import IconButton from '@material-ui/core/IconButton'; import Logo from './Logo'; import * as gapi from './gapi'; 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); }); } const styles = theme => ({ root: { display: 'flex', height: '100vh', }, appBar: { zIndex: theme.zIndex.drawer + 1, transition: theme.transitions.create(['width', 'margin'], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), }, title: { flexGrow: 1, }, sectionTitle: { flex: '0 0 auto' }, appBarSpacer: theme.mixins.toolbar, content: { flexGrow: 1, padding: theme.spacing.unit * 3, overflow: 'auto', }, buttonSpacer: { marginBottom: theme.spacing.unit * 4, }, fab: { margin: theme.spacing.unit, }, }); class Dashboard extends React.Component { state = { patterns: [], calendars: [], timeRange: null, token: gapi.getAuthToken(), patternGraphData: default_chart_data, calendarGraphData: default_chart_data, activePattern: null }; constructor(props) { super(props); let port = chrome.runtime.connect({name: 'main'}); const getCallBack = t => this.requestCallback[t]; port.onMessage.addListener(function(msg) { console.log(msg); let t = getCallBack(msg.type); let e = t.inFlight[msg.id]; console.assert(e !== undefined); t.ids.push(msg.id); e(msg); }); this.port = port; this.requestCallback = {}; this.sendMsg({ type: 1 }).then(msg => { this.setState({ patterns: msg.data.map(p => PatternEntry.revive(p)) }); }); this.sendMsg({ type: 3 }).then(msg => { this.setState({ calendars: msg.data }); }); } updatePattern = (field, idx, value) => { let patterns = this.state.patterns; patterns[idx][field] = value; this.setState({ patterns }); this.sendMsg({ type: 0, 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.sendMsg({ type: 0, 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.sendMsg({ type: 0, data: patterns }); }; loadPatterns = patterns => { this.setState({ patterns }); this.sendMsg({ type: 0, data: patterns }); }; loadCalendars = calendars => { this.setState({ calendars }); this.sendMsg({ type: 5, data: calendars }); }; sendMsg = msg => { if (!this.requestCallback.hasOwnProperty(msg.type)) this.requestCallback[msg.type] = {inFlight: {}, ids: [], maxId: 0}; let t = this.requestCallback[msg.type]; let cb; let pm = new Promise(resolve => { cb = resolve; }); let id; if (t.ids.length > 0) { id = t.ids.pop(); } else { id = t.maxId++; } t.inFlight[id] = cb; msg.id = id; this.port.postMessage(msg); return pm; } getCalEvents = (id, start, end) => { return this.sendMsg({ type: 4, 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 (
Chromicle
Event Patterns this.newPattern()}> Time Range
{ this.setState({ startDate, endDate }); }} focusedInput={this.state.focusedInput} onFocusChange={focusedInput => this.setState({ focusedInput })} isOutsideRange={() => false}/>
Graph
); } } Dashboard.propTypes = { classes: PropTypes.object.isRequired, }; export default withStyles(styles)(Dashboard);