aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDeterminant <[email protected]>2019-01-31 14:48:28 -0500
committerDeterminant <[email protected]>2019-01-31 14:48:28 -0500
commitf9f537e3dd28aa543770aea158e4eff65be0b261 (patch)
treeddaafeec02f2fe66fb913c0cf4795b7b41d06bde /src
parente458b7a692d9ac4ab91b2ef6ee36ff439c528c50 (diff)
impl GCalendar sync model
Diffstat (limited to 'src')
-rwxr-xr-xsrc/App.js46
-rw-r--r--src/PatternTable.js10
-rw-r--r--src/gapi.js167
3 files changed, 178 insertions, 45 deletions
diff --git a/src/App.js b/src/App.js
index 6e9dc27..4d23440 100755
--- a/src/App.js
+++ b/src/App.js
@@ -80,14 +80,6 @@ class Dashboard extends React.Component {
calendars: {}
};
- handleChangePage = (event, page) => {
- this.setState({ page });
- };
-
- handleChangeRowsPerPage = event => {
- this.setState({ rowsPerPage: event.target.value });
- };
-
updatePattern = (field, idx, value) => {
let patterns = this.state.patterns;
patterns[idx][field] = value;
@@ -114,32 +106,31 @@ class Dashboard extends React.Component {
alert("Please choose a valid time range.");
return;
}
- let start = this.state.startDate.toISOString();
- let end = this.state.endDate.toISOString();
+ let start = this.state.startDate.toDate();
+ let end = this.state.endDate.toDate();
+ console.log(start, end);
let event_pms = [];
- for (let id in this.cached.calendars) {
- event_pms.push(
- this.state.token
- .then(gapi.genEventsGetter(id, start, end))
- .then(items => this.cached.calendars[id].events = items));
- }
+ for (let id in this.cached.calendars)
+ event_pms.push(this.cached.calendars[id].cal.getEvents(start, end).then(
+ r => { return { id, events: r } }));
- Promise.all(event_pms).then(() => {
+ Promise.all(event_pms).then(all_events => {
+ let events = {};
let results = {}; // pattern idx => time
let cal_results = {}; // cal id => time
+ all_events.forEach(e => events[e.id] = e.events);
for (let i = 0; i < this.state.patterns.length; i++)
results[i] = 0;
for (let id in this.cached.calendars) {
+ if (!events[id]) continue;
let patterns = filterPatterns(this.state.patterns, this.cached.calendars[id].name);
- if (!this.cached.calendars[id].events) continue;
- this.cached.calendars[id].events.forEach(event => {
- if (event.status !== "confirmed") return;
+ events[id].forEach(event => {
patterns.forEach(p => {
if (!p.event.regex.test(event.summary)) return;
- if (cal_results[id] === undefined) {
+ if (!cal_results.hasOwnProperty(id)) {
cal_results[id] = 0;
}
- let duration = (new Date(event.end.dateTime) - new Date(event.start.dateTime)) / 60000;
+ let duration = (event.end - event.start) / 60000;
results[p.idx] += duration;
cal_results[id] += duration;
});
@@ -156,6 +147,7 @@ class Dashboard extends React.Component {
value: (cal_results[id] / 60.0),
color: this.cached.calendars[id].color.background});
}
+ //console.log(patternGraphData, calendarGraphData);
this.setState({ patternGraphData, calendarGraphData });
});
};
@@ -170,8 +162,8 @@ class Dashboard extends React.Component {
items.forEach(item => {
this.cached.calendars[item.id] = {
name: item.summary,
- events: {},
- color: colors[item.colorId]
+ color: colors[item.colorId],
+ cal: new gapi.GCalendar(item.id, item.summary)
};
});
this.setState({ patterns: items.map((item, idx) => {
@@ -210,7 +202,11 @@ class Dashboard extends React.Component {
style={{marginBottom: '0.12em', marginLeft: '0.5em'}}
onClick={() => this.newPattern()}><AddCircleIcon /></IconButton>
</Typography>
- <PatternTable patterns={this.state.patterns} cached={this.cached} />
+ <PatternTable
+ patterns={this.state.patterns}
+ cached={this.cached}
+ onRemovePattern={this.removePattern}
+ onUpdatePattern={this.updatePattern} />
</FormGroup>
<FormGroup>
<Typography variant="h6" component="h1" gutterBottom>
diff --git a/src/PatternTable.js b/src/PatternTable.js
index 001c468..19ac292 100644
--- a/src/PatternTable.js
+++ b/src/PatternTable.js
@@ -45,11 +45,11 @@ class PatternTable extends React.Component {
rowsPerPage: 5,
};
- handleChangePage(event, page) {
+ handleChangePage = (event, page) => {
this.setState({ page });
}
- handleChangeRowsPerPage(event) {
+ handleChangeRowsPerPage = event => {
this.setState({ rowsPerPage: event.target.value });
}
@@ -69,13 +69,13 @@ class PatternTable extends React.Component {
<CustomText
value={p[s.field]}
cached={cached}
- onChange={event => this.updatePattern(s.field, p.idx, event.target.value)}/>
+ onChange={event => this.props.onUpdatePattern(s.field, p.idx, event.target.value)}/>
</TableCell>)})
}
<span className={this.state.activePattern === p.idx ? classes.deleteButtonShow : classes.deleteButtonHide}>
<DeleteOutlinedIcon
className={classes.deleteIcon}
- onClick={() => this.removePattern(p.idx)} />
+ onClick={() => this.props.onRemovePattern(p.idx)} />
</span>
</TableRow>));
@@ -116,6 +116,8 @@ PatternTable.propTypes = {
classes: PropTypes.object.isRequired,
patterns: PropTypes.array.isRequired,
cached: PropTypes.object.isRequired,
+ onRemovePattern: PropTypes.func.isRequired,
+ onUpdatePattern: PropTypes.func.isRequired,
};
export default withStyles(styles)(PatternTable);
diff --git a/src/gapi.js b/src/gapi.js
index 975d4d9..c66a9ff 100644
--- a/src/gapi.js
+++ b/src/gapi.js
@@ -12,28 +12,163 @@ export function getAuthToken() {
}
export function getCalendars(token) {
- return fetch(gapi_base + '/users/me/calendarList?' + to_params({access_token: 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 genEventsGetter(calId, timeMin, timeMax) {
- return token => fetch(gapi_base + '/calendars/' + calId + '/events?' + to_params({
- access_token: token,
- timeMin,
- timeMax
- }), { method: 'GET', async: true })
- .then(response => {
- if (response.status === 200)
- return response.json()
- else throw `got response ${response.status}`;
- })
- .catch(e => { console.log(e); return []; })
- .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());
}
-export function getColors(token) {
- return fetch(gapi_base + '/colors?' + to_params({access_token: token}), { method: 'GET', async: true })
+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, resultsPerRequest = '') {
+ let results = [];
+ const singleFetch = (pageToken, syncToken) => fetch(`${gapi_base}/calendars/${calId}/events?${to_params({
+ access_token: token,
+ pageToken,
+ syncToken,
+ maxResults: resultsPerRequest
+ })}`, { method: 'GET', async: true })
+ .then(response => {
+ if (response.status === 200)
+ return response.json();
+ else throw {};
+ })
+ .catch(e => e)
+ .then(data => {
+ if (!data.items)
+ return ({
+ nextSyncToken: '',
+ results: []
+ });
+ 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) {
+ this.calId = calId;
+ this.name = name;
+ this.token = getAuthToken();
+ this.syncToken = '';
+ this.cache = {};
+ }
+
+ static dateToCacheKey(date) {
+ return Math.floor(date / 8.64e7);
+ }
+
+ getSlot(k) {
+ if (!this.cache[k])
+ this.cache[k] = {};
+ return this.cache[k];
+ }
+
+ static slotStartDate(k) { return new Date(k * 8.64e7); }
+ static slotEndDate(k) { return new Date((k + 1) * 8.64e7); }
+
+ addEvent(e) {
+ let ks = GCalendar.dateToCacheKey(e.start);
+ let ke = GCalendar.dateToCacheKey(new Date(e.end.getTime() - 1));
+ 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: GCalendar.slotEndDate(ks),
+ id: e.id };
+ this.getSlot(ke)[e.id] = {
+ start: GCalendar.slotStartDate(ke),
+ end: e.end,
+ id: e.id };
+ for (let k = ks + 1; k < ke; k++)
+ this.getSlot(k)[e.id] = {
+ start: GCalendar.slotStartDate(k),
+ end: GCalendar.slotEndDate(k),
+ id: e.id };
+ }
+ }
+
+ removeEvent(e) {
+ let ks = GCalendar.dateToCacheKey(e.start);
+ let ke = GCalendar.dateToCacheKey(new Date(e.end.getTime() - 1));
+ for (let k = ks; k <= ke; k++)
+ delete this.getSlot(k)[e.id];
+ }
+
+ getSlotEvents(k, start, end) {
+ let s = this.getSlot(k);
+ let results = [];
+ for (let id in s) {
+ if (!(s[id].start >= end || s[id].end <= start))
+ {
+ let nstart = s[id].start < start ? start: s[id].start;
+ let nend = s[id].end > end ? end: s[id].end;
+ if (nstart > nend) console.log(s[id], start, end);
+ results.push({
+ id,
+ start: s[id].start < start ? start: s[id].start,
+ end: s[id].end > end ? end: s[id].end
+ });
+ }
+ }
+ return results;
+ }
+
+ getCachedEvents(start, end) {
+ let ks = GCalendar.dateToCacheKey(start);
+ let ke = GCalendar.dateToCacheKey(new Date(end.getTime() - 1));
+ let results = this.getSlotEvents(ks, start, end);
+ for (let k = ks + 1; k < ke; k++)
+ {
+ let s = this.getSlot(k);
+ for (let id in s)
+ results.push(s[id]);
+ }
+ if (ke > ks)
+ results.push(...this.getSlotEvents(ke, start, end));
+ return results;
+ }
+
+ sync() {
+ return this.token.then(token => getEvents(this.calId, token, this.syncToken).then(r => {
+ this.syncToken = r.nextSyncToken;
+ let pm_results = r.results.map(e => e.start ? Promise.resolve(e) : getEvent(this.calId, e.id, token));
+ return Promise.all(pm_results).then(results => results.forEach(e => {
+ e.start = new Date(e.start.dateTime);
+ e.end = new Date(e.end.dateTime);
+ if (e.status === 'confirmed')
+ this.addEvent(e);
+ else if (e.status === 'cancelled')
+ this.removeEvent(e);
+ }));
+ }));
+ }
+
+ getEvents(start, end) {
+ return this.sync().then(() => this.getCachedEvents(start, end));
+ }
+}