├── README.md
├── ghc.js
└── scheduler.PNG
/README.md:
--------------------------------------------------------------------------------
1 | # ghc-scheduler
2 | Helps with visualising the schedule for GHC 2018.
3 |
4 |
5 |
6 | ## Goals
7 | * Easy to install.
8 | * Make sure we are always operating on current data - If the schedule changes, this script will pick up the changes.
9 |
10 | ## Install
11 | 1. Go to the [raw js](https://raw.githubusercontent.com/charlottetan/ghc-scheduler/master/ghc.js) and copy it (ctrl-c).
12 | 2. Go to the [GHC sessions page](http://www.cvent.com/events/grace-hopper-celebration/agenda-6083a0df738343e2ad8b262237e56423.aspx?p=13).
13 | 3. Open Developer Tools and paste the js into the console.
14 |
15 | ## Usage
16 | * **Dates:** Click to toggle between the dates
17 | * **Audience | Track | Off:** Choose to colour the sessions by audience, track, or turn it off.
18 | * **Show all sessions:** Toggle to exclude sessions outside of the main session block. List of excluded sessions is in the code.
19 | * Schedule controls:
20 | * **Show my schedule:** While viewing sessions, click on a session to add it to your schedule, then click this button to show only the sessions that you have added. Note: This disables the *Show all sessions* filter.
21 | * **Clear:** Remove all sessions that were in your schedule.
22 | * **Export:** Export schedule as a `|` delimited list of titles.
23 | * **Import:** Import a `|` delimited list of titles as a schedule and load it up for viewing.
24 | * Textbox: For grabbing exported schedule and importing schedule.
25 | * **Audience/Track colour blocks:** These can be toggled to show sessions that match a particular audience or track.
26 | * **Schedule container**: Has two horizontal scrollbars to help with the scrolling.
27 | * **Each session**: Hovering over a session brings up the description of the session. Clicking on a session will add it to your schedule which you can then view and edit using the schedule controls.
28 |
--------------------------------------------------------------------------------
/ghc.js:
--------------------------------------------------------------------------------
1 | // For: http://www.cvent.com/events/grace-hopper-celebration/agenda-6083a0df738343e2ad8b262237e56423.aspx?p=13
2 |
3 | function parseGhcSessions() {
4 | var tracksIdToTextMap = {};
5 | var tracksTextToIdMap = {};
6 | var audienceIdToTextMap = {};
7 | var audienceTextToIdMap = {};
8 | var focusAreaSet = new Set();
9 | var ghcDays = new Set();
10 |
11 | var sessionContainerDivs = jQuery(".reg-matrix-header-container");
12 |
13 | var sesArr = [];
14 | for (var i = 0; i < sessionContainerDivs.length; i++) {
15 | var oneSes = {};
16 | oneSes.title = sessionContainerDivs[i].childNodes[1].childNodes[1].innerHTML.trim();
17 |
18 | var sessionContentDiv = sessionContainerDivs[i].childNodes[3];
19 | var sessionInfoDiv = sessionContentDiv.childNodes[1].childNodes[1];
20 |
21 | var infoDivChildCounter = 0;
22 |
23 | if (sessionInfoDiv.childNodes[infoDivChildCounter].childNodes[0].nodeValue == "Audience Level: ")
24 | {
25 | oneSes.audience = sessionInfoDiv.childNodes[infoDivChildCounter].childNodes[1].innerHTML;
26 | var audienceKey = oneSes.audience.replace(/[^a-z0-9]/gi, '');
27 | audienceIdToTextMap[audienceKey] = oneSes.audience;
28 | audienceTextToIdMap[oneSes.audience] = audienceKey;
29 |
30 | infoDivChildCounter += 2;
31 | }
32 |
33 | if (sessionInfoDiv.childNodes[infoDivChildCounter].childNodes[0].nodeValue == "Track: ")
34 | {
35 | oneSes.track = sessionInfoDiv.childNodes[infoDivChildCounter].childNodes[1].innerHTML;
36 | var trackKey = oneSes.track.replace(/[^a-z0-9]/gi, '') + "Tr";
37 | tracksIdToTextMap[trackKey] = oneSes.track;
38 | tracksTextToIdMap[oneSes.track] = trackKey;
39 |
40 | infoDivChildCounter += 2;
41 | }
42 |
43 | if (sessionInfoDiv.childNodes[infoDivChildCounter].childNodes[0].nodeValue == "Focus Area: ")
44 | {
45 | oneSes.focusArea = sessionInfoDiv.childNodes[infoDivChildCounter].childNodes[1].innerHTML;
46 | focusAreaSet.add(oneSes.focusArea);
47 | infoDivChildCounter += 2;
48 | }
49 |
50 | oneSes.startDate = sessionInfoDiv.childNodes[infoDivChildCounter].childNodes[1].innerHTML;
51 | ghcDays.add(oneSes.startDate);
52 | infoDivChildCounter += 2;
53 |
54 | oneSes.timeStart = sessionInfoDiv.childNodes[infoDivChildCounter].childNodes[1].innerHTML;
55 | oneSes.timestampStart = Date.parse(oneSes.startDate + ' ' + oneSes.timeStart);
56 | infoDivChildCounter += 2;
57 |
58 | oneSes.timeEnd = sessionInfoDiv.childNodes[infoDivChildCounter].childNodes[1].innerHTML;
59 | oneSes.timestampEnd = Date.parse(oneSes.startDate + ' ' + oneSes.timeEnd);
60 | infoDivChildCounter += 2;
61 |
62 | if (infoDivChildCounter < sessionInfoDiv.childNodes.length &&
63 | sessionInfoDiv.childNodes[infoDivChildCounter].childNodes[0].nodeValue == "Location: ")
64 | {
65 | oneSes.location = sessionInfoDiv.childNodes[infoDivChildCounter].childNodes[1].innerHTML;
66 | infoDivChildCounter += 2;
67 | }
68 |
69 | if (infoDivChildCounter <= sessionInfoDiv.childNodes.length)
70 | {
71 | var speakerDivList = sessionInfoDiv.childNodes[infoDivChildCounter].childNodes;
72 | oneSes.speakers = [];
73 |
74 | for (var j = 0; j < speakerDivList.length; j+=2) {
75 | oneSes.speakers.push(speakerDivList[j].childNodes[0].nodeValue);
76 | }
77 |
78 | infoDivChildCounter += 2;
79 | }
80 |
81 | oneSes.desc = sessionContentDiv.childNodes[3].innerHTML.trim();
82 |
83 | sesArr.push(oneSes);
84 | }
85 |
86 | localStorage.setItem('ghcSessionArray', JSON.stringify(sesArr));
87 | localStorage.setItem('ghcDays', [...ghcDays].join('|'));
88 | localStorage.setItem('tracksIdToTextMap', JSON.stringify(tracksIdToTextMap));
89 | localStorage.setItem('tracksTextToIdMap', JSON.stringify(tracksTextToIdMap));
90 | localStorage.setItem('audienceIdToTextMap', JSON.stringify(audienceIdToTextMap));
91 | localStorage.setItem('audienceTextToIdMap', JSON.stringify(audienceTextToIdMap));
92 | localStorage.setItem('focusAreaSet', [...focusAreaSet].join('|'));
93 |
94 | if (!localStorage.getItem('includedTracks')) {
95 | var includedTracks = Object.keys(tracksIdToTextMap);
96 | localStorage.setItem('includedTracks', [...includedTracks].join('|'));
97 | }
98 |
99 | if (!localStorage.getItem('includedAudiences')) {
100 | var includedAudiences = Object.keys(audienceIdToTextMap);
101 | localStorage.setItem('includedAudiences', [...includedAudiences].join('|'));
102 | }
103 | }
104 |
105 | function slotSessions() {
106 | // for some reason this double parse is required for arrays of objects in localStorage
107 | var sessionArray = JSON.parse(JSON.parse(localStorage.getItem('ghcSessionArray')));
108 | var sList = {};
109 | var mList = {};
110 | var lList = {};
111 |
112 | // sList: 0-2hrs
113 | // mList: 2.1-3 hrs
114 | // lList: more than 3 hours
115 | const sLimit = 1000*60*60*2;
116 | const mLimit = 1000*60*60*3;
117 |
118 | const partyName = "GHC Evening Celebration";
119 |
120 | var dayArray = localStorage.getItem('ghcDays').split('|');
121 |
122 | // get included list
123 | var includedTracks = localStorage.getItem('includedTracks').split('|');
124 | var includedAudiences = localStorage.getItem('includedAudiences').split('|');
125 |
126 | var excludedSessions = [
127 | 'Community Volunteer Orientation',
128 | 'Wednesday Keynote: Padmasree Warrior and Jessica Matthews',
129 | 'Mentoring Circles',
130 | 'CR111: Speed Mentoring',
131 | 'Poster Session 1',
132 | 'Poster Session 2',
133 | 'Poster Session 3',
134 | 'Poster Session 4',
135 | 'Poster Session 5',
136 | 'Faculty Lounge',
137 | 'Speaker Lounge',
138 | 'Student Lounge',
139 | 'Gallery: Our Time',
140 | 'GHC Expo',
141 | 'Open Source Day Code-a-thon for Humanity',
142 | 'AnitaB.org PitcHER',
143 | 'Friday Keynote: Justine Cassell and PitcHER Winners',
144 | 'GHC Evening Celebration',
145 | 'Session Chair Orientation (Invite Only)',
146 | 'Speaker\'s Reception (Invite Only)',
147 | 'AnitaB.org Community Leadership Dinner (Invite Only)',
148 | 'Hoppers Orientation (Invite Only)',
149 | 'BRAID Welcome Reception (Invite Only)',
150 | 'SMASH Academy Networking Reception (Invite Only)',
151 | 'AnitaB.org Partner Meeting (Invite Only)',
152 | 'GHC Scholar Networking Reception (Invite Only)',
153 | 'Technical Executive Forum (Invite Only)',
154 | 'Senior Women\'s Program (Invite Only)',
155 | 'ACM-W Luncheon (Invite Only)',
156 | 'Committee\'s Lunch (Invite Only)',
157 | ];
158 |
159 | if (localStorage.getItem('showAllSessions') === 'true') {
160 | excludedSessions = [];
161 | }
162 |
163 | //showSchedule overrides excludedSessions
164 | var mySchedule = [];
165 | var showingSchedule = localStorage.getItem('showSchedule') === 'true';
166 | if (showingSchedule) {
167 | var storageSched = localStorage.getItem('mySchedule');
168 | if (storageSched) {
169 | mySchedule = storageSched.split('|');
170 | }
171 | excludedSessions = [];
172 | }
173 |
174 | for (var i = 0; i < sessionArray.length; i++) {
175 | var sesDay = sessionArray[i].startDate;
176 |
177 | var aDay = [];
178 | var aTrack = {};
179 | var trackSessions = [];
180 | var trackId = 0;
181 | var days;
182 |
183 | // only proceed to slot if included
184 | var trackKey = undefined, audienceKey = undefined;
185 | if (sessionArray[i].track) {
186 | trackKey = sessionArray[i].track.replace(/[^a-z0-9]/gi, '') + "Tr";
187 | }
188 | if (sessionArray[i].audience) {
189 | audienceKey = sessionArray[i].audience.replace(/[^a-z0-9]/gi, '');
190 | }
191 | var trackKeyIncluded = !trackKey || (trackKey && includedTracks.includes(trackKey));
192 | var audienceKeyIncluded = !audienceKey || (audienceKey && includedAudiences.includes(audienceKey));
193 | var isNotAnExcludedSession = (!showingSchedule && !excludedSessions.includes(sessionArray[i].title)) ||
194 | (showingSchedule && mySchedule.includes(sessionArray[i].title));
195 |
196 | if (trackKeyIncluded && audienceKeyIncluded && isNotAnExcludedSession) {
197 | // get tshirt size
198 | var duration = sessionArray[i].timestampEnd - sessionArray[i].timestampStart;
199 | switch (true) {
200 | case (duration <= sLimit):
201 | days = sList;
202 | break;
203 | case (duration <= mLimit):
204 | days = mList;
205 | break;
206 | default:
207 | days = lList;
208 | }
209 |
210 | // Exception for GHC Evening Celebration
211 | if (sessionArray[i].title == partyName) {
212 | days = sList;
213 | }
214 |
215 | if (days[sesDay]) {
216 | aDay = days[sesDay];
217 |
218 | var foundTrack = false;
219 | // find a suitable track
220 | for (var j = 0; j < aDay.length; j++) {
221 | var candidateTrack = aDay[j];
222 | if (candidateTrack.latest < sessionArray[i].timestampStart) {
223 | // we found our track
224 | foundTrack = true;
225 | trackId = j;
226 | aTrack = candidateTrack;
227 | trackSessions = candidateTrack.sessions;
228 | break;
229 | }
230 | }
231 |
232 | // if none found, add to end
233 | if (!foundTrack) {
234 | trackId = aDay.length;
235 | }
236 | }
237 |
238 | // add session to track
239 | trackSessions.push(sessionArray[i]);
240 | aTrack.latest = sessionArray[i].timestampEnd;
241 | aTrack.sessions = trackSessions;
242 |
243 | // add track to day
244 | aDay[trackId] = aTrack;
245 |
246 | // add day to days
247 | // Exception for GHC Evening Celebration
248 | if (sessionArray[i].title == partyName) {
249 | sList[sesDay] = aDay;
250 | } else {
251 | switch (true) {
252 | case (duration <= sLimit):
253 | sList[sesDay] = aDay;
254 | break;
255 | case (duration <= mLimit):
256 | mList[sesDay] = aDay;
257 | break;
258 | default:
259 | lList[sesDay] = aDay;
260 | }
261 | }
262 | }
263 | }
264 |
265 | // combine days
266 | var schedule = {};
267 | for (let dayItem of dayArray) {
268 | schedule[dayItem] = [];
269 | if (sList[dayItem]) {
270 | schedule[dayItem].push(...sList[dayItem]);
271 | }
272 | if (mList[dayItem]) {
273 | schedule[dayItem].push(...mList[dayItem]);
274 | }
275 | if (lList[dayItem]) {
276 | schedule[dayItem].push(...lList[dayItem]);
277 | }
278 | };
279 |
280 | return schedule;
281 | }
282 |
283 | function toggleTabs(eventObj) {
284 | var timestampButton = eventObj.target.id;
285 | var timestamp = eventObj.target.id.replace("button", "");
286 |
287 | var newWidth = jQuery('#' + timestamp).width();
288 | jQuery('#dayDummyContent').width(newWidth);
289 |
290 | jQuery('.dayDiv').hide();
291 | jQuery('#dayTabs button').removeClass('selected');
292 |
293 | jQuery('#' + timestampButton).addClass('selected');
294 | jQuery('#' + timestamp).css('display', 'flex');
295 |
296 | var bufferSoThatDivWillNotScroll = 250;
297 | jQuery('#dayContent').height(jQuery(`#${timestamp}`)[0].scrollHeight+bufferSoThatDivWillNotScroll + 'px');
298 |
299 | localStorage.setItem('selectedTimestampButton', timestampButton);
300 | }
301 |
302 | function toggleAudienceTrackOff(eventObj) {
303 | jQuery(".toggleOptions").removeClass("selected");
304 |
305 | switch (eventObj.target.id) {
306 | case "toggleAudienceDiv":
307 | jQuery("#toggleAudienceDiv").addClass("selected");
308 |
309 | jQuery("#audienceLegend").show();
310 | jQuery("#trackLegend").hide();
311 | jQuery(".sessionDiv").addClass("audienceColours").removeClass("trackColours");
312 | break;
313 | case "toggleTrackDiv":
314 | jQuery("#toggleTrackDiv").addClass("selected");
315 |
316 | jQuery("#audienceLegend").hide();
317 | jQuery("#trackLegend").show();
318 | jQuery(".sessionDiv").removeClass("audienceColours").addClass("trackColours");
319 | break;
320 | default:
321 | jQuery("#toggleOffDiv").addClass("selected");
322 |
323 | jQuery("#audienceLegend").hide();
324 | jQuery("#trackLegend").hide();
325 | jQuery(".sessionDiv").removeClass("audienceColours").removeClass("trackColours");
326 | }
327 |
328 | localStorage.setItem('selectedColouring', eventObj.target.id);
329 | }
330 |
331 | function toggleAudienceCategories(eventObj) {
332 | // update localstorage
333 | var included = localStorage.getItem('includedAudiences').split('|');
334 | if (included.includes(eventObj.target.id)) {
335 | var index = included.indexOf(eventObj.target.id);
336 | included.splice(index, 1);
337 | } else {
338 | included.push(eventObj.target.id);
339 | }
340 | localStorage.setItem('includedAudiences', [...included].join('|'));
341 |
342 | jQuery(`#${eventObj.target.id}`).toggleClass("selected");
343 |
344 | reflow();
345 | }
346 |
347 | function toggleTrackCategories(eventObj) {
348 | // update localstorage
349 | var included = localStorage.getItem('includedTracks').split('|');
350 | if (included.includes(eventObj.target.id)) {
351 | var index = included.indexOf(eventObj.target.id);
352 | included.splice(index, 1);
353 | } else {
354 | included.push(eventObj.target.id);
355 | }
356 | localStorage.setItem('includedTracks', [...included].join('|'));
357 |
358 | jQuery(`#${eventObj.target.id}`).toggleClass("selected");
359 |
360 | reflow();
361 | }
362 |
363 | function toggleMainSessions(eventObj) {
364 | var jQele = jQuery(`#${eventObj.target.id}`);
365 |
366 | if (!jQele.hasClass('disabled')) {
367 | var newValue = !(localStorage.getItem('showAllSessions') === 'true');
368 | localStorage.setItem('showAllSessions', newValue);
369 |
370 | if ((newValue && !jQele.hasClass("selected")) ||
371 | (!newValue && jQele.hasClass("selected"))) {
372 | jQele.toggleClass("selected");
373 | }
374 |
375 | reflow();
376 | }
377 | }
378 |
379 | function sessionClick(eventObj) {
380 | var title = "";
381 | if (eventObj.target.tagName === "SPAN") {
382 | if (eventObj.target.className === "title") {
383 | title = eventObj.target.innerHTML;
384 | } else {
385 | title = eventObj.target.siblings()[1].innerHTML;
386 | }
387 | } else {
388 | title = eventObj.target.childNodes[2].innerHTML;
389 | }
390 |
391 | var mySched = localStorage.getItem('mySchedule');
392 | if (mySched) {
393 | mySched += '|' + title;
394 | } else {
395 | mySched = title;
396 | }
397 |
398 | localStorage.setItem('mySchedule', mySched);
399 | }
400 |
401 | function toggleShowSchedule(eventObj) {
402 | var jQele = jQuery(`#${eventObj.target.id}`);
403 | var newValue = !(localStorage.getItem('showSchedule') === 'true');
404 | localStorage.setItem('showSchedule', newValue);
405 |
406 | // update controls
407 | if ((newValue && !jQele.hasClass("selected")) ||
408 | (!newValue && jQele.hasClass("selected"))) {
409 | jQele.toggleClass("selected");
410 | }
411 |
412 | if (newValue) {
413 | // disable mainSessionsFilter
414 | jQuery("#mainSessionsFilter").removeClass("selected");
415 | jQuery("#mainSessionsFilter").addClass("disabled");
416 | } else {
417 | // reenable
418 | jQuery("#mainSessionsFilter").removeClass("disabled");
419 |
420 | var showingAll = (localStorage.getItem('showAllSessions') === 'true');
421 | localStorage.setItem('showAllSessions', !showingAll);
422 | jQuery("#mainSessionsFilter").click();
423 | }
424 |
425 | reflow();
426 | }
427 |
428 | function clearSchedule() {
429 | localStorage.removeItem('mySchedule');
430 |
431 | // update controls
432 | jQuery("#scheduleInput").val('');
433 | jQuery("#mainSessionsFilter").removeClass("disabled");
434 |
435 | // set it up so that we can toggle the showSchedule button
436 | localStorage.setItem('showSchedule', true);
437 | jQuery("#scheduleShowDiv").click();
438 |
439 | // no need reflow(), toggleShowSchedule will do it
440 | }
441 |
442 | function exportSchedule() {
443 | jQuery("#scheduleInput").val(localStorage.getItem('mySchedule'));
444 | }
445 |
446 | function importSchedule() {
447 | localStorage.setItem('mySchedule', jQuery("#scheduleInput").val());
448 | localStorage.setItem('showSchedule', 'false');
449 | jQuery("#scheduleShowDiv").click();
450 | }
451 |
452 | function createDiv() {
453 | jQuery('#schedCss').remove();
454 | jQuery('#output').remove();
455 |
456 | const trackColours = [
457 | '#bf968f', '#f29979', '#7f5940', '#e5b073', '#fff780',
458 | '#f2eeb6', '#798060', '#8fcc66', '#53a674', '#79f2da',
459 | '#53a0a6', '#80d5ff', '#7c98a6', '#80a2ff', '#99a0cc',
460 | '#8959b3', '#f279da', '#80406a', '#e6accb', '#ff8091',
461 | '#994d57'
462 | ];
463 |
464 | const audienceColours = [
465 | '#d97b6c', '#807560', '#ffe680', '#73e6a1', '#bff2ff',
466 | '#80c4ff', '#46628c', '#9c66cc', '#f2b6de', '#804059'
467 | ];
468 |
469 | // hard to see
470 | var flipColors = [
471 | '#80406a', '#994d57', '#46628c', '#804059', '#807560'
472 | ];
473 |
474 | var dayLegend = jQuery('