Commit 3d04aa8c authored by Zac Wood's avatar Zac Wood
Browse files

Calendar export works

parent 483bddcc
Pipeline #2935 failed with stage
in 2 minutes and 22 seconds
......@@ -12,6 +12,7 @@
//
//= require rails-ujs
//= require turbolinks
//= require FileSaver
//= require_tree .
// require jquery3
// require popper
......
......@@ -4,7 +4,7 @@
class Schedule {
constructor() {
this.isOpen = false;
this._ids = Array.from(document.getElementById('cart-list').children).map(e => Number(e.id));
this._ids = Array.from(document.getElementById('cart-list').children).map(e => Number(e.id.split('-')[1]));
}
get ids() {
......@@ -15,7 +15,7 @@ class Schedule {
this._ids = ids;
document.getElementById('course-counter').innerText = ids.length;
fetch('/search/update?ids=' + ids.join(','));
fetch('/search/update?ids=' + ids.join(','), { cache: 'no-store' });
}
toggle() {
......@@ -34,9 +34,9 @@ class Schedule {
}
addToSchedule(section) {
if (this.ids.includes(section.id)) return;
if (this.ids.includes(section.crn)) return;
this.ids = [...this.ids, section.id];
this.ids = [...this.ids, section.crn];
const courses = document.getElementById('cart-list');
const newCourseCard = this._constructSectionCard(section);
......@@ -45,16 +45,15 @@ class Schedule {
removeFromSchedule(id) {
const cart = document.getElementById('cart-list');
const children = Array.from(cart.children);
const withId = children.findIndex(c => c.id == id);
cart.removeChild(children[withId]);
const section = cart.querySelector(`#section-${id}`);
cart.removeChild(section);
this.ids = this.ids.filter(_id => _id != Number(id));
}
_constructSectionCard(section) {
const str = `
<li id="${section.id}" class="list-group-item schedule-section-card" onclick="removeFromSchedule(this)">
<li id="section-${section.crn}" class="list-group-item schedule-section-card" onclick="removeFromSchedule(this)">
<span style="float:left"><b class="subj">${section.name}</b>: ${section.title}</span>
<span style="float:right"><i class="fas fa-map-marker-alt"></i> ${section.location} </span>
<div style="clear: both"></div>
......@@ -69,7 +68,7 @@ class Schedule {
class Search {
sectionWithId(sectionId) {
return document.getElementById('search-list').querySelector(`#section-${Number(sectionId)}`);
return document.getElementById('search-list').querySelector(`#${sectionId}`);
}
}
......@@ -85,7 +84,7 @@ const addToSchedule = (event, section) => {
const removeFromSchedule = section => {
this.search.sectionWithId(section.id).classList.remove('selected');
this.schedule.removeFromSchedule(section.id);
this.schedule.removeFromSchedule(section.id.split('-')[1]);
};
const toggleSections = course => {
......@@ -97,6 +96,22 @@ const toggleSections = course => {
}
};
const setUrlInModal = () => {
document.getElementById('calendar-link').innerText = `https://${window.location.hostname}/api/schedule?crns=${this.schedule.ids.join(',')}`;
};
const downloadIcs = async () => {
const cal = await fetch(`/api/schedules?crns=${this.schedule.ids.join(',')}`);
const text = await cal.text();
var blob = new Blob([text], { type: 'text/calendar;charset=utf-8' });
saveAs(blob, 'test.ics');
};
const addToSystemCalendar = async () => {
const url = `webcal://${window.location.hostname}/api/schedule?crns=${this.schedule.ids.join(',')}`;
window.open(url, '_self');
};
document.addEventListener('DOMContentLoaded', () => {
this.schedule = new Schedule();
this.search = new Search();
......
......@@ -4,8 +4,8 @@ class SearchController < ApplicationController
course.course_sections.count > 0
end
@cart = cookies[:ids].split(',').map do |id|
CourseSection.find_by_id id
@cart = cookies[:ids].split(',').map do |crn|
CourseSection.find_by_crn crn
end
end
......@@ -13,20 +13,4 @@ class SearchController < ApplicationController
puts params[:ids]
cookies[:ids] = params[:ids]
end
# def add
# ids = cookies[:ids].split(',').to_set
# ids.add(params[:id])
# cookies[:ids] = ids.to_a.join(',')
# end
# def remove
# ids = cookies[:ids].split(',').to_set
# ids.delete(params[:id])
# puts ids
# cookies[:ids] = ids.to_a.join(',')
# end
end
......@@ -58,9 +58,13 @@
<!-- List of Course Sections -->
<ul class="list-group list-group-flush" id="sections" style="display:none">
<% course.course_sections.each do |section| %>
<li id="section-<%= section.id %>"
<li id="section-<%= section.crn %>"
class="list-group-item section-item <%= "selected" if @cart.include? section %>"
data-section="<%= section.to_json %>"
data-name="<%= section.name %>"
data-title="<%= section.title %>"
data-instructor="<%= section.instructor.name %>"
data-time="<%= "#{section.days}, #{section.start_time}-#{section.end_time}" %>"
onclick="addToSchedule(event, this)"
>
<span style="float:left"><b class="subj"><%= "#{section.name}" %></b>: <%= section.title %></span>
......@@ -84,7 +88,7 @@
</div>
<ul class="list-group list-group-flush" id="cart-list">
<% @cart.each do |section| %>
<li id="<%= section.id %>" class="list-group-item schedule-section-card" data-section="<%= section.to_json %>" onclick="removeFromSchedule(this)">
<li id="section-<%= section.crn %>" class="list-group-item schedule-section-card" data-section="<%= section.to_json %>" onclick="removeFromSchedule(this)">
<span style="float:left"><b class="subj"><%= "#{section.name}" %></b>: <%= section.title %></span>
<span style="float:right"><i class="fas fa-map-marker-alt"></i> <%= section.location %></span>
<div style="clear: both"></div>
......@@ -95,7 +99,48 @@
<% end %>
</ul>
<div class="card-body">
<button class="btn btn-primary">Export Schedule</button>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal" onclick="setUrlInModal()">
Export schedule
</button>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Your calendar has been generated!</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<h5>Apple Calendar</h5>
To add your schedule to Apple Calendar, click the "Add to calendar" button below. If you are on a device
running macOS or iOS, this will open a dialogue which will walk you through adding the calendar.
<hr />
<h5>Google Calendar</h5>
<strong>On desktop:</strong>
<br />
Open your <a href="https://calendar.google.com/">Google Calendar</a>. Click the "Settings" button in the top
right, and then click the Settings tab. In the menu on the left, click "Add calendar" and "From URL". Now,
paste the following link inside the text box: <br />
<code id="calendar-link"></code>
<br />
<strong>On mobile (Android only):</strong>
<br />
Click the "Download calendar file" button. This will download the calendar file which you may then open and
add to your calendar.
<hr />
<h5>.ics file</h5>
To download a .ics file containing your schedule, click the "Download calendar file" button below.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="downloadIcs()">Download calendar file</button>
<button type="button" class="btn btn-primary" onclick="addToSystemCalendar()">Add to system calendar</button>
</div>
</div>
</div>
......
/* FileSaver.js
* A saveAs() FileSaver implementation.
* 1.3.8
* 2018-03-22 14:03:47
*
* By Eli Grey, https://eligrey.com
* License: MIT
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
*/
/*global self */
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/src/FileSaver.js */
var saveAs =
saveAs ||
(function(view) {
'use strict';
// IE <10 is explicitly unsupported
if (typeof view === 'undefined' || (typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent))) {
return;
}
var doc = view.document,
// only get URL when necessary in case Blob.js hasn't overridden it yet
get_URL = function() {
return view.URL || view.webkitURL || view;
},
save_link = doc.createElementNS('http://www.w3.org/1999/xhtml', 'a'),
can_use_save_link = 'download' in save_link,
click = function(node) {
var event = new MouseEvent('click');
node.dispatchEvent(event);
},
is_safari = /constructor/i.test(view.HTMLElement) || view.safari,
is_chrome_ios = /CriOS\/[\d]+/.test(navigator.userAgent),
setImmediate = view.setImmediate || view.setTimeout,
throw_outside = function(ex) {
setImmediate(function() {
throw ex;
}, 0);
},
force_saveable_type = 'application/octet-stream',
// the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
arbitrary_revoke_timeout = 1000 * 40, // in ms
revoke = function(file) {
var revoker = function() {
if (typeof file === 'string') {
// file is an object URL
get_URL().revokeObjectURL(file);
} else {
// file is a File
file.remove();
}
};
setTimeout(revoker, arbitrary_revoke_timeout);
},
dispatch = function(filesaver, event_types, event) {
event_types = [].concat(event_types);
var i = event_types.length;
while (i--) {
var listener = filesaver['on' + event_types[i]];
if (typeof listener === 'function') {
try {
listener.call(filesaver, event || filesaver);
} catch (ex) {
throw_outside(ex);
}
}
}
},
auto_bom = function(blob) {
// prepend BOM for UTF-8 XML and text/* types (including HTML)
// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
return new Blob([String.fromCharCode(0xfeff), blob], { type: blob.type });
}
return blob;
},
FileSaver = function(blob, name, no_auto_bom) {
if (!no_auto_bom) {
blob = auto_bom(blob);
}
// First try a.download, then web filesystem, then object URLs
var filesaver = this,
type = blob.type,
force = type === force_saveable_type,
object_url,
dispatch_all = function() {
dispatch(filesaver, 'writestart progress write writeend'.split(' '));
},
// on any filesys errors revert to saving with object URLs
fs_error = function() {
if ((is_chrome_ios || (force && is_safari)) && view.FileReader) {
// Safari doesn't allow downloading of blob urls
var reader = new FileReader();
reader.onloadend = function() {
var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
var popup = view.open(url, '_blank');
if (!popup) view.location.href = url;
url = undefined; // release reference before dispatching
filesaver.readyState = filesaver.DONE;
dispatch_all();
};
reader.readAsDataURL(blob);
filesaver.readyState = filesaver.INIT;
return;
}
// don't create more object URLs than needed
if (!object_url) {
object_url = get_URL().createObjectURL(blob);
}
if (force) {
view.location.href = object_url;
} else {
var opened = view.open(object_url, '_blank');
if (!opened) {
// Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
view.location.href = object_url;
}
}
filesaver.readyState = filesaver.DONE;
dispatch_all();
revoke(object_url);
};
filesaver.readyState = filesaver.INIT;
if (can_use_save_link) {
object_url = get_URL().createObjectURL(blob);
setImmediate(function() {
save_link.href = object_url;
save_link.download = name;
click(save_link);
dispatch_all();
revoke(object_url);
filesaver.readyState = filesaver.DONE;
}, 0);
return;
}
fs_error();
},
FS_proto = FileSaver.prototype,
saveAs = function(blob, name, no_auto_bom) {
return new FileSaver(blob, name || blob.name || 'download', no_auto_bom);
};
// IE 10+ (native saveAs)
if (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob) {
return function(blob, name, no_auto_bom) {
name = name || blob.name || 'download';
if (!no_auto_bom) {
blob = auto_bom(blob);
}
return navigator.msSaveOrOpenBlob(blob, name);
};
}
// todo: detect chrome extensions & packaged apps
//save_link.target = "_blank";
FS_proto.abort = function() {};
FS_proto.readyState = FS_proto.INIT = 0;
FS_proto.WRITING = 1;
FS_proto.DONE = 2;
FS_proto.error = FS_proto.onwritestart = FS_proto.onprogress = FS_proto.onwrite = FS_proto.onabort = FS_proto.onerror = FS_proto.onwriteend = null;
return saveAs;
})((typeof self !== 'undefined' && self) || (typeof window !== 'undefined' && window) || this);
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment