Commit 7361194d authored by Zac Wood's avatar Zac Wood
Browse files

The Webpackening

parent 758c4ec2
Pipeline #3510 failed with stages
in 2 minutes and 2 seconds
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": "> 1%",
"uglify": true
},
"useBuiltIns": true
}]
],
"plugins": [
"syntax-dynamic-import",
"transform-object-rest-spread",
["transform-class-properties", { "spec": true }]
]
}
...@@ -21,3 +21,8 @@ ...@@ -21,3 +21,8 @@
/yarn-error.log /yarn-error.log
.byebug_history .byebug_history
/public/packs
/public/packs-test
/node_modules
yarn-debug.log*
.yarn-integrity
plugins:
postcss-import: {}
postcss-cssnext: {}
...@@ -19,6 +19,8 @@ gem 'jbuilder', '~> 2.5' ...@@ -19,6 +19,8 @@ gem 'jbuilder', '~> 2.5'
gem 'uglifier' gem 'uglifier'
gem 'webpacker', '~> 3.5'
# Use Redis adapter to run Action Cable in production # Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0' # gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password # Use ActiveModel has_secure_password
......
...@@ -57,7 +57,7 @@ GEM ...@@ -57,7 +57,7 @@ GEM
childprocess (0.9.0) childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11) ffi (~> 1.0, >= 1.0.11)
coderay (1.1.2) coderay (1.1.2)
concurrent-ruby (1.1.3) concurrent-ruby (1.1.4)
crass (1.0.4) crass (1.0.4)
erubi (1.7.1) erubi (1.7.1)
execjs (2.7.0) execjs (2.7.0)
...@@ -101,7 +101,6 @@ GEM ...@@ -101,7 +101,6 @@ GEM
parallel (1.12.1) parallel (1.12.1)
parser (2.5.3.0) parser (2.5.3.0)
ast (~> 2.4.0) ast (~> 2.4.0)
pg (1.1.3)
powerpack (0.1.2) powerpack (0.1.2)
pry (0.12.2) pry (0.12.2)
coderay (~> 1.1.0) coderay (~> 1.1.0)
...@@ -113,6 +112,8 @@ GEM ...@@ -113,6 +112,8 @@ GEM
puma (3.12.0) puma (3.12.0)
rack (2.0.6) rack (2.0.6)
rack-cors (1.0.2) rack-cors (1.0.2)
rack-proxy (0.6.5)
rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (5.1.6.1) rails (5.1.6.1)
...@@ -197,6 +198,10 @@ GEM ...@@ -197,6 +198,10 @@ GEM
activemodel (>= 5.0) activemodel (>= 5.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 5.0) railties (>= 5.0)
webpacker (3.5.5)
activesupport (>= 4.2)
rack-proxy (>= 0.6.1)
railties (>= 4.2)
websocket-driver (0.6.5) websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3) websocket-extensions (0.1.3)
...@@ -217,7 +222,6 @@ DEPENDENCIES ...@@ -217,7 +222,6 @@ DEPENDENCIES
listen (>= 3.0.5, < 3.2) listen (>= 3.0.5, < 3.2)
maruku maruku
nokogiri nokogiri
pg
pry pry
pry-doc pry-doc
puma (~> 3.7) puma (~> 3.7)
...@@ -233,6 +237,7 @@ DEPENDENCIES ...@@ -233,6 +237,7 @@ DEPENDENCIES
tzinfo-data tzinfo-data
uglifier uglifier
web-console (>= 3.3.0) web-console (>= 3.3.0)
webpacker (~> 3.5)
BUNDLED WITH BUNDLED WITH
1.16.3 1.16.3
/**
* Either adds or removes a section from the cart depending on
* if it is currently in the cart.
*/
const addOrRemoveFromCart = async (event, sectionNode) => {
event && event.stopPropagation();
const section = { ...sectionNode.dataset };
await this.cart.addSection(section);
if (this.cart.includesSection(section)) {
sectionNode.classList.add('selected');
} else {
sectionNode.classList.remove('selected');
}
};
const initListeners = () => {
const items = Array.from(document.querySelectorAll('.section-item'));
items.forEach(item => (item.onclick = e => addOrRemoveFromCart(e, item)));
};
document.addEventListener('DOMContentLoaded', initListeners);
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
{
"compilerOptions": {
"lib": ["es2015", "dom"]
}
}
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
...@@ -21,7 +21,7 @@ class SchedulesController < ApplicationController ...@@ -21,7 +21,7 @@ class SchedulesController < ApplicationController
@all = params[:crns].split(',').map { |crn| @all = params[:crns].split(',').map { |crn|
CourseSection.latest_by_crn(crn) CourseSection.latest_by_crn(crn)
} }
@all.reject! { |s| s.nil? } @all.reject!(&:nil?)
@without_online = @all.reject { |s| @without_online = @all.reject { |s|
s.start_time == "TBA" || s.end_time == "TBA" s.start_time == "TBA" || s.end_time == "TBA"
} }
......
//= require FileSaver /* eslint no-console:0 */
//= require cart // This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
//
// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate
// layout file, like app/views/layouts/application.html.erb
import '@babel/polyfill';
import 'url-polyfill';
const elementFromString = string => { const elementFromString = string => {
const html = new DOMParser().parseFromString(string, 'text/html'); const html = new DOMParser().parseFromString(string, 'text/html');
...@@ -7,7 +16,6 @@ const elementFromString = string => { ...@@ -7,7 +16,6 @@ const elementFromString = string => {
}; };
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
this.cart = new Cart();
initGlobalListeners(); initGlobalListeners();
}); });
......
import Cart from 'src/cart';
import { saveAs } from 'file-saver';
import html2canvas from 'html2canvas';
import $ from 'jquery';
import 'fullcalendar';
import 'moment';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const eventsTemplate = document.querySelector('#events'); const eventsTemplate = document.querySelector('#events');
if (eventsTemplate) { if (eventsTemplate) {
...@@ -13,7 +20,6 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -13,7 +20,6 @@ document.addEventListener('DOMContentLoaded', () => {
allDaySlot: false, allDaySlot: false,
}); });
} }
initListeners(); initListeners();
}); });
...@@ -22,7 +28,7 @@ const renderEvents = (start, end, timezone, callback) => { ...@@ -22,7 +28,7 @@ const renderEvents = (start, end, timezone, callback) => {
}; };
const remove = async item => { const remove = async item => {
await window.cart.toggleSection({ ...item.dataset }); await Cart.toggleSection({ ...item.dataset });
location.reload(true); location.reload(true);
}; };
...@@ -31,22 +37,22 @@ const remove = async item => { ...@@ -31,22 +37,22 @@ const remove = async item => {
* and sets the link in the modal to it. * and sets the link in the modal to it.
*/ */
const setUrlInModal = () => { const setUrlInModal = () => {
document.getElementById('calendar-link').innerText = `${window.location.protocol}//${window.location.hostname}/api/schedules?crns=${window.cart._courses.join(',')}`; document.getElementById('calendar-link').innerText = `${window.location.protocol}//${window.location.hostname}/api/schedules?crns=${Cart._courses.join(',')}`;
}; };
const downloadIcs = async () => { const downloadIcs = async () => {
const response = await fetch(`${window.location.protocol}//${window.location.hostname}/api/schedules?crns=${window.cart._courses.join(',')}`); const response = await fetch(`${window.location.protocol}//${window.location.hostname}/api/schedules?crns=${Cart._courses.join(',')}`);
const text = await response.text(); const text = await response.text();
const blob = new Blob([text], { type: 'text/calendar;charset=utf-8' }); const blob = new Blob([text], { type: 'text/calendar;charset=utf-8' });
saveAs(blob, 'GMU Schedule.ics'); saveAs(blob, 'GMU Schedule.ics');
}; };
const addToSystemCalendar = () => { const addToSystemCalendar = () => {
window.open(`webcal://${window.location.hostname}/api/schedules?crns=${window.cart._courses.join(',')}`); window.open(`webcal://${window.location.hostname}/api/schedules?crns=${Cart._courses.join(',')}`);
}; };
const saveImage = () => { const saveImage = () => {
html2canvas(document.querySelector("#calendar")).then(canvas => { html2canvas(document.querySelector('#calendar')).then(canvas => {
canvas.toBlob(blob => { canvas.toBlob(blob => {
saveAs(blob, 'GMU Schedule.png'); saveAs(blob, 'GMU Schedule.png');
}); });
...@@ -62,5 +68,24 @@ const initListeners = () => { ...@@ -62,5 +68,24 @@ const initListeners = () => {
document.getElementById('add-to-system').onclick = addToSystemCalendar; document.getElementById('add-to-system').onclick = addToSystemCalendar;
document.getElementById('save-image').onclick = saveImage; document.getElementById('save-image').onclick = saveImage;
document.getElementById('share-url').innerText = `${window.location.protocol}//${window.location.hostname}/schedule/view?crns=${window.cart._courses.join(',')}`; document.getElementById('share-url').innerText = `${window.location.protocol}//${window.location.hostname}/schedule/view?crns=${Cart._courses.join(',')}`;
}; };
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function(callback, type, quality) {
var canvas = this;
setTimeout(function() {
var binStr = atob(canvas.toDataURL(type, quality).split(',')[1]),
len = binStr.length,
arr = new Uint8Array(len);
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i);
}
callback(new Blob([arr], { type: type || 'image/png' }));
});
},
});
}
import Cart from 'src/cart';
import { saveAs } from 'file-saver';
import html2canvas from 'html2canvas';
import $ from 'jquery';
import 'fullcalendar';
import 'moment';
import 'url-polyfill';
const params = new URLSearchParams(document.location.search); const params = new URLSearchParams(document.location.search);
const crns = params.get('crns'); const crns = params.get('crns');
...@@ -24,7 +32,6 @@ const renderEvents = (start, end, timezone, callback) => { ...@@ -24,7 +32,6 @@ const renderEvents = (start, end, timezone, callback) => {
callback(window.events); callback(window.events);
}; };
/** /**
* Generates a URL for the current sections in the schedule * Generates a URL for the current sections in the schedule
* and sets the link in the modal to it. * and sets the link in the modal to it.
...@@ -45,7 +52,7 @@ const addToSystemCalendar = () => { ...@@ -45,7 +52,7 @@ const addToSystemCalendar = () => {
}; };
const saveImage = () => { const saveImage = () => {
html2canvas(document.querySelector("#calendar")).then(canvas => { html2canvas(document.querySelector('#calendar')).then(canvas => {
canvas.toBlob(blob => { canvas.toBlob(blob => {
saveAs(blob, 'GMU Schedule.png'); saveAs(blob, 'GMU Schedule.png');
}); });
...@@ -56,6 +63,24 @@ const initListeners = () => { ...@@ -56,6 +63,24 @@ const initListeners = () => {
document.getElementById('open-modal-btn').onclick = setUrlInModal; document.getElementById('open-modal-btn').onclick = setUrlInModal;
document.getElementById('download-ics').onclick = downloadIcs; document.getElementById('download-ics').onclick = downloadIcs;
document.getElementById('add-to-system').onclick = addToSystemCalendar; document.getElementById('add-to-system').onclick = addToSystemCalendar;
document.getElementById('share-url').innerText = `${window.location.protocol}//${window.location.hostname}/schedule/view?crns=${crns}`;
document.getElementById('save-image').onclick = saveImage; document.getElementById('save-image').onclick = saveImage;
}; };
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function(callback, type, quality) {
var canvas = this;
setTimeout(function() {
var binStr = atob(canvas.toDataURL(type, quality).split(',')[1]),
len = binStr.length,
arr = new Uint8Array(len);
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i);
}
callback(new Blob([arr], { type: type || 'image/png' }));
});
},
});
}
// Place all the behaviors and hooks related to the matching controller here. // Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js. // All this logic will automatically be available in application.js.
import Cart from 'src/cart';
/** /**
* Either adds or removes a section from the cart depending on * Either adds or removes a section from the cart depending on
...@@ -9,10 +10,10 @@ const addOrRemoveFromCart = async (event, sectionNode) => { ...@@ -9,10 +10,10 @@ const addOrRemoveFromCart = async (event, sectionNode) => {
event && event.stopPropagation(); event && event.stopPropagation();
const section = { ...sectionNode.dataset }; const section = { ...sectionNode.dataset };
await this.cart.toggleSection(section); await Cart.toggleSection(section);
const icon = $(sectionNode.querySelector('.add-remove-btn #icon')); const icon = $(sectionNode.querySelector('.add-remove-btn #icon'));
const text = sectionNode.querySelector('.add-remove-btn .text'); const text = sectionNode.querySelector('.add-remove-btn .text');
if (this.cart.includesSection(section)) { if (Cart.includesSection(section)) {
icon.addClass('fa-minus').removeClass('fa-plus'); icon.addClass('fa-minus').removeClass('fa-plus');
text.innerText = 'Remove'; text.innerText = 'Remove';
} else { } else {
......
//import '@babel/polyfill';
class Cart { class Cart {
constructor() { constructor() {
this.isOpen = false; this.isOpen = false;
...@@ -9,6 +11,13 @@ class Cart { ...@@ -9,6 +11,13 @@ class Cart {
} }
} }
_parseData() {
const cartData = document.getElementById('cart-data');
if (cartData) {
this._courses = JSON.parse(cartData.dataset.cart);
}
}
toggle() { toggle() {
const list = document.getElementById('cart'); const list = document.getElementById('cart');
const icon = document.getElementById('schedule-icon'); const icon = document.getElementById('schedule-icon');
...@@ -33,7 +42,10 @@ class Cart { ...@@ -33,7 +42,10 @@ class Cart {
} }
async toggleSection(section) { async toggleSection(section) {
const resp = await fetch(`/sessions/cart?&crn=${section.crn}`, { cache: 'no-store' }); const resp = await fetch(`/sessions/cart?&crn=${section.crn}`, {
cache: 'no-store',
credentials: 'same-origin'
});
const json = await resp.json(); const json = await resp.json();
this.courses = json; this.courses = json;
} }
...@@ -47,3 +59,10 @@ class Cart { ...@@ -47,3 +59,10 @@ class Cart {
return false; return false;
} }
} }
const cart = new Cart();
document.addEventListener('DOMContentLoaded', () => cart._parseData());
export default cart;
...@@ -29,5 +29,5 @@ ...@@ -29,5 +29,5 @@
</div> </div>
<%= javascript_include_tag 'search' %> <%= javascript_pack_tag 'search' %>
<%= stylesheet_link_tag 'search' %> <%= stylesheet_link_tag 'search' %>
...@@ -21,5 +21,5 @@ ...@@ -21,5 +21,5 @@
</div> </div>
</div> </div>
<%= javascript_include_tag 'search' %> <%= javascript_pack_tag 'search' %>
<%= stylesheet_link_tag 'search' %> <%= stylesheet_link_tag 'search' %>
...@@ -6,11 +6,12 @@ ...@@ -6,11 +6,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<%= javascript_include_tag 'masonstrap.min' %> <%= javascript_include_tag 'masonstrap.min' %>
<%= javascript_include_tag 'application' %>
<%= stylesheet_link_tag 'masonstrap.min' %> <%= stylesheet_link_tag 'masonstrap.min' %>
<%= stylesheet_link_tag 'application' %> <%= stylesheet_link_tag 'application' %>
<%= javascript_pack_tag 'application' %>
<!-- FB/Opengraph tags --> <!-- FB/Opengraph tags -->
<meta property="og:url" content="https://schedules.gmu.edu/"> <meta property="og:url" content="https://schedules.gmu.edu/">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
......
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