diff options
author | Determinant <[email protected]> | 2019-02-19 01:03:09 -0500 |
---|---|---|
committer | Determinant <[email protected]> | 2019-02-19 01:03:09 -0500 |
commit | a6b9dadfe68921e6c7795518441109e77963ecd9 (patch) | |
tree | c368572137ea7d092fcc1a623609e4faa23ad20c /src | |
parent | 63fe414b6bf556fcc69e7065cfe96a642e54cf7f (diff) |
support new tab page; fix bugs
Diffstat (limited to 'src')
-rw-r--r-- | src/Chart.tsx | 113 | ||||
-rw-r--r-- | src/PatternTable.tsx | 6 | ||||
-rw-r--r-- | src/Settings.tsx | 56 | ||||
-rw-r--r-- | src/background.ts | 39 | ||||
-rw-r--r-- | src/gapi.ts | 5 | ||||
-rw-r--r-- | src/msg.ts | 4 | ||||
-rw-r--r-- | src/tab.html | 17 | ||||
-rw-r--r-- | src/tab.tsx | 135 |
8 files changed, 310 insertions, 65 deletions
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<PatternPieChartProps> { + 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 ( - <Grid item xs={12} lg={6}> - <div style={{height: (props.height ? props.height : 300)}}> - <ResponsivePie - data={props.data.map(p => ({ - 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`} /> - </div> - </Grid> + <div style={{height: (height ? height : 300)}}> + <ResponsivePie + data={data.map(p => ({ + 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`} /> + </div> ); + } } export const StyledPatternPieChart = withStyles(styles)(PatternPieChart); @@ -78,8 +107,12 @@ type DoublePieChartProps = { function DoublePieChart(props: DoublePieChartProps) { return ( <Grid container spacing={0}> + <Grid item xs={12} lg={6}> <StyledPatternPieChart data={props.patternGraphData} height={300} /> + </Grid> + <Grid item xs={12} lg={6}> <StyledPatternPieChart data={props.calendarGraphData} height={300} /> + </Grid> </Grid>); } 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 ( <span> <div className={props.classes.colorSample} - style={{backgroundColor: color ? color.background : defaultChartColor}} + style={{backgroundColor: (color ? color : defaultChartColor)}} onClick={props.colorOnClick}> </div> <TextField @@ -117,7 +117,7 @@ class PatternTable extends React.Component<PatternTableProps> { 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<SettingsProps> { 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<SettingsProps> { 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<SettingsProps> { 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<SettingsProps> { 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<SettingsProps> { } 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<SettingsProps> { 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<SettingsProps> { 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<SettingsProps> { open={this.state.snackBarOpen} variant={this.state.snackBarVariant} onClose={this.handleSnackbarClose}/> - <Typography variant="h6" component="h1" gutterBottom> - General - </Typography> <Table> <TableBody> <TableRow> @@ -464,8 +474,8 @@ class Settings extends React.Component<SettingsProps> { Tracked Time Range </STableCell> <STableCell className={classNames(classes.tableContent, classes.trackedPeriodInput)}> - {this.state.config.trackedPeriods && - this.state.config.trackedPeriods.map((p, idx) => + {this.state.trackedPeriods && + this.state.trackedPeriods.map((p, idx) => <FormGroup key={idx}> <TrackedPeriodInput name={p.name} @@ -477,6 +487,24 @@ class Settings extends React.Component<SettingsProps> { </FormGroup>)} </STableCell> </TableRow> + <TableRow> + <STableCell className={classes.tableHead}> + Misc + </STableCell> + <STableCell className={classes.tableContent}> + <List> + <CompactListItem + key="overrideNewTab" + onClick={() => this.toggleOverrideNewTab()} + disableGutters dense button> + <Checkbox + checked={this.state.overrideNewTab} + disableRipple /> + <ListItemText primary="Show graphs when open a new tab" /> + </CompactListItem> + </List> + </STableCell> + </TableRow> </TableBody> </Table> <div className={classes.bottomButtons}> 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]; @@ -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 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta + name="viewport" + content="width=device-width, initial-scale=1, shrink-to-fit=no" + /> + <meta name="theme-color" content="#000000" /> + <link rel="stylesheet" href="/fonts/TypoPRO-FantasqueSansMono-Regular.css" /> + <title>New Tab</title> + </head> + <body> + <noscript>You need to enable JavaScript to run this app.</noscript> + <div id="root"></div> + </body> +</html> 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<TabProps> { + 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 ( + <MuiThemeProvider theme={theme}> + <CssBaseline /> + <main className={classes.content}> + <div className={classes.buttons}> + <Logo style={{height: 48, verticalAlign: 'bottom'}}/> + <IconButton + disabled={this.state.loading} + style={{float: 'right'}} + onClick={() => ( + new Promise(resolver => ( + this.setState({ loading: true }, resolver))) + .then(() => this.loadGraphData(true)) + .then(() => this.setState({ loading: false })) + )}><RefreshIcon /> + </IconButton> + </div> + <div className={classes.buttonSpacer} /> + <Grid container spacing={0} style={{ maxWidth: 1024, minWidth: 640, margin: '0 auto' }}> + { + (data.length > 0 && + data.map((d, idx) => ( + <Grid item key={idx} xs={12} lg={6}> + <Typography variant="subtitle1" align="center" color="textPrimary"> + {d.name} + </Typography> + <Typography variant="caption" align="center"> + {`${moment(d.start).format('ddd, MMM Do, YYYY')} - + ${moment(d.end).format('ddd, MMM Do, YYYY')}`} + </Typography> + {(d.data.some(dd => dd.value > 1e-3) && + <StyledPatternPieChart + data={d.data} + height={400} + borderWidth={2} + marginTop={60} marginBottom={60} + marginLeft={100} marginRight={100} + radialLabelsLinkDiagonalLength={40} + labelFontSize={14} + padAngle={0.8} /> + ) || + <Typography variant="subtitle1" align="center" color="textSecondary"> + No matching events. + </Typography>} + </Grid> + ))) || ( + <div className={classes.loading}><CircularProgress color="primary" /></div> + ) + } + </Grid> + </main> + </MuiThemeProvider> + ); + } +} + +const StyledTab = withStyles(styles)(Tab); + +ReactDOM.render(<StyledTab />, document.getElementById('root')); |