From a6b9dadfe68921e6c7795518441109e77963ecd9 Mon Sep 17 00:00:00 2001 From: Determinant Date: Tue, 19 Feb 2019 01:03:09 -0500 Subject: support new tab page; fix bugs --- src/Chart.tsx | 113 +++++++++++++++++++++++++++--------------- src/PatternTable.tsx | 6 +-- src/Settings.tsx | 56 +++++++++++++++------ src/background.ts | 39 +++++++++++++-- src/gapi.ts | 5 +- src/msg.ts | 4 +- src/tab.html | 17 +++++++ src/tab.tsx | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 310 insertions(+), 65 deletions(-) create mode 100644 src/tab.html create mode 100644 src/tab.tsx (limited to 'src') diff --git a/src/Chart.tsx b/src/Chart.tsx index af439f9..e1b9c6d 100644 --- a/src/Chart.tsx +++ b/src/Chart.tsx @@ -18,50 +18,79 @@ type PatternPieChartProps = { pieChart: string }, height?: number, - data: PatternGraphData[] + data: PatternGraphData[], + radialLabelsLinkStrokeWidth?: number, + radialLabelsLinkDiagonalLength?: number, + borderWidth: number, + labelFontSize : number, + marginTop: number, + marginBottom: number, + marginLeft: number, + marginRight: number, + padAngle: number }; -function PatternPieChart(props: PatternPieChartProps) { +export class PatternPieChart extends React.Component { + public static defaultProps = { + radialLabelsLinkStrokeWidth: 1, + borderWidth: 1, + radialLabelsLinkDiagonalLength: 16, + labelFontSize: 12, + marginTop: 40, + marginBottom: 40, + marginLeft: 80, + marginRight: 80, + padAngle: 0.7 + }; + render() { + let { height, data, labelFontSize } = this.props; + const theme = { + labels: { + text: { + fontSize: labelFontSize + } + } + }; return ( - -
- ({ - id: p.name, - label: p.name, - value: p.value, - color: p.color ? p.color: defaultChartColor - }))} - margin={{ - top: 40, - right: 80, - bottom: 40, - left: 80 - }} - innerRadius={0.5} - padAngle={0.7} - cornerRadius={3} - colorBy={d => d.color as string} - borderWidth={1} - borderColor="inherit:darker(0.2)" - radialLabelsSkipAngle={10} - radialLabelsTextXOffset={6} - radialLabelsTextColor="#333333" - radialLabelsLinkOffset={0} - radialLabelsLinkDiagonalLength={16} - radialLabelsLinkHorizontalLength={24} - radialLabelsLinkStrokeWidth={1} - radialLabelsLinkColor="inherit" - sliceLabel={(d) => `${d.value.toFixed(2)} hr`} - slicesLabelsSkipAngle={10} - slicesLabelsTextColor="#ffffff" - animate={true} - motionStiffness={90} - motionDamping={15} - tooltipFormat={v => `${v.toFixed(2)} hr`} /> -
-
+
+ ({ + id: p.name, + label: p.name, + value: p.value, + color: p.color ? p.color: defaultChartColor + }))} + margin={{ + top: this.props.marginTop, + right: this.props.marginRight, + bottom: this.props.marginBottom, + left: this.props.marginLeft + }} + innerRadius={0.5} + padAngle={this.props.padAngle} + cornerRadius={3} + colorBy={d => d.color as string} + borderWidth={this.props.borderWidth} + borderColor="inherit:darker(0.2)" + radialLabelsSkipAngle={10} + radialLabelsTextXOffset={6} + radialLabelsTextColor="#333333" + radialLabelsLinkOffset={0} + radialLabelsLinkDiagonalLength={this.props.radialLabelsLinkDiagonalLength} + radialLabelsLinkHorizontalLength={24} + radialLabelsLinkStrokeWidth={this.props.radialLabelsLinkStrokeWidth} + radialLabelsLinkColor="inherit" + sliceLabel={(d) => `${d.value.toFixed(2)} hr`} + slicesLabelsSkipAngle={10} + slicesLabelsTextColor="#ffffff" + animate={true} + motionStiffness={90} + motionDamping={15} + theme={theme} + tooltipFormat={v => `${v.toFixed(2)} hr`} /> +
); + } } export const StyledPatternPieChart = withStyles(styles)(PatternPieChart); @@ -78,8 +107,12 @@ type DoublePieChartProps = { function DoublePieChart(props: DoublePieChartProps) { return ( + + + + ); } diff --git a/src/PatternTable.tsx b/src/PatternTable.tsx index 3aef72f..b9d0e9e 100644 --- a/src/PatternTable.tsx +++ b/src/PatternTable.tsx @@ -60,12 +60,12 @@ type NameFieldProps = { }; function NameField(props: NameFieldProps) { - let color = props.value.color; + let color = props.value.color.background; return (
{ handleColorPickerClose = () => { this.setState({ colorPickerOpen: false }); - this.activeColorPattern !== null && this.chosenColor !== null && + this.activeColorPattern !== null && this.chosenColor && this.props.onUpdatePattern('color', this.activeColorPattern, { background: this.chosenColor }); } diff --git a/src/Settings.tsx b/src/Settings.tsx index c6e5a00..13488c4 100644 --- a/src/Settings.tsx +++ b/src/Settings.tsx @@ -161,7 +161,8 @@ class Settings extends React.Component { isLoggedIn: false, patterns: [] as PatternEntry[], calendars: {} as {[id: string]: gapi.GCalendarMeta}, - config: {} as { trackedPeriods: TrackedPeriodFlat[] }, + trackedPeriods: [] as TrackedPeriodFlat[], + overrideNewTab: false, snackBarOpen: false, snackBarMsg: 'unknown', snackBarVariant: 'error' as SnackbarVariant, @@ -194,13 +195,14 @@ class Settings extends React.Component { this.msgClient.sendMsg({ opt: MsgType.getConfig, - data: ['trackedPeriods'] + data: ['trackedPeriods', 'overrideNewTab'] }).then(msg => { let config = { - trackedPeriods: msg.data.trackedPeriods + trackedPeriods: msg.data.trackedPeriods, + overrideNewTab: msg.data.overrideNewTab }; console.log(msg.data.trackedPeriods); - this.setState({ config }); + this.setState(config); }); this.dialogPromiseResolver = null; @@ -221,6 +223,7 @@ class Settings extends React.Component { if (!ans) return; try { await gapi.logout(); + await this.msgClient.sendMsg({ opt: MsgType.clearCache, data: {} }); this.setState({ isLoggedIn: false }); } catch (_) { this.openSnackbar("Failed to logout!", 'error' as SnackbarVariant); @@ -253,6 +256,7 @@ class Settings extends React.Component { pms.push(this.loadDefaultPatterns(cals)); await Promise.all(pms); this.setState({ calendarsLoading: false }); + if (reloadAll) this.handleApply(); }; loadDefaultPatterns(calendars: {[ id: string ]: gapi.GCalendarMeta }) { @@ -326,29 +330,29 @@ class Settings extends React.Component { } updateTrackedPeriods = (trackedPeriods: TrackedPeriodFlat[]) => { - this.setState({...this.state.config, trackedPeriods }); + this.setState({ trackedPeriods }); } handlePeriodNameChange = (idx: number) => (name: string) => { - let trackedPeriods = [...this.state.config.trackedPeriods]; + let trackedPeriods = [...this.state.trackedPeriods]; trackedPeriods[idx].name = name; this.updateTrackedPeriods(trackedPeriods); } handlePeriodFromChange = (idx: number) => (duration: DurationFlat) => { - let trackedPeriods = [...this.state.config.trackedPeriods]; + let trackedPeriods = [...this.state.trackedPeriods]; trackedPeriods[idx].start = duration; this.updateTrackedPeriods(trackedPeriods); } handlePeriodToChange = (idx: number) => (duration: DurationFlat) => { - let trackedPeriods = [...this.state.config.trackedPeriods]; + let trackedPeriods = [...this.state.trackedPeriods]; trackedPeriods[idx].end = duration; this.updateTrackedPeriods(trackedPeriods); } handleApply = async () => { - let trackedPeriods = this.state.config.trackedPeriods; + let trackedPeriods = this.state.trackedPeriods; if (trackedPeriods.some(p => ( TrackedPeriodInput.toValue(p.start.value) === null || TrackedPeriodInput.toValue(p.end.value) === null ))) { @@ -368,6 +372,11 @@ class Settings extends React.Component { opt: MsgType.updateConfig, data: { trackedPeriods } }); + let pm4 = this.msgClient.sendMsg({ + opt: MsgType.updateConfig, + data: {'overrideNewTab': this.state.overrideNewTab } + }); + await Promise.all([pm1, pm2, pm3]); this.openSnackbar("Saved changes.", 'success' as SnackbarVariant); } @@ -378,6 +387,10 @@ class Settings extends React.Component { this.loadDefaultPatterns(this.state.calendars); } + toggleOverrideNewTab() { + this.setState({ overrideNewTab: !this.state.overrideNewTab }); + } + render() { const { classes } = this.props; return ( @@ -392,9 +405,6 @@ class Settings extends React.Component { open={this.state.snackBarOpen} variant={this.state.snackBarVariant} onClose={this.handleSnackbarClose}/> - - General - @@ -464,8 +474,8 @@ class Settings extends React.Component { Tracked Time Range - {this.state.config.trackedPeriods && - this.state.config.trackedPeriods.map((p, idx) => + {this.state.trackedPeriods && + this.state.trackedPeriods.map((p, idx) => { )} + + + Misc + + + + this.toggleOverrideNewTab()} + disableGutters dense button> + + + + + +
diff --git a/src/background.ts b/src/background.ts index 39c10c8..9b95061 100644 --- a/src/background.ts +++ b/src/background.ts @@ -14,7 +14,8 @@ let config = { new TrackedPeriod('Today', Duration.days(1), Duration.days(0)), new TrackedPeriod('Yesterday', Duration.days(2), Duration.days(1)), new TrackedPeriod('This Week', Duration.weeks(1), Duration.weeks(0)), - new TrackedPeriod('This Month', Duration.months(1), Duration.months(0))] as TrackedPeriod[] + new TrackedPeriod('This Month', Duration.months(1), Duration.months(0))] as TrackedPeriod[], + overrideNewTab: false }; let mainGraphData: GraphData[] = []; let dirtyMetadata = false; @@ -48,7 +49,8 @@ async function loadMetadata() { else { config = { - trackedPeriods: items.config.trackedPeriods.map((p: TrackedPeriodFlat) => TrackedPeriod.inflate(p)) + trackedPeriods: items.config.trackedPeriods.map((p: TrackedPeriodFlat) => TrackedPeriod.inflate(p)), + overrideNewTab: items.config.overrideNewTab }; calendars = items.calendars; mainPatterns = items.mainPatterns.map((p: PatternEntryFlat) => PatternEntry.inflate(p)); @@ -64,7 +66,8 @@ async function saveMetadata() { await chromeStorageSet({ calendars, config: { - trackedPeriods: config.trackedPeriods.map(p => p.deflate()) + trackedPeriods: config.trackedPeriods.map(p => p.deflate()), + overrideNewTab: config.overrideNewTab }, mainPatterns: mainPatterns.map(p => p.deflate()), analyzePatterns: analyzePatterns.map(p => p.deflate()) @@ -169,7 +172,10 @@ async function pollSync() { let pms = []; for (let id in calendars) { if (!calendars[id].enabled) continue; - pms.push(getCalData(id).sync()); + pms.push(getCalData(id).sync().catch(err => { + console.log(`cannot sync calendar ${id}`, err); + calendars[id].enabled = false; + })); } await Promise.all(pms); /* update the tracked graph data */ @@ -239,7 +245,13 @@ function handleMsg(port: chrome.runtime.Port) { break; } case MsgType.updateConfig: { - config.trackedPeriods = msg.data.trackedPeriods.map((p: TrackedPeriodFlat) => TrackedPeriod.inflate(p)); + for (let prop in msg.data) { + if (prop === 'trackedPeriods') { + config.trackedPeriods = msg.data.trackedPeriods.map((p: TrackedPeriodFlat) => TrackedPeriod.inflate(p)); + } else if (prop == 'overrideNewTab') { + config.overrideNewTab = msg.data.overrideNewTab as boolean; + } + } dirtyMetadata = true; port.postMessage(msg.genResp(null)); break; @@ -249,6 +261,8 @@ function handleMsg(port: chrome.runtime.Port) { msg.data.forEach((prop: string) => { if (prop === 'trackedPeriods') res.trackedPeriods = config.trackedPeriods.map(p => p.deflate()); + else if (prop === 'overrideNewTab') + res.overrideNewTab = config.overrideNewTab; }); port.postMessage(msg.genResp(res)); break; @@ -270,6 +284,11 @@ function handleMsg(port: chrome.runtime.Port) { })(); break; } + case MsgType.clearCache: { + calData = {}; + port.postMessage(msg.genResp(null)); + break; + } default: console.error("unknown msg opt"); } }); @@ -281,3 +300,13 @@ loadPromise = (async () => { })(); chrome.runtime.onConnect.addListener(handleMsg); + +chrome.tabs.onCreated.addListener(function(tab) { + if (tab.url === "chrome://newtab/") { + if (config.overrideNewTab) { + chrome.tabs.update(tab.id, { + url: chrome.extension.getURL("tab.html") + }); + } + } +}); diff --git a/src/gapi.ts b/src/gapi.ts index 2252230..8e74483 100644 --- a/src/gapi.ts +++ b/src/gapi.ts @@ -332,8 +332,7 @@ export class GCalendar { addEvent(e: {start: Date, end: Date, id: string, summary: string}, evict = false) { //console.log('adding event', e); - if (this.eventMeta.hasOwnProperty(e.id)) - this.removeEvent(e); + this.removeEvent(e); let r = this.dateRangeToCacheKeys(e); let ks = r.start; let ke = r.end; @@ -361,6 +360,8 @@ export class GCalendar { } removeEvent(e: {id: string}) { + if (!this.eventMeta.hasOwnProperty(e.id)) + return; let keys = this.eventMeta[e.id].keys; keys.forEach(k => delete this.getSlot(k)[e.id]); delete this.eventMeta[e.id]; diff --git a/src/msg.ts b/src/msg.ts index efbadb5..daef6f9 100644 --- a/src/msg.ts +++ b/src/msg.ts @@ -8,7 +8,8 @@ export enum MsgType { getCalEvents = "getCalEvents", updateConfig = "updateConfig", getConfig = "getConfig", - getGraphData = "getGraphData" + getGraphData = "getGraphData", + clearCache = "clearCache" } function stringifyMsgType(opt: MsgType): string { return MsgType[opt]; } @@ -23,6 +24,7 @@ function parseMsgType(s: string): MsgType { case "updateConfig": return MsgType.updateConfig; case "getConfig": return MsgType.getConfig; case "getGraphData": return MsgType.getGraphData; + case "clearCache": return MsgType.clearCache; default: console.error(`unknown MsgType: ${s}`); } } diff --git a/src/tab.html b/src/tab.html new file mode 100644 index 0000000..deaa3ec --- /dev/null +++ b/src/tab.html @@ -0,0 +1,17 @@ + + + + + + + + New Tab + + + +
+ + diff --git a/src/tab.tsx b/src/tab.tsx new file mode 100644 index 0000000..e720844 --- /dev/null +++ b/src/tab.tsx @@ -0,0 +1,135 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Theme, withStyles, StyleRules, MuiThemeProvider } 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 IconButton from '@material-ui/core/IconButton'; +import RefreshIcon from '@material-ui/icons/Refresh'; +import Grid from '@material-ui/core/Grid'; +import CircularProgress from '@material-ui/core/CircularProgress'; + +import Logo from './Logo'; +import { theme } from './theme'; +import { StyledPatternPieChart } from './Chart'; +import { MsgType, MsgClient } from './msg'; +import { GraphData } from './graph'; +import moment from 'moment'; + +const styles = (theme: Theme): StyleRules => ({ + content: { + padding: theme.spacing.unit * 1, + overflow: 'auto', + }, + buttons: { + width: '100%', + height: 0, + lineHeight: '48px' + }, + buttonSpacer: { + marginBottom: theme.spacing.unit * 2, + }, + loading: { + textAlign: 'center' + } +}); + +type TabProps = { + classes: { + content: string, + buttons: string, + buttonSpacer: string, + loading: string + } +}; + + +class Tab extends React.Component { + msgClient: MsgClient; + state = { + patternGraphData: [] as GraphData[], + loading: false, + }; + constructor(props: TabProps) { + super(props); + this.msgClient = new MsgClient('main'); + this.state.loading = true; + this.loadGraphData(false).then(() => this.setState({ loading: false })); + } + + loadGraphData(sync: boolean) { + return this.msgClient.sendMsg({ + opt: MsgType.getGraphData, + data: { sync } + }).then(msg => { + this.setState({ patternGraphData: msg.data.map((d: GraphData) => ({ + name: d.name, + data: d.data, + start: new Date(d.start), + end: new Date(d.end) + }))}); + }); + } + + render() { + let { classes } = this.props; + let data = this.state.patternGraphData; + return ( + + +
+
+ + ( + new Promise(resolver => ( + this.setState({ loading: true }, resolver))) + .then(() => this.loadGraphData(true)) + .then(() => this.setState({ loading: false })) + )}> + +
+
+ + { + (data.length > 0 && + data.map((d, idx) => ( + + + {d.name} + + + {`${moment(d.start).format('ddd, MMM Do, YYYY')} - + ${moment(d.end).format('ddd, MMM Do, YYYY')}`} + + {(d.data.some(dd => dd.value > 1e-3) && + + ) || + + No matching events. + } + + ))) || ( +
+ ) + } +
+
+
+ ); + } +} + +const StyledTab = withStyles(styles)(Tab); + +ReactDOM.render(, document.getElementById('root')); -- cgit v1.2.3