diff options
author | Determinant <ted.sybil@gmail.com> | 2019-02-13 01:11:31 -0500 |
---|---|---|
committer | Determinant <ted.sybil@gmail.com> | 2019-02-13 01:11:31 -0500 |
commit | c594888953151ddfb4ca04b7752bfd51edc1d6da (patch) | |
tree | 59b6d0b0f514f76d152eee9a4359c08110f73531 | |
parent | f28b818cc62c7fff67517a4147e64f08ebd73027 (diff) |
WIP: migrate to TypeScriptX
-rw-r--r-- | .babelrc | 2 | ||||
-rw-r--r-- | package-lock.json | 109 | ||||
-rw-r--r-- | package.json | 7 | ||||
-rw-r--r-- | src/Analyze.tsx (renamed from src/Analyze.js) | 36 | ||||
-rw-r--r-- | src/Chart.tsx (renamed from src/Chart.js) | 0 | ||||
-rw-r--r-- | src/Dashboard.tsx (renamed from src/Dashboard.js) | 0 | ||||
-rw-r--r-- | src/Dialog.tsx (renamed from src/Dialog.js) | 0 | ||||
-rw-r--r-- | src/Logo.tsx (renamed from src/Logo.js) | 0 | ||||
-rw-r--r-- | src/PatternTable.tsx (renamed from src/PatternTable.js) | 0 | ||||
-rw-r--r-- | src/RegexField.tsx (renamed from src/RegexField.js) | 0 | ||||
-rw-r--r-- | src/Settings.tsx (renamed from src/Settings.js) | 16 | ||||
-rw-r--r-- | src/Snackbar.tsx (renamed from src/Snackbar.js) | 0 | ||||
-rw-r--r-- | src/background.ts (renamed from src/background.js) | 2 | ||||
-rw-r--r-- | src/decl.ts | 1 | ||||
-rw-r--r-- | src/duration.js | 21 | ||||
-rw-r--r-- | src/duration.ts | 26 | ||||
-rw-r--r-- | src/gapi.js | 335 | ||||
-rw-r--r-- | src/gapi.ts | 369 | ||||
-rw-r--r-- | src/index.js | 11 | ||||
-rw-r--r-- | src/index.tsx | 5 | ||||
-rw-r--r-- | src/msg.js | 97 | ||||
-rw-r--r-- | src/msg.ts | 87 | ||||
-rw-r--r-- | src/pattern.ts (renamed from src/pattern.js) | 46 | ||||
-rw-r--r-- | src/popup.tsx (renamed from src/popup.js) | 10 | ||||
-rw-r--r-- | src/serviceWorker.js | 135 | ||||
-rw-r--r-- | src/theme.tsx (renamed from src/theme.js) | 0 | ||||
-rw-r--r-- | tsconfig.json | 16 | ||||
-rw-r--r-- | types/lru-cache/index.d.ts | 10 | ||||
-rw-r--r-- | types/lru-cache/log | 1485 | ||||
-rw-r--r-- | webpack.config.js | 19 |
30 files changed, 2199 insertions, 646 deletions
@@ -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.tsx index 98e3ce2..5450998 100644 --- a/src/Analyze.js +++ b/src/Analyze.tsx @@ -3,7 +3,7 @@ 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 { 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'; @@ -15,51 +15,55 @@ 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 { 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 => ({ +const styles = (theme: Theme) => ({ buttonSpacer: { marginBottom: theme.spacing.unit * 4, }, }); class Analyze extends React.Component { + msgClient: MsgClient; + state = { - patterns: [], + patterns: [] as PatternEntry[], calendars: {}, - startDate: null, - endDate: null, + 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: ''}, + dialogMsg: {title: '', message: ''} }; - constructor(props) { + constructor(props: any) { super(props); + this.msgClient = new MsgClient('main'); this.msgClient.sendMsg({ - type: msgType.getPatterns, + type: MsgType.getPatterns, data: { id: 'analyze' } }).then(msg => { this.setState({ patterns: msg.data.map(p => PatternEntry.inflate(p)) }); }); this.msgClient.sendMsg({ - type: msgType.getCalendars, + type: MsgType.getCalendars, data: { enabledOnly: true } }).then(msg => { this.setState({ calendars: msg.data }); @@ -71,20 +75,20 @@ class Analyze extends React.Component { this.dialogPromiseResolver = null; } - loadPatterns = patterns => { + loadPatterns = (patterns: PatternEntry[]) => { this.msgClient.sendMsg({ - type: msgType.updatePatterns, + type: MsgType.updatePatterns, data: { id: 'analyze', patterns: patterns.map(p => p.deflate()) } }).then(() => this.setState({ patterns })); }; - updatePattern = (field, idx, value) => { + updatePattern = (field: string, idx: number, value: PatternEntry[]) => { let patterns = this.state.patterns; patterns[idx][field] = value; this.loadPatterns(patterns); }; - removePattern = idx => { + removePattern = (idx: number) => { let patterns = this.state.patterns; patterns.splice(idx, 1); for (let i = 0; i < patterns.length; i++) @@ -99,8 +103,8 @@ class Analyze extends React.Component { this.loadPatterns(patterns); }; - getCalEvents = (id, start, end) => { - return this.msgClient.sendMsg({ type: msgType.getCalEvents, data: { id, + 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 => { diff --git a/src/Chart.js b/src/Chart.tsx index b1c36ed..b1c36ed 100644 --- a/src/Chart.js +++ b/src/Chart.tsx diff --git a/src/Dashboard.js b/src/Dashboard.tsx index 04ced46..04ced46 100644 --- a/src/Dashboard.js +++ b/src/Dashboard.tsx diff --git a/src/Dialog.js b/src/Dialog.tsx index 7e24176..7e24176 100644 --- a/src/Dialog.js +++ b/src/Dialog.tsx diff --git a/src/Logo.js b/src/Logo.tsx index a4036a9..a4036a9 100644 --- a/src/Logo.js +++ b/src/Logo.tsx diff --git a/src/PatternTable.js b/src/PatternTable.tsx index 93be293..93be293 100644 --- a/src/PatternTable.js +++ b/src/PatternTable.tsx diff --git a/src/RegexField.js b/src/RegexField.tsx index e3fa9f4..e3fa9f4 100644 --- a/src/RegexField.js +++ b/src/RegexField.tsx diff --git a/src/Settings.js b/src/Settings.tsx index 2835483..83f1da6 100644 --- a/src/Settings.js +++ b/src/Settings.tsx @@ -20,7 +20,7 @@ import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; import Checkbox from '@material-ui/core/Checkbox'; import * as gapi from './gapi'; -import { msgType, MsgClient } from './msg'; +import { MsgType, MsgClient } from './msg'; import { Pattern, PatternEntry } from './pattern'; import PatternTable from './PatternTable'; import Snackbar from './Snackbar'; @@ -137,21 +137,21 @@ class Settings extends React.Component { this.msgClient = new MsgClient('main'); this.msgClient.sendMsg({ - type: msgType.getPatterns, + type: MsgType.getPatterns, data: { id: 'main' } }).then(msg => { this.setState({ patterns: msg.data.map(p => PatternEntry.inflate(p)) }); }); this.msgClient.sendMsg({ - type: msgType.getCalendars, + type: MsgType.getCalendars, data: { enabledOnly: false } }).then(msg => { this.setState({ calendars: msg.data }); }); this.msgClient.sendMsg({ - type: msgType.getConfig, + type: MsgType.getConfig, data: ['trackedPeriods'] }).then(msg => { let config = { @@ -191,7 +191,7 @@ class Settings extends React.Component { var calendars = {...this.state.calendars}; calendars[id].enabled = !calendars[id].enabled; this.msgClient.sendMsg({ - type: msgType.updateCalendars, + type: MsgType.updateCalendars, data: calendars }).then(() => this.setState({ calendars })); } @@ -240,14 +240,14 @@ class Settings extends React.Component { calendars[id].enabled = this.state.calendars[id].enabled; } this.msgClient.sendMsg({ - type: msgType.updateCalendars, + type: MsgType.updateCalendars, data: calendars }).then(() => this.setState({ calendars })); }; loadPatterns = (patterns, id) => { this.msgClient.sendMsg({ - type: msgType.updatePatterns, + type: MsgType.updatePatterns, data: { id, patterns: patterns.map(p => p.deflate()) } }).then(() => this.setState({ patterns })); }; @@ -297,7 +297,7 @@ class Settings extends React.Component { updateTrackedPeriods = trackedPeriods => { this.msgClient.sendMsg({ - type: msgType.updateConfig, + type: MsgType.updateConfig, data: { trackedPeriods: trackedPeriods.map(p => ({ name: p.name, start: p.start.deflate(), diff --git a/src/Snackbar.js b/src/Snackbar.tsx index f17863c..f17863c 100644 --- a/src/Snackbar.js +++ b/src/Snackbar.tsx diff --git a/src/background.js b/src/background.ts index f22970e..2a23b57 100644 --- a/src/background.js +++ b/src/background.ts @@ -5,7 +5,7 @@ import moment from 'moment'; import { getChartData } from './Chart'; import { PatternEntry } from './pattern'; -let mainPatterns = []; +let mainPatterns: number[] = []; let analyzePatterns = []; let calendars = {}; let calData = {}; diff --git a/src/decl.ts b/src/decl.ts new file mode 100644 index 0000000..5b3daa3 --- /dev/null +++ b/src/decl.ts @@ -0,0 +1 @@ +declare module 'react-dates'; diff --git a/src/duration.js b/src/duration.js deleted file mode 100644 index 53de0ad..0000000 --- a/src/duration.js +++ /dev/null @@ -1,21 +0,0 @@ -import moment from 'moment'; - -export class Duration { - constructor(value, unit) { - this.value = value - this.unit = unit - } - - toMoment() { - let m = moment.duration(this.value, this.unit); - if (m.isValid()) return m; - return null; - } - - static days(n) { return new Duration(n, 'days'); } - static weeks(n) { return new Duration(n, 'weeks'); } - static months(n) { return new Duration(n, 'months'); } - - deflate() { return { value: this.value, unit: this.unit }; } - static inflate = obj => new Duration(obj.value, obj.unit); -} diff --git a/src/duration.ts b/src/duration.ts new file mode 100644 index 0000000..18849a0 --- /dev/null +++ b/src/duration.ts @@ -0,0 +1,26 @@ +import moment from 'moment'; + +export type TimeUnit = moment.unitOfTime.DurationConstructor; + +export class Duration { + value: number; + unit: TimeUnit; + constructor(value: number, unit: TimeUnit) { + this.value = value + this.unit = unit + } + + isValid() { return moment.duration(this.value, this.unit).isValid(); } + toMoment() { + let m = moment.duration(this.value, this.unit); + if (m.isValid()) return m; + return null; + } + + static days(n: number) { return new Duration(n, 'days'); } + static weeks(n: number) { return new Duration(n, 'weeks'); } + 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); +} diff --git a/src/gapi.js b/src/gapi.js deleted file mode 100644 index 3938864..0000000 --- a/src/gapi.js +++ /dev/null @@ -1,335 +0,0 @@ -/* global chrome */ -import LRU from "lru-cache"; -const gapi_base = 'https://www.googleapis.com/calendar/v3'; - -const GApiError = Object.freeze({ - invalidSyncToken: Symbol("invalidSyncToken"), - notLoggedIn: Symbol("notLoggedIn"), - notLoggedOut: Symbol("notLoggedOut"), - otherError: Symbol("otherError"), -}); - -function to_params(dict) { - return Object.entries(dict).filter(([k, v]) => v).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&'); -} - -let loggedIn = null; - -function _getAuthToken(interactive = false) { - return new Promise(resolver => - chrome.identity.getAuthToken( - { interactive }, token => resolver([token, !chrome.runtime.lastError]))) - .then(([token, ok]) => { - if (ok) return token; - else throw GApiError.notLoggedIn; - }); -} - -function _removeCachedAuthToken(token) { - return new Promise(resolver => - chrome.identity.removeCachedAuthToken({ token }, () => resolver())); -} - -export function getLoggedIn() { - if (loggedIn === null) - { - return _getAuthToken(false) - .then(() => loggedIn = true) - .catch(() => loggedIn = false) - .then(() => loggedIn); - } - else return Promise.resolve(loggedIn); -} - -export function getAuthToken() { - return getLoggedIn().then(b => { - if (b) return _getAuthToken(false); - else throw GApiError.notLoggedIn; - }); -} - -export function login() { - return getLoggedIn().then(b => { - if (!b) return _getAuthToken(true).then(() => loggedIn = true); - else throw GApiError.notLoggedOut; - }); -} - -export function logout() { - return getAuthToken().then(token => { - return fetch(`https://accounts.google.com/o/oauth2/revoke?${to_params({ token })}`, - { method: 'GET', async: true }).then(response => { - //if (response.status === 200) - return _removeCachedAuthToken(token); - //else throw GApiError.otherError; - }); - }).then(() => loggedIn = false); -} - -export function getCalendars(token) { - return fetch(`${gapi_base}/users/me/calendarList?${to_params({access_token: token})}`, - { method: 'GET', async: true }) - .then(response => response.json()) - .then(data => data.items); -} - -export function getColors(token) { - return fetch(`${gapi_base}/colors?${to_params({access_token: token})}`, - { method: 'GET', async: true }) - .then(response => response.json()); -} - -function getEvent(calId, eventId, token) { - return fetch(`${gapi_base}/calendars/${calId}/events/${eventId}?${to_params({access_token: token})}`, - { method: 'GET', async: true }) - .then(response => response.json()); -} - -function getEvents(calId, token, syncToken=null, timeMin=null, timeMax=null, resultsPerRequest=100) { - let results = []; - const singleFetch = (pageToken, syncToken) => fetch(`${gapi_base}/calendars/${calId}/events?${to_params({ - access_token: token, - pageToken, - syncToken, - timeMin, - timeMax, - maxResults: resultsPerRequest - })}`, { method: 'GET', async: true }) - .then(response => { - if (response.status === 200) - return response.json(); - else if (response.status === 410) - throw GApiError.invalidSyncToken; - else throw GApiError.otherError; - }) - .then(data => { - results.push(...data.items); - if (data.nextPageToken) { - return singleFetch(data.nextPageToken, ''); - } else { - return ({ - nextSyncToken: data.nextSyncToken, - results - }); - } - }) - - return singleFetch('', syncToken); -} - -export class GCalendar { - constructor(calId, name, options={maxCachedItems: 100, nDaysPerSlot: 10, largeQuery: 10}) { - this.calId = calId; - this.name = name; - this.syncToken = ''; - this.cache = new LRU({ - max: options.maxCachedItems, - dispose: (k, v) => this.onRemoveSlot(k, v) - }); - this.eventMeta = {}; - this.options = options; - this.divider = 8.64e7 * this.options.nDaysPerSlot; - } - - get token() { return getAuthToken(); } - - dateToCacheKey(date) { - return Math.floor(date / this.divider); - } - - dateRangeToCacheKeys(range) { - return { - start: this.dateToCacheKey(range.start), - end: this.dateToCacheKey(new Date(range.end.getTime() - 1)) - }; - } - - getSlot(k) { - if (!this.cache.has(k)) - { - let res = {}; - this.cache.set(k, res); - return res; - } - else return this.cache.get(k); - } - - onRemoveSlot(k, v) { - for (let id in v) { - console.assert(this.eventMeta[id]); - let keys = this.eventMeta[id].keys; - keys.delete(k); - if (keys.size === 0) - delete this.eventMeta[id]; - } - } - - slotStartDate(k) { return new Date(k * this.divider); } - slotEndDate(k) { return new Date((k + 1) * this.divider); } - - addEvent(e, evict = false) { - //console.log('adding event', e); - if (this.eventMeta.hasOwnProperty(e.id)) - this.removeEvent(e); - let r = this.dateRangeToCacheKeys(e); - let ks = r.start; - let ke = r.end; - let t = this.cache.length; - let keys = new Set(); - for (let i = ks; i <= ke; i++) - { - keys.add(i); - if (!this.cache.has(i)) t++; - } - this.eventMeta[e.id] = { - keys, - summary: e.summary, - }; - if (!evict && t > this.options.maxCachedItems) return; - if (ks === ke) - this.getSlot(ks)[e.id] = { - start: e.start, - end: e.end, - id: e.id }; - else - { - this.getSlot(ks)[e.id] = { - start: e.start, - end: this.slotEndDate(ks), - id: e.id }; - this.getSlot(ke)[e.id] = { - start: this.slotStartDate(ke), - end: e.end, - id: e.id }; - for (let k = ks + 1; k < ke; k++) - this.getSlot(k)[e.id] = { - start: this.slotStartDate(k), - end: this.slotEndDate(k), - id: e.id}; - } - } - - removeEvent(e) { - let keys = this.eventMeta[e.id].keys; - console.assert(keys); - keys.forEach(k => delete this.getSlot(k)[e.id]); - delete this.eventMeta[e.id]; - } - - getSlotEvents(k, start, end) { - let s = this.getSlot(k); - //console.log(s); - let results = []; - for (let id in s) { - if (!(s[id].start >= end || s[id].end <= start)) - { - results.push({ - id, - start: s[id].start < start ? start: s[id].start, - end: s[id].end > end ? end: s[id].end, - summary: this.eventMeta[id].summary - }); - } - } - return results; - } - - getCachedEvents(_r) { - let r = this.dateRangeToCacheKeys(_r); - let ks = r.start; - let ke = r.end; - let results = this.getSlotEvents(ks, _r.start, _r.end); - for (let k = ks + 1; k < ke; k++) - { - let s = this.getSlot(k); - for (let id in s) - results.push(s[id]); |