Commit 25bd1134 authored by Zac Wood's avatar Zac Wood
Browse files

Added instructor page and links on section cards to its instructor's page

parent 64d7d103
Pipeline #2976 passed with stage
in 2 minutes and 23 seconds
class Schedule {
constructor() {
this.isOpen = false;
this._ids = Array.from(document.getElementById('schedule').children).map(e => e.dataset.crn);
}
get ids() {
return this._ids;
}
set ids(ids) {
this._ids = ids;
document.getElementById('course-counter').innerText = ids.length;
fetch('/search/update?ids=' + ids.join(','), { cache: 'no-store' });
}
toggle() {
const list = document.getElementById('cart');
const icon = document.getElementById('schedule-icon');
if (this.isOpen) {
list.style.display = 'none';
icon.style.color = 'black';
} else {
list.style.display = 'block';
icon.style.color = 'green';
}
this.isOpen = !this.isOpen;
}
addToSchedule(section) {
if (this.ids.includes(section.dataset.crn)) return;
this.ids = [...this.ids, section.dataset.crn];
section.classList.remove('section-item');
section.classList.remove('selected');
section.classList.add('schedule-section-card');
section.onclick = () => removeFromSchedule(section);
document.getElementById('schedule').appendChild(section);
}
removeFromSchedule(id) {
const cart = document.getElementById('schedule');
const section = cart.querySelector(`#section-${id}`);
cart.removeChild(section);
this.ids = this.ids.filter(_id => _id != id);
}
async downloadIcs() {
const cal = await fetch(`/api/schedules?crns=${this.ids.join(',')}`);
const text = await cal.text();
var blob = new Blob([text], { type: 'text/calendar;charset=utf-8' });
saveAs(blob, 'test.ics');
}
async addToSystemCalendar() {
const url = `webcal://${window.location.hostname}/api/schedule?crns=${this.ids.join(',')}`;
window.open(url, '_self');
}
}
// 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.
class Schedule {
constructor() {
this.isOpen = false;
this._ids = Array.from(document.getElementById('schedule').children).map(e => e.dataset.crn);
}
get ids() {
return this._ids;
}
set ids(ids) {
this._ids = ids;
document.getElementById('course-counter').innerText = ids.length;
fetch('/search/update?ids=' + ids.join(','), { cache: 'no-store' });
}
toggle() {
const list = document.getElementById('cart');
const icon = document.getElementById('schedule-icon');
if (this.isOpen) {
list.style.display = 'none';
icon.style.color = 'black';
} else {
list.style.display = 'block';
icon.style.color = 'green';
}
this.isOpen = !this.isOpen;
}
addToSchedule(section) {
if (this.ids.includes(section.dataset.crn)) return;
this.ids = [...this.ids, section.dataset.crn];
section.classList.remove('section-item');
section.classList.remove('selected');
section.classList.add('schedule-section-card');
section.onclick = () => removeFromSchedule(section);
document.getElementById('schedule').appendChild(section);
}
removeFromSchedule(id) {
const cart = document.getElementById('schedule');
const section = cart.querySelector(`#section-${id}`);
cart.removeChild(section);
this.ids = this.ids.filter(_id => _id != id);
}
_constructSectionCard(section) {
const str = `
<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>
<span style="float:left"><i class="fas fa-chalkboard-teacher"></i> TODO </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>`;
return elementFromString(str);
}
}
class Search { class Search {
sectionWithCrn(crn) { sectionWithCrn(crn) {
return document.getElementById('search-list').querySelector(`[data-crn="${crn}"]`); return document.getElementById('search-list').querySelector(`[data-crn="${crn}"]`);
} }
} }
const toggleSchedule = () => this.schedule.toggle();
const addToSchedule = (event, section) => { const addToSchedule = (event, section) => {
section.classList.add('selected'); section.classList.add('selected');
...@@ -86,8 +16,12 @@ const addToSchedule = (event, section) => { ...@@ -86,8 +16,12 @@ const addToSchedule = (event, section) => {
}; };
const removeFromSchedule = section => { const removeFromSchedule = section => {
this.search.sectionWithCrn(section.dataset.crn).classList.remove('selected'); const sectionInSearch = this.search.sectionWithCrn(section.dataset.crn);
this.schedule.removeFromSchedule(section.id.split('-')[1]); if (sectionInSearch) {
sectionInSearch.classList.remove('selected');
}
this.schedule.removeFromSchedule(section.dataset.crn);
}; };
const toggleSections = course => { const toggleSections = course => {
...@@ -102,15 +36,3 @@ const toggleSections = course => { ...@@ -102,15 +36,3 @@ const toggleSections = course => {
const setUrlInModal = () => { const setUrlInModal = () => {
document.getElementById('calendar-link').innerText = `https://${window.location.hostname}/api/schedule?crns=${this.schedule.ids.join(',')}`; 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');
};
...@@ -14,7 +14,83 @@ ...@@ -14,7 +14,83 @@
*= require_self *= require_self
*/ */
// @import "bootstrap"; body {
background-color: #E4E4E4;
}
// @import "font-awesome-sprockets"; .card {
// @import "font-awesome"; margin-bottom: 12px;
background-color: white;
border-radius: 8px;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
transition: 0.3s;
}
/* On mouse-over, add a deeper shadow */
.card:hover {
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;
}
.schedule-section-card:hover {
transition: 0.15s;
background-color: red;
}
.align-vertical {
display: flex;
align-items: center;
}
.align-left {
display: flex;
justify-content: flex-start;
align-items: center;
}
.align-center {
display: flex;
justify-content: center;
align-items: center;
}
.align-right {
display: flex;
justify-content: flex-end;
align-items: center;
}
#navbar {
margin-top: 8px;
margin-bottom: 48px;
}
#logo {
font-size: 24pt;
color: black;
}
.form-control:focus {
border-color: transparent;
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.075) inset, 0px 0px 0px rgba(0, 0, 255, 0.5);
}
#cart {
display: none;
}
.card .small {
margin-bottom: 16px;
padding: 12px;
}
// Place all the styles related to the search controller here. // Place all the styles related to the search controller here.
// They will automatically be included in application.css. // They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/ // You can use Sass (SCSS) here: http://sass-lang.com/
body {
background-color: #E4E4E4;
}
// .container {
// margin: auto;
// width: 100%;
// }
.card {
margin-bottom: 12px;
background-color: white;
border-radius: 8px;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
transition: 0.3s;
}
/* On mouse-over, add a deeper shadow */
.card:hover {
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;
}
.schedule-section-card:hover {
transition: 0.15s;
background-color: red;
}
.align-vertical {
display: flex;
align-items: center;
}
.align-left {
display: flex;
justify-content: flex-start;
align-items: center;
}
.align-center {
display: flex;
justify-content: center;
align-items: center;
}
.align-right {
display: flex;
justify-content: flex-end;
align-items: center;
}
#navbar {
margin-top: 8px;
margin-bottom: 48px;
}
#logo {
font-size: 24pt;
color: black;
}
.form-control:focus {
border-color: transparent;
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.075) inset, 0px 0px 0px rgba(0, 0, 255, 0.5);
}
#cart {
display: none;
}
.card .small {
margin-bottom: 16px;
padding: 12px;
}
class InstructorsController < ApplicationController class InstructorsController < ApplicationController
before_action :set_instructor, only: [:show]
def index def index
@instructors = Instructor.all
end end
def show def show
sections = CourseSection.where instructor: @instructor
# TODO: move this to a model somewhere
@courses = [].to_set
sections.each do |s|
@courses.add s.course
end
end
private
def set_instructor
@instructor = Instructor.find_by_id params[:id]
end end
end end
<h1>Instructors#index</h1> <h1>Instructors</h1>
<p>Find me in app/views/instructors/index.html.erb</p>
<ul>
<% @instructors.each do |i| %>
<li><%= link_to i.name, instructor_path(i) %></li>
<% end %>
</ul>
<h1>Instructors#show</h1> <h1><%= @instructor.name %></h1>
<p>Find me in app/views/instructors/show.html.erb</p>
<% if @courses.any? %>
<%= render partial: 'shared/course', collection: @courses, locals: { expanded: true } %>
<% else %>
<p><%= @instructor.name %> has not taught any courses...</p>
<% end %>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html data-turbolinks="false">
<head> <head>
<title>Schedules</title> <title>Schedules</title>
<%= csrf_meta_tags %> <%= csrf_meta_tags %>
<link href="https://srct.gmu.io/masonstrap/css/masonstrap.min.css" rel="stylesheet">
<script src="https://srct.gmu.io/masonstrap/js/masonstrap.min.js"></script>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'masonstrap.min' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'FileSaver' %>
<%= javascript_include_tag 'application' %>
<%= stylesheet_link_tag 'masonstrap.min' %>
<%= stylesheet_link_tag 'application' %>
</head> </head>
<body> <body data-turbolinks="false">
<%= render partial: 'shared/navbar' %> <%= render partial: 'shared/navbar' %>
<%= yield %> <%= yield %>
<%= render partial: 'shared/cart'%> <%= render partial: 'shared/cart'%>
</body> </body>
<%= javascript_tag do %>
document.addEventListener('DOMContentLoaded', () => {
this.schedule = new Schedule();
});
document.addEventListener('turbolinks:load', () => {
FontAwesome.dom.i2svg();
});
<% end %>
</html> </html>
...@@ -5,48 +5,8 @@ ...@@ -5,48 +5,8 @@
<p>Please try again!</p> <p>Please try again!</p>
<% end %> <% end %>
<!-- Export 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>
</div>
<%= javascript_tag do %> <%= javascript_tag do %>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
this.schedule = new Schedule();
this.search = new Search(); this.search = new Search();
}); });
<% end %> <% end %>
...@@ -21,3 +21,42 @@ ...@@ -21,3 +21,42 @@
</div> </div>
</div> </div>
<!-- Export 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>
</div>
<% expanded = false unless defined? expanded %>
<div class="card" id="course-<%= course.id %>" onclick="toggleSections(this)" data-course="<%= course.to_json %>"> <div class="card" id="course-<%= course.id %>" onclick="toggleSections(this)" data-course="<%= course.to_json %>">
<div class="card-body"> <div class="card-body">
<div> <div>
...@@ -14,8 +16,12 @@ ...@@ -14,8 +16,12 @@
</div> </div>
<!-- List of Course Sections --> <!-- List of Course Sections -->
<ul class="list-group list-group-flush" id="sections" style="display:none"> <ul class="list-group list-group-flush" id="sections" style="display: <%= expanded ? "block" : "none" %>">
<%= render partial: 'shared/section', collection: course.course_sections, locals: { in_cart: false } %> <% if defined?(@instructor) %>
<%= render partial: 'shared/section', collection: course.course_sections.where(instructor: @instructor), locals: { in_cart: false } %>
<% else %>
<%= render partial: 'shared/section', collection: course.course_sections, locals: { in_cart: false } %>
<% end %>
</ul>