From c594888953151ddfb4ca04b7752bfd51edc1d6da Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 13 Feb 2019 01:11:31 -0500 Subject: WIP: migrate to TypeScriptX --- .babelrc | 2 +- package-lock.json | 109 +++- package.json | 7 + src/Analyze.js | 266 -------- src/Analyze.tsx | 270 ++++++++ src/Chart.js | 165 ----- src/Chart.tsx | 165 +++++ src/Dashboard.js | 98 --- src/Dashboard.tsx | 98 +++ src/Dialog.js | 45 -- src/Dialog.tsx | 45 ++ src/Logo.js | 34 - src/Logo.tsx | 34 + src/PatternTable.js | 208 ------- src/PatternTable.tsx | 208 +++++++ src/RegexField.js | 98 --- src/RegexField.tsx | 98 +++ src/Settings.js | 437 ------------- src/Settings.tsx | 437 +++++++++++++ src/Snackbar.js | 78 --- src/Snackbar.tsx | 78 +++ src/background.js | 217 ------- src/background.ts | 217 +++++++ src/decl.ts | 1 + src/duration.js | 21 - src/duration.ts | 26 + src/gapi.js | 335 ---------- src/gapi.ts | 369 +++++++++++ src/index.js | 11 - src/index.tsx | 5 + src/msg.js | 97 --- src/msg.ts | 87 +++ src/pattern.js | 48 -- src/pattern.ts | 84 +++ src/popup.js | 118 ---- src/popup.tsx | 112 ++++ src/serviceWorker.js | 135 ---- src/theme.js | 18 - src/theme.tsx | 18 + tsconfig.json | 16 + types/lru-cache/index.d.ts | 10 + types/lru-cache/log | 1485 ++++++++++++++++++++++++++++++++++++++++++++ webpack.config.js | 19 +- 43 files changed, 3991 insertions(+), 2438 deletions(-) delete mode 100644 src/Analyze.js create mode 100644 src/Analyze.tsx delete mode 100644 src/Chart.js create mode 100644 src/Chart.tsx delete mode 100644 src/Dashboard.js create mode 100644 src/Dashboard.tsx delete mode 100644 src/Dialog.js create mode 100644 src/Dialog.tsx delete mode 100644 src/Logo.js create mode 100644 src/Logo.tsx delete mode 100644 src/PatternTable.js create mode 100644 src/PatternTable.tsx delete mode 100644 src/RegexField.js create mode 100644 src/RegexField.tsx delete mode 100644 src/Settings.js create mode 100644 src/Settings.tsx delete mode 100644 src/Snackbar.js create mode 100644 src/Snackbar.tsx delete mode 100644 src/background.js create mode 100644 src/background.ts create mode 100644 src/decl.ts delete mode 100644 src/duration.js create mode 100644 src/duration.ts delete mode 100644 src/gapi.js create mode 100644 src/gapi.ts delete mode 100644 src/index.js create mode 100644 src/index.tsx delete mode 100644 src/msg.js create mode 100644 src/msg.ts delete mode 100644 src/pattern.js create mode 100644 src/pattern.ts delete mode 100644 src/popup.js create mode 100644 src/popup.tsx delete mode 100644 src/serviceWorker.js delete mode 100644 src/theme.js create mode 100644 src/theme.tsx create mode 100644 tsconfig.json create mode 100644 types/lru-cache/index.d.ts create mode 100644 types/lru-cache/log diff --git a/.babelrc b/.babelrc index 58a023d..2775ac7 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,4 @@ { - "presets": ["@babel/preset-env", "@babel/preset-react"], + "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"], "plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime"] } diff --git a/package-lock.json b/package-lock.json index d5894e7..331f290 100644 --- a/package-lock.json +++ b/package-lock.json @@ -424,6 +424,15 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-syntax-typescript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.2.0.tgz", + "integrity": "sha512-WhKr6yu6yGpGcNMVgIBuI9MkredpVc7Y3YR4UzEZmDztHoL6wV56YBHLhWnjO1EvId1B32HrD3DRFc+zSoKI1g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, "@babel/plugin-transform-arrow-functions": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", @@ -743,6 +752,16 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-transform-typescript": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.3.2.tgz", + "integrity": "sha512-Pvco0x0ZSCnexJnshMfaibQ5hnK8aUHSvjCQhC1JR8eeg+iBwt0AtCO7gWxJ358zZevuf9wPSO5rv+WJcbHPXQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-typescript": "^7.2.0" + } + }, "@babel/plugin-transform-unicode-regex": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz", @@ -836,6 +855,16 @@ "@babel/plugin-transform-react-jsx-source": "^7.0.0" } }, + "@babel/preset-typescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.1.0.tgz", + "integrity": "sha512-LYveByuF9AOM8WrsNne5+N79k1YxjNB6gmpCQsnuSBAcV8QUeB+ZUxQzL7Rz7HksPbahymKkq2qBR+o36ggFZA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.1.0" + } + }, "@babel/runtime": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.1.tgz", @@ -964,6 +993,27 @@ "react-is": "^16.6.3" } }, + "@types/chrome": { + "version": "0.0.79", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.79.tgz", + "integrity": "sha512-4+Xducpig6lpwVX65Hk8KSZwRoURHXMDbd38SDNcV8TBaw4xyJki39fjB1io2h7ip+BsyFvgTm9OxR5qneLPiA==", + "requires": { + "@types/filesystem": "*" + } + }, + "@types/filesystem": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.29.tgz", + "integrity": "sha512-85/1KfRedmfPGsbK8YzeaQUyV1FQAvMPMTuWFQ5EkLd2w7szhNO96bk3Rh/SKmOfd9co2rCLf0Voy4o7ECBOvw==", + "requires": { + "@types/filewriter": "*" + } + }, + "@types/filewriter": { + "version": "0.0.28", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.28.tgz", + "integrity": "sha1-wFTor02d11205jq8dviFFocU1LM=" + }, "@types/jss": { "version": "9.5.7", "resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.7.tgz", @@ -1416,6 +1466,15 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, "async-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", @@ -1765,6 +1824,23 @@ "ssri": "^6.0.1", "unique-filename": "^1.1.1", "y18n": "^4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } } }, "cache-base": { @@ -4907,7 +4983,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "requires": { "yallist": "^3.0.2" } @@ -6822,6 +6897,16 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, + "source-map-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "dev": true, + "requires": { + "async": "^2.5.0", + "loader-utils": "^1.1.0" + } + }, "source-map-resolve": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", @@ -7168,6 +7253,19 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, + "ts-loader": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.3.3.tgz", + "integrity": "sha512-KwF1SplmOJepnoZ4eRIloH/zXL195F51skt7reEsS6jvDqzgc/YSbz9b8E07GxIUwLXdcD4ssrJu6v8CwaTafA==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^1.0.2", + "micromatch": "^3.1.4", + "semver": "^5.0.1" + } + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", @@ -7200,6 +7298,12 @@ "resolved": "https://registry.npmjs.org/typeface-roboto/-/typeface-roboto-0.0.54.tgz", "integrity": "sha512-sOFA1FXgP0gOgBYlS6irwq6hHYA370KE3dPlgYEJHL3PJd5X8gQE0RmL79ONif6fL5JZuGDj+rtOrFeOqz5IZQ==" }, + "typescript": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3.tgz", + "integrity": "sha512-Y21Xqe54TBVp+VDSNbuDYdGw0BpoR/Q6wo/+35M8PAU0vipahnyduJWirxxdxjsAkS7hue53x2zp8gz7F05u0A==", + "dev": true + }, "ua-parser-js": { "version": "0.7.19", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz", @@ -7657,8 +7761,7 @@ "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" }, "yargs": { "version": "12.0.5", diff --git a/package.json b/package.json index 6914549..75f0a66 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@babel/polyfill": "^7.2.5", "@babel/preset-env": "^7.3.1", "@babel/preset-react": "^7.0.0", + "@babel/preset-typescript": "^7.1.0", "babel-eslint": "^10.0.1", "babel-loader": "^8.0.5", "copy-webpack-plugin": "^4.6.0", @@ -28,7 +29,10 @@ "eslint-loader": "^2.1.2", "eslint-plugin-react": "^7.12.4", "html-webpack-plugin": "^4.0.0-beta.5", + "source-map-loader": "^0.2.4", "style-loader": "^0.23.1", + "ts-loader": "^5.3.3", + "typescript": "^3.3.3", "url-loader": "^1.1.2", "webpack": "^4.29.2", "webpack-cli": "^3.2.3" @@ -36,6 +40,9 @@ "dependencies": { "@material-ui/core": "^3.9.2", "@material-ui/icons": "^3.0.2", + "@types/chrome": "0.0.79", + "@types/react": "^16.8.2", + "lru-cache": "^5.1.1", "moment": "^2.24.0", "react": "^16.8.1", "react-dates": "^19.0.0", diff --git a/src/Analyze.js b/src/Analyze.js deleted file mode 100644 index 98e3ce2..0000000 --- a/src/Analyze.js +++ /dev/null @@ -1,266 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import 'react-dates/initialize'; -import 'react-dates/lib/css/_datepicker.css'; -import { DateRangePicker } from 'react-dates'; -import { withStyles } from '@material-ui/core/styles'; -import cyan from '@material-ui/core/colors/cyan'; -import deepOrange from '@material-ui/core/colors/deepOrange'; -import CssBaseline from '@material-ui/core/CssBaseline'; -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 * as gapi from './gapi'; -import { msgType, MsgClient } from './msg'; -import { Pattern, PatternEntry } from './pattern'; -import { AnalyzePieChart, getChartData } from './Chart'; -import PatternTable from './PatternTable'; -import Snackbar from './Snackbar'; -import AlertDialog from './Dialog'; - -const default_chart_data = [ - {name: 'Work', value: 10, color: cyan[300]}, - {name: 'Wasted', value: 10, color: deepOrange[300]}]; - -const styles = theme => ({ - buttonSpacer: { - marginBottom: theme.spacing.unit * 4, - }, -}); - -class Analyze extends React.Component { - state = { - patterns: [], - calendars: {}, - startDate: null, - endDate: null, - patternGraphData: default_chart_data, - calendarGraphData: default_chart_data, - snackBarOpen: false, - snackBarMsg: 'unknown', - snackBarVariant: 'error', - dialogOpen: false, - dialogMsg: {title: '', message: ''}, - }; - - constructor(props) { - super(props); - this.msgClient = new MsgClient('main'); - - this.msgClient.sendMsg({ - type: msgType.getPatterns, - data: { id: 'analyze' } - }).then(msg => { - this.setState({ patterns: msg.data.map(p => PatternEntry.inflate(p)) }); - }); - - this.msgClient.sendMsg({ - type: msgType.getCalendars, - data: { enabledOnly: true } - }).then(msg => { - this.setState({ calendars: msg.data }); - }); - - gapi.getLoggedIn().then(b => !b && - this.handleSnackbarOpen('Not logged in. Operating in offline mode.', 'warning')); - - this.dialogPromiseResolver = null; - } - - loadPatterns = patterns => { - this.msgClient.sendMsg({ - type: msgType.updatePatterns, - data: { id: 'analyze', patterns: patterns.map(p => p.deflate()) } - }).then(() => this.setState({ patterns })); - }; - - updatePattern = (field, idx, value) => { - let patterns = this.state.patterns; - patterns[idx][field] = value; - this.loadPatterns(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.loadPatterns(patterns); - }; - - newPattern = () => { - let patterns = [PatternEntry.defaultPatternEntry(0), ...this.state.patterns]; - for (let i = 1; i < patterns.length; i++) - patterns[i].idx = i; - this.loadPatterns(patterns); - }; - - getCalEvents = (id, start, end) => { - return this.msgClient.sendMsg({ type: msgType.getCalEvents, 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)) { - this.handleSnackbarOpen('Please choose a valid time range.', 'error'); - return; - } - let start = this.state.startDate.startOf('day').toDate(); - let end = this.state.endDate.startOf('day').toDate(); - getChartData(start, end, - this.state.patterns, - this.state.calendars, - this.getCalEvents).then(results => { - this.setState(results); - }); - } - - reset = () => { - this.handleDialogOpen("Reset", "Are you sure to reset the patterns?").then(ans => { - if (!ans) return; - this.loadPatterns([]); - this.setState({ startDate: null, endDate: null }); - }); - } - - loadDefaultPatterns() { - let patterns = []; - let idx = 0; - for (let id in this.state.calendars) { - let cal = this.state.calendars[id]; - if (!cal.enabled) continue; - patterns.push(new PatternEntry(cal.name, idx++, - new Pattern(id, false, cal.name, cal.name), - Pattern.anyPattern(), - cal.color)); - } - console.log(patterns); - this.loadPatterns(patterns); - } - - default = () => { - this.handleDialogOpen("Load Default", "Load the calendars as patterns?").then(ans => { - if (!ans) return; - this.loadDefaultPatterns(); - }); - } - - handleSnackbarClose = (event, reason) => { - if (reason === 'clickaway') return; - this.setState({ snackBarOpen: false }); - } - - handleSnackbarOpen = (msg, variant) => { - this.setState({ snackBarOpen: true, snackBarMsg: msg, snackBarVariant: variant }); - } - - handleDialogOpen = (title, message) => { - let pm = new Promise(resolver => { - this.dialogPromiseResolver = resolver - }); - this.setState({ dialogOpen: true, dialogMsg: {title, message} }); - return pm; - } - - handleDialogClose = result => { - this.dialogPromiseResolver(result); - this.setState({ dialogOpen: false }); - } - - render() { - const { classes } = this.props; - - return ( - - - - - - - - Analyzed Events - this.newPattern()}> - - - - - - Time Range - -
- { - this.setState({ startDate, endDate }); - }} - focusedInput={this.state.focusedInput} - onFocusChange={focusedInput => this.setState({ focusedInput })} - isOutsideRange={() => false} /> -
-
-
- - - - - - - - - - - - - - - - - - - - - - Results - - - - - ); - } -} - -Analyze.propTypes = { - classes: PropTypes.object.isRequired, -}; - -export default withStyles(styles)(Analyze); diff --git a/src/Analyze.tsx b/src/Analyze.tsx new file mode 100644 index 0000000..5450998 --- /dev/null +++ b/src/Analyze.tsx @@ -0,0 +1,270 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import 'react-dates/initialize'; +import 'react-dates/lib/css/_datepicker.css'; +import { DateRangePicker } from 'react-dates'; +import { Theme, withStyles } from '@material-ui/core/styles'; +import cyan from '@material-ui/core/colors/cyan'; +import deepOrange from '@material-ui/core/colors/deepOrange'; +import CssBaseline from '@material-ui/core/CssBaseline'; +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 * as gapi from './gapi'; +import { MsgType, MsgClient } from './msg'; +import { Pattern, PatternEntry } from './pattern'; +import { AnalyzePieChart, getChartData } from './Chart'; +import PatternTable from './PatternTable'; +import Snackbar from './Snackbar'; +import AlertDialog from './Dialog'; +import moment from 'moment'; + +const default_chart_data = [ + {name: 'Work', value: 10, color: cyan[300]}, + {name: 'Wasted', value: 10, color: deepOrange[300]}]; + +const styles = (theme: Theme) => ({ + buttonSpacer: { + marginBottom: theme.spacing.unit * 4, + }, +}); + +class Analyze extends React.Component { + msgClient: MsgClient; + + state = { + patterns: [] as PatternEntry[], + calendars: {}, + startDate: null as moment.Moment, + endDate: null as moment.Moment, + patternGraphData: default_chart_data, + calendarGraphData: default_chart_data, + snackBarOpen: false, + snackBarMsg: 'unknown', + snackBarVariant: 'error', + dialogOpen: false, + dialogMsg: {title: '', message: ''} + }; + + constructor(props: any) { + super(props); + + this.msgClient = new MsgClient('main'); + + this.msgClient.sendMsg({ + type: MsgType.getPatterns, + data: { id: 'analyze' } + }).then(msg => { + this.setState({ patterns: msg.data.map(p => PatternEntry.inflate(p)) }); + }); + + this.msgClient.sendMsg({ + type: MsgType.getCalendars, + data: { enabledOnly: true } + }).then(msg => { + this.setState({ calendars: msg.data }); + }); + + gapi.getLoggedIn().then(b => !b && + this.handleSnackbarOpen('Not logged in. Operating in offline mode.', 'warning')); + + this.dialogPromiseResolver = null; + } + + loadPatterns = (patterns: PatternEntry[]) => { + this.msgClient.sendMsg({ + type: MsgType.updatePatterns, + data: { id: 'analyze', patterns: patterns.map(p => p.deflate()) } + }).then(() => this.setState({ patterns })); + }; + + updatePattern = (field: string, idx: number, value: PatternEntry[]) => { + let patterns = this.state.patterns; + patterns[idx][field] = value; + this.loadPatterns(patterns); + }; + + removePattern = (idx: number) => { + let patterns = this.state.patterns; + patterns.splice(idx, 1); + for (let i = 0; i < patterns.length; i++) + patterns[i].idx = i; + this.loadPatterns(patterns); + }; + + newPattern = () => { + let patterns = [PatternEntry.defaultPatternEntry(0), ...this.state.patterns]; + for (let i = 1; i < patterns.length; i++) + patterns[i].idx = i; + this.loadPatterns(patterns); + }; + + getCalEvents = (id: string, start: Date, end: Date) => { + return this.msgClient.sendMsg({ type: MsgType.getCalEvents, 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)) { + this.handleSnackbarOpen('Please choose a valid time range.', 'error'); + return; + } + let start = this.state.startDate.startOf('day').toDate(); + let end = this.state.endDate.startOf('day').toDate(); + getChartData(start, end, + this.state.patterns, + this.state.calendars, + this.getCalEvents).then(results => { + this.setState(results); + }); + } + + reset = () => { + this.handleDialogOpen("Reset", "Are you sure to reset the patterns?").then(ans => { + if (!ans) return; + this.loadPatterns([]); + this.setState({ startDate: null, endDate: null }); + }); + } + + loadDefaultPatterns() { + let patterns = []; + let idx = 0; + for (let id in this.state.calendars) { + let cal = this.state.calendars[id]; + if (!cal.enabled) continue; + patterns.push(new PatternEntry(cal.name, idx++, + new Pattern(id, false, cal.name, cal.name), + Pattern.anyPattern(), + cal.color)); + } + console.log(patterns); + this.loadPatterns(patterns); + } + + default = () => { + this.handleDialogOpen("Load Default", "Load the calendars as patterns?").then(ans => { + if (!ans) return; + this.loadDefaultPatterns(); + }); + } + + handleSnackbarClose = (event, reason) => { + if (reason === 'clickaway') return; + this.setState({ snackBarOpen: false }); + } + + handleSnackbarOpen = (msg, variant) => { + this.setState({ snackBarOpen: true, snackBarMsg: msg, snackBarVariant: variant }); + } + + handleDialogOpen = (title, message) => { + let pm = new Promise(resolver => { + this.dialogPromiseResolver = resolver + }); + this.setState({ dialogOpen: true, dialogMsg: {title, message} }); + return pm; + } + + handleDialogClose = result => { + this.dialogPromiseResolver(result); + this.setState({ dialogOpen: false }); + } + + render() { + const { classes } = this.props; + + return ( + + + + + + + + Analyzed Events + this.newPattern()}> + + + + + + Time Range + +
+ { + this.setState({ startDate, endDate }); + }} + focusedInput={this.state.focusedInput} + onFocusChange={focusedInput => this.setState({ focusedInput })} + isOutsideRange={() => false} /> +
+
+
+ + + + + + + + + + + + + + + + + + + + + + Results + + + + + ); + } +} + +Analyze.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Analyze); diff --git a/src/Chart.js b/src/Chart.js deleted file mode 100644 index b1c36ed..0000000 --- a/src/Chart.js +++ /dev/null @@ -1,165 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { 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'; - -export function getChartData(start, end, patterns, calendars, calEventsGetter) { - if (start >= end) return Promise.resolve({ patternGraphData: [], calendarGraphData: [] }); - let event_pms = []; - for (let id in calendars) - { - if (!calendars[id].enabled) continue; - let filtered = patterns.filter(p => p.cal.regex.test(calendars[id].name)); - if (filtered.length > 0) - event_pms.push(calEventsGetter(id, start, end) - .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 - 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) { - if (!events[id]) continue; - events[id].forEach(event => { - patternsByCal[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 = []; - const filterMarginal = data => { - let sum = 0; - let majorParts = []; - let minorSum = 0; - data.forEach(d => sum += d.value); - data.forEach(d => { - let ratio = d.value / sum; - if (ratio < 1e-2) minorSum += d.value; - else majorParts.push(d); - }); - majorParts.push({ - name: 'Other', - value: minorSum, - color: defaultChartColor, - }); - return majorParts; - }; - for (let i = 0; i < patterns.length; i++) { - patternGraphData.push({ - name: patterns[i].name, - value: results[i] / 60.0, - color: patterns[i].color.background}); - } - for (let id in cal_results) { - calendarGraphData.push({ - name: calendars[id].name, - value: (cal_results[id] / 60.0), - color: calendars[id].color.background}); - } - return {start, end, - patternGraphData: filterMarginal(patternGraphData), - calendarGraphData: filterMarginal(calendarGraphData) }; - }); -} - -const styles = theme => ({ - pieChart: { - margin: '0 auto', - } -}); - -function customizedLabel(props) { - const {cx, cy, x, y, fill, name} = props; - let anchor = "middle"; - const EPS = 2; - let dx = 0; - let dy = 0; - if (x < cx - EPS) { - dx = -5; - anchor = "end" - } else if (x > cx + EPS) { - dx = 5; - anchor = "start"; - } - - if (y < cy - EPS) { - dy = -5; - } else if (y > cy + EPS) { - dy = 10; - } - - return ({`${name}`}); -} - -function PatternPieChart(props) { - return ( - -
- - - {props.data.map((d, i) => )} - - `${value.toFixed(2)} hr`}/> - -
-
- ); -} - -export const StyledPatternPieChart = withStyles(styles)(PatternPieChart); - -function DoublePieChart(props) { - return ( - - - -
- - - {props.calendarGraphData.map((d, i) => )} - - `${value.toFixed(2)} hr`}/> - -
-
-
); -} - -DoublePieChart.propTypes = { - patternGraphData: PropTypes.array.isRequired, - calendarGraphData: PropTypes.array.isRequired, -}; - -export const AnalyzePieChart = withStyles(styles)(DoublePieChart); diff --git a/src/Chart.tsx b/src/Chart.tsx new file mode 100644 index 0000000..b1c36ed --- /dev/null +++ b/src/Chart.tsx @@ -0,0 +1,165 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { 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'; + +export function getChartData(start, end, patterns, calendars, calEventsGetter) { + if (start >= end) return Promise.resolve({ patternGraphData: [], calendarGraphData: [] }); + let event_pms = []; + for (let id in calendars) + { + if (!calendars[id].enabled) continue; + let filtered = patterns.filter(p => p.cal.regex.test(calendars[id].name)); + if (filtered.length > 0) + event_pms.push(calEventsGetter(id, start, end) + .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 + 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) { + if (!events[id]) continue; + events[id].forEach(event => { + patternsByCal[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 = []; + const filterMarginal = data => { + let sum = 0; + let majorParts = []; + let minorSum = 0; + data.forEach(d => sum += d.value); + data.forEach(d => { + let ratio = d.value / sum; + if (ratio < 1e-2) minorSum += d.value; + else majorParts.push(d); + }); + majorParts.push({ + name: 'Other', + value: minorSum, + color: defaultChartColor, + }); + return majorParts; + }; + for (let i = 0; i < patterns.length; i++) { + patternGraphData.push({ + name: patterns[i].name, + value: results[i] / 60.0, + color: patterns[i].color.background}); + } + for (let id in cal_results) { + calendarGraphData.push({ + name: calendars[id].name, + value: (cal_results[id] / 60.0), + color: calendars[id].color.background}); + } + return {start, end, + patternGraphData: filterMarginal(patternGraphData), + calendarGraphData: filterMarginal(calendarGraphData) }; + }); +} + +const styles = theme => ({ + pieChart: { + margin: '0 auto', + } +}); + +function customizedLabel(props) { + const {cx, cy, x, y, fill, name} = props; + let anchor = "middle"; + const EPS = 2; + let dx = 0; + let dy = 0; + if (x < cx - EPS) { + dx = -5; + anchor = "end" + } else if (x > cx + EPS) { + dx = 5; + anchor = "start"; + } + + if (y < cy - EPS) { + dy = -5; + } else if (y > cy + EPS) { + dy = 10; + } + + return ({`${name}`}); +} + +function PatternPieChart(props) { + return ( + +
+ + + {props.data.map((d, i) => )} + + `${value.toFixed(2)} hr`}/> + +
+
+ ); +} + +export const StyledPatternPieChart = withStyles(styles)(PatternPieChart); + +function DoublePieChart(props) { + return ( + + + +
+ + + {props.calendarGraphData.map((d, i) => )} + + `${value.toFixed(2)} hr`}/> + +
+
+
); +} + +DoublePieChart.propTypes = { + patternGraphData: PropTypes.array.isRequired, + calendarGraphData: PropTypes.array.isRequired, +}; + +export const AnalyzePieChart = withStyles(styles)(DoublePieChart); diff --git a/src/Dashboard.js b/src/Dashboard.js deleted file mode 100644 index 04ced46..0000000 --- a/src/Dashboard.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import 'typeface-roboto'; -import { 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 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 Logo from './Logo'; -import { theme } from './theme'; -import Analyze from './Analyze'; -import Settings from './Settings'; - -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, - display: 'inline-block' - }, - appBarSpacer: theme.mixins.toolbar, - content: { - flexGrow: 1, - padding: theme.spacing.unit * 3, - overflow: 'auto', - }, - indicator: { - backgroundColor: theme.palette.primary.contrastText - } -}); - -class DashboardTabs extends React.Component { - handleChangeTab = (event, currentTab) => { - this.props.history.push(currentTab); - } - render() { - const { classes } = this.props; - return ( -
- - - - Chromicle - - - - - - - - -
-
- - - }/> -
-
- ); - } -} - -DashboardTabs.propTypes = { - classes: PropTypes.object.isRequired, -}; - -class Dashboard extends React.Component { - render() { - const { classes } = this.props; - let Tabs = withRouter(withStyles(styles)(DashboardTabs)); - return ( - - - ); - } -} - -export default Dashboard; diff --git a/src/Dashboard.tsx b/src/Dashboard.tsx new file mode 100644 index 0000000..04ced46 --- /dev/null +++ b/src/Dashboard.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import 'typeface-roboto'; +import { 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 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 Logo from './Logo'; +import { theme } from './theme'; +import Analyze from './Analyze'; +import Settings from './Settings'; + +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, + display: 'inline-block' + }, + appBarSpacer: theme.mixins.toolbar, + content: { + flexGrow: 1, + padding: theme.spacing.unit * 3, + overflow: 'auto', + }, + indicator: { + backgroundColor: theme.palette.primary.contrastText + } +}); + +class DashboardTabs extends React.Component { + handleChangeTab = (event, currentTab) => { + this.props.history.push(currentTab); + } + render() { + const { classes } = this.props; + return ( +
+ + + + Chromicle + + + + + + + + +
+
+ + + }/> +
+
+ ); + } +} + +DashboardTabs.propTypes = { + classes: PropTypes.object.isRequired, +}; + +class Dashboard extends React.Component { + render() { + const { classes } = this.props; + let Tabs = withRouter(withStyles(styles)(DashboardTabs)); + return ( + + + ); + } +} + +export default Dashboard; diff --git a/src/Dialog.js b/src/Dialog.js deleted file mode 100644 index 7e24176..0000000 --- a/src/Dialog.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogContentText from '@material-ui/core/DialogContentText'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import Button from '@material-ui/core/Button'; -import Slide from '@material-ui/core/Slide'; - -// modified from https://material-ui.com/demos/dialogs/ - -function Transition(props) { - return ; -} - -function AlertDialog(props) { - return ( - props.handleClose(false)} - aria-labelledby="alert-dialog-slide-title" - aria-describedby="alert-dialog-slide-description"> - - {props.title} - - - - {props.message} - - - - - - - - ); -} - -export default AlertDialog; diff --git a/src/Dialog.tsx b/src/Dialog.tsx new file mode 100644 index 0000000..7e24176 --- /dev/null +++ b/src/Dialog.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import Button from '@material-ui/core/Button'; +import Slide from '@material-ui/core/Slide'; + +// modified from https://material-ui.com/demos/dialogs/ + +function Transition(props) { + return ; +} + +function AlertDialog(props) { + return ( + props.handleClose(false)} + aria-labelledby="alert-dialog-slide-title" + aria-describedby="alert-dialog-slide-description"> + + {props.title} + + + + {props.message} + + + + + + + + ); +} + +export default AlertDialog; diff --git a/src/Logo.js b/src/Logo.js deleted file mode 100644 index a4036a9..0000000 --- a/src/Logo.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -export default (props) => - - - - - - - Cr - - - - - diff --git a/src/Logo.tsx b/src/Logo.tsx new file mode 100644 index 0000000..a4036a9 --- /dev/null +++ b/src/Logo.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +export default (props) => + + + + + + + Cr + + + + + diff --git a/src/PatternTable.js b/src/PatternTable.js deleted file mode 100644 index 93be293..0000000 --- a/src/PatternTable.js +++ /dev/null @@ -1,208 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { withStyles, withTheme } from '@material-ui/core/styles'; -import TextField from '@material-ui/core/TextField'; -import Table from '@material-ui/core/Table'; -import TableBody from '@material-ui/core/TableBody'; -import TableRow from '@material-ui/core/TableRow'; -import TableCell from '@material-ui/core/TableCell'; -import TableHead from '@material-ui/core/TableHead'; -import TablePagination from '@material-ui/core/TablePagination'; -import DeleteOutlinedIcon from '@material-ui/icons/DeleteOutlined'; -import Popover from '@material-ui/core/Popover'; -import MaterialColorPicker from 'react-material-color-picker'; -import { CalendarField, EventField } from './RegexField'; -import { theme, defaultChartColor } from './theme'; - -const styles = theme => ({ - deleteButton: { - width: 0, - position: 'absolute', - marginRight: '2em', - right: 0, - height: 48, - }, - deleteButtonHide: { - display: 'none' - }, - deleteButtonShow: {}, - deleteIcon: { - position: 'absolute', - height: '100%', - cursor: 'pointer' - }, - patternTableWrapper: { - overflowX: 'auto', - overflowY: 'hidden' - }, - patternTable: { - minWidth: 600 - } -}); - -let nameFieldstyles = { - colorSample: { - display: 'inline-block', - height: 30, - width: 30, - marginRight: 10, - cursor: 'pointer' - } -}; - -function NameField(props) { - let color = props.value.color; - return ( - -
-
- props.onChange('name', event.target.value)} /> -
); -} - -const patternHead = [ - {label: "Name", elem: withStyles(nameFieldstyles)(NameField)}, - {label: "Calendar", elem: withTheme(theme)(CalendarField)}, - {label: "Event", elem: withTheme(theme)(EventField)}]; - -class PatternTable extends React.Component { - state = { - page: 0, - rowsPerPage: 5, - activePattern: null, - anchorEl: null, - colorPickerOpen: false, - colorPickerDefault: defaultChartColor - }; - - handleChangePage = (event, page) => { - this.setState({ page }); - } - - handleChangeRowsPerPage = event => { - this.setState({ rowsPerPage: event.target.value }); - } - - handleColorPickerClose = () => { - this.setState({ colorPickerOpen: false }); - this.activeColorPattern !== null && - this.chosenColor && - this.props.onUpdatePattern('color', this.activeColorPattern, - {background: this.chosenColor.target.value}) - } - - render() { - const { classes, calendars, patterns } = this.props; - const { rowsPerPage, page } = this.state; - const nDummy = rowsPerPage - Math.min(rowsPerPage, patterns.length - page * rowsPerPage); - let rows = patterns.slice(page * rowsPerPage, (page + 1) * rowsPerPage).map((p, i) => { - let setActive = () => this.setState({ activePattern: p.idx }); - let unsetActive = () => this.setState({ activePattern: null }); - return [ - - - this.props.onRemovePattern(p.idx)} /> - - - , - - { - patternHead.map((s, i) => { - const CustomText = s.elem; - return ( - - this.props.onUpdatePattern(field, p.idx, value)} - colorOnClick={event => { - this.activeColorPattern = p.idx; - this.setState({ - anchorEl: event.currentTarget, - colorPickerDefault: p.color.background, - colorPickerOpen: true - }); - }}/> - )}) - } - ] - }); - rows.flat(); - - return ( -
- -