Commit f444af2e authored by Zac Wood's avatar Zac Wood

basic courses page

parent fdfb621b
......@@ -10,13 +10,14 @@
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require rails-ujs
// require turbolinks
//= require FileSaver
//= require_tree .
//= require cart
// require_tree .
// require jquery3
// require popper
// require bootstrap-sprockets
// require rails-ujs
const elementFromString = string => {
const html = new DOMParser().parseFromString(string, 'text/html');
......@@ -25,7 +26,7 @@ const elementFromString = string => {
document.addEventListener('DOMContentLoaded', () => {
this.cart = new Cart();
FontAwesome.dom.i2svg();
initGlobalListeners();
});
const setSemester = async select => {
......@@ -33,8 +34,7 @@ const setSemester = async select => {
location.reload(true);
};
/** Loads FontAwesome icons on load; fixes weird flickering */
FontAwesome.dom.watch({ observeMutationsRoot: document });
// document.addEventListener('turbolinks:load', () => {
// FontAwesome.dom.i2svg();
// });
const initGlobalListeners = () => {
const semesterSelect = document.getElementById('semester-select');
semesterSelect.onchange = () => setSemester(semesterSelect);
};
{
"compilerOptions": {
"lib": ["es2015", "dom"]
}
}
......@@ -10,28 +10,42 @@ document.addEventListener('DOMContentLoaded', () => {
header: false,
events: renderEvents,
});
// document.getElementById('numSchedules').innerText = window.events.length;
}
});
// let i = 0;
initListeners();
});
const renderEvents = (start, end, timezone, callback) => {
// document.getElementById('currentSchedule').innerText = i + 1;
callback(window.events);
};
// const nextSchedule = () => {
// if (i + 1 < window.events.length) i++;
const remove = async item => {
await window.cart.addSection({ ...item.dataset });
location.reload(true);
};
/**
* Generates a URL for the current sections in the schedule
* and sets the link in the modal to it.
*/
const setUrlInModal = () => {
document.getElementById('calendar-link').innerText = `${window.location.protocol}//${window.location.hostname}/api/schedules?section_ids=${window.cart._courses.join(',')}`;
};
// $('#calendar').fullCalendar('refetchEvents');
// };
const downloadIcs = async () => {
const response = await fetch(`http://localhost:3000/api/schedules?section_ids=${window.cart._courses.join(',')}`);
const text = await response.text();
const blob = new Blob([text], { type: 'text/calendar;charset=utf-8' });
saveAs(blob, 'GMU Schedule.ics');
};
// const prevSchedule = () => {
// if (i > 0) i--;
const addToSystemCalendar = () => {};
// $('#calendar').fullCalendar('refetchEvents');
const initListeners = () => {
const items = Array.from(document.querySelectorAll('.section-item'));
items.forEach(item => (item.onclick = () => remove(item)));
// console.log(window.events[i]);
// };
document.getElementById('open-modal-btn').onclick = setUrlInModal;
document.getElementById('download-ics').onclick = downloadIcs;
document.getElementById('add-to-system').onclick = addToSystemCalendar;
};
......@@ -3,20 +3,6 @@
const sectionWithCrn = crn => document.getElementById('search-list').querySelector(`[data-crn="${crn}"]`);
// const addCourse = async (event, id) => {
// event && event.stopPropagation();
// const courseCard = document.getElementById(`course-${id}`);
// const title = courseCard.querySelector('.title').innerText;
// const sectionsItems = Array.from(courseCard.querySelectorAll('li'));
// const filtered = sectionsItems.filter(li => {
// return !li.parentNode.classList.contains('pair') || li.dataset.type === 'Lecture';
// });
// for (const section of filtered) {
// await addOrRemoveFromCart(undefined, section);
// }
// }
/**
* Either adds or removes a section from the cart depending on
* if it is currently in the cart.
......@@ -46,10 +32,14 @@ const toggleSections = course => {
}
};
/**
* Generates a webcal:// URL for the current sections in the schedule
* and sets the link in the modal to it.
*/
const setUrlInModal = () => {
document.getElementById('calendar-link').innerText = `${window.location.protocol}//${window.location.hostname}/api/schedule?crns=${this.cart._courses.join(',')}`;
const initSearchListeners = () => {
const courseCards = Array.from(document.querySelectorAll('.course-card'));
courseCards.forEach(card => {
card.onclick = () => toggleSections(card);
});
const sectionItems = Array.from(document.querySelectorAll('.section-item'));
sectionItems.forEach(item => (item.onclick = event => addOrRemoveFromCart(event, item)));
};
document.addEventListener('DOMContentLoaded', initSearchListeners);
......@@ -10,7 +10,9 @@
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_tree .
* require_tree .
*= require cart
*= require navbar
*= require_self
*/
......@@ -32,26 +34,24 @@ body {
display: flex;
flex-direction: column;
}
.card-body {
.attr-list {
display: flex;
flex-direction: row;
}
.attr {
.icon {
padding-right: 4px;
}
align-items: center;
display: inline-flex;
white-space: nowrap;
}
}
.attr-list {
display: flex;
flex-direction: row;
.attr {
.icon {
padding-right: 4px;
}
align-items: center;
display: inline-flex;
white-space: nowrap;
}
}
}
.unpadded {
padding: 0px;
padding: 0px;
}
/* On mouse-over, add a deeper shadow */
......@@ -59,25 +59,6 @@ body {
box-shadow: 0 0 20px rgba(0,0,0,0.4);
}
.list-group-item:hover {
transition: 0.15s;
background-color: lightgray;
}
.list-group-item.selected {
background-color: lightgreen;
}
.list-group-item.selected:hover {
transition: 0.15s;
background-color: red;
}
.schedule-section-card:hover {
transition: 0.15s;
background-color: red;
}
.align-vertical {
display: flex;
align-items: center;
......
.cart-course {
display: flex; justify-content: space-between;
.title {
min-width: 15%;
}
.crns {
color: gray;
font-size: 10pt;
}
}
#cart {
display: none;
}
......
......@@ -2,3 +2,11 @@
background-color: white;
padding: 16px;
}
.section-item.selected {
background-color: white;
}
.section-item.selected:hover {
background-color: red;
}
// Place all the styles related to the search controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
.section-item:hover {
transition: 0.15s;
background-color: lightgray;
}
.section-item.selected {
background-color: lightgreen;
}
.section-item.selected:hover {
transition: 0.15s;
background-color: red;
}
.section-item:hover {
transition: 0.15s;
background-color: red;
}
......@@ -5,10 +5,10 @@ class API::SchedulesController < ApplicationController
# Render an iCal file containing the schedules of all the
# course sections with the given CRNs.
api :GET, '/schedules', 'Generate an iCal file with events for the given CRNs'
param :crns, String, desc: 'Comma separated list of CRNs to include as events in the calendar', required: true
param :section_ids, String, desc: 'Comma separated list of section ids to include as events in the calendar', required: true
def index
crns = params["crns"].split ','
@schedule = Schedule.new crns
ids = params["section_ids"].split ','
@schedule = Schedule.new ids
render plain: @schedule.to_ical # render a plaintext iCal file
end
end
......@@ -7,7 +7,9 @@ class ApplicationController < ActionController::Base
@semester = if cookies.key?(:semester_id)
Semester.find_by(id: cookies[:semester_id])
else
Semester.find_by(season: 'Spring', year: '2019')
sem = Semester.find_by(season: 'Spring', year: '2019')
cookies[:semester_id] = sem.id
sem
end
end
......
class CoursesController < ApplicationController
before_action :set_course
def show
end
private
def set_course
@course = Course.find_by_id params[:id]
end
end
......@@ -12,6 +12,10 @@ class Course < ApplicationRecord
validates :subject, presence: true
validates :semester_id, presence: true
def full_name
"#{subject} #{course_number}"
end
def self.from_subject(base_query, subject)
base_query.where("courses.subject = ?", subject.upcase)
end
......
require 'icalendar'
require 'time'
# Creates a iCal object given a list of CRNs
# Creates a iCal object given a list of section ids
class Schedule
def initialize(crns)
def initialize(ids)
@cal = Icalendar::Calendar.new
@cal.x_wr_calname = 'GMU Fall 2018'
@course_sections = crns.map do |crn|
CourseSection.find_by crn: crn
end
@course_sections = ids.map { |id| CourseSection.find_by_id id }
@course_sections.compact!
load_events
......
<div class="row">
<div class="col-5">
<h1><%= @course.full_name %></h1>
<h4><%= @course.title %></h4>
<div class="d-flex">
<div class="attr-list justify-content-start">
<div class="attr">
<div class="icon">
<i class="fa fa-book"></i>
</div>
3 credits
</div>
&nbsp;&nbsp;&nbsp;
<div class="attr">
<div class="icon">
<i class="fa fa-bars"></i>
</div>
<!-- TODO: FIX THIS -->
<%#= "#{course.section_count}" %> 3 sections
</div>
</div>
</div>
<p><%= @course.description %></p>
</div>
<div class ="col-7">
<%= render partial: 'shared/section', collection: @course.course_sections %>
</div>
</div>
......@@ -5,13 +5,9 @@
<%= csrf_meta_tags %>
<%= javascript_include_tag 'masonstrap.min' %>
<%= javascript_include_tag 'moment.min' %>
<%= javascript_include_tag 'fullcalendar.min' %>
<%= javascript_include_tag 'FileSaver' %>
<%= javascript_include_tag 'application' %>
<%= stylesheet_link_tag 'masonstrap.min' %>
<%= stylesheet_link_tag 'fullcalendar.min' %>
<%= stylesheet_link_tag 'application' %>
</head>
......
<!-- <button onclick="nextSchedule()">Next</button>
<button onclick="prevSchedule()">Prev</button>
<span id="currentSchedule">1</span> / <span id="numSchedules">1</span> -->
<%= javascript_include_tag 'schedules' %>
<%= stylesheet_link_tag 'schedules' %>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exportModal" onclick="setUrlInModal()">
<%= javascript_include_tag 'moment.min' %>
<%= javascript_include_tag 'FileSaver' %>
<%= javascript_include_tag 'fullcalendar.min'%>
<%= stylesheet_link_tag 'fullcalendar.min' %>
<button id="open-modal-btn" type="button" class="btn btn-primary" data-toggle="modal" data-target="#exportModal">
Generate Schedule
</button>
<div id="calendar"></div>
......@@ -46,12 +51,9 @@
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>
<button id="download-ics" type="button" class="btn btn-secondary">Download calendar file</button>
<button id="add-to-system" type="button" class="btn btn-primary">Add to system calendar</button>
</div>
</div>
</div>
</div>
......@@ -21,3 +21,5 @@
<% end %>
<%= javascript_include_tag 'search' %>
<%= stylesheet_link_tag 'search' %>
<% expanded = false unless defined? expanded %>
<div class="card" id="course-<%= course.id %>" onclick="toggleSections(this)">
<div class="card course-card" id="course-<%= course.id %>">
<div class="card-header">
<div class="row">
<div class="col">
......
......@@ -7,7 +7,7 @@
Schedules
</a>
&nbsp;&nbsp;
<select onchange="setSemester(this)">
<select id="semester-select">
<% Semester.all.each do |semester| %>
<option value="<%= semester.id %>" <% if @semester == semester %> selected <% end %> >
<%= "#{semester.season} #{semester.year}" %>
......
......@@ -4,12 +4,11 @@
data-id="<%= section.id %>"
data-cid="<%= course.id if defined?(course) %>"
data-type="<%= section.section_type %>"
onclick="addOrRemoveFromCart(event, 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>
<span style="float:left"><i class="fas fa-chalkboard-teacher"></i> <%= link_to section.instructor.name, instructor_path(section.instructor) %></span>
<span style="float:left"><i class="fas fa-chalkboard-teacher"></i> <%= link_to section.instructor.name, section.instructor %></span>
<span style="float:right"><i class="fas fa-clock"></i> <%= "#{section.days}, #{section.start_time}-#{section.end_time}" %></span>
<div style="clear: both"></div>
</li>
......@@ -12,9 +12,16 @@ Rails.application.config.assets.paths << Rails.root.join('node_modules')
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
Rails.application.config.assets.precompile += %w(
FileSaver.js
schedule.js
search.js
search.scss
schedules.js
schedules.scss
cart.js
cart.scss
FileSaver.js
masonstrap.min.css
masonstrap.min.js
moment.min.js
......
......@@ -110,7 +110,7 @@ def main
wipe_db
parser = PatriotWeb::Parser.new
semesters = parser.parse_semesters[0..6] # expand to include however many semesters you want
semesters = parser.parse_semesters[0..1] # expand to include however many semesters you want
courses = nil
semesters.each do |semester|
......
require 'test_helper'
class CoursesControllerTest < ActionDispatch::IntegrationTest
# test "the truth" do
# assert true
# end
end
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