Commit 5ca634f2 authored by Zac Wood's avatar Zac Wood

Revert "Merge branch '44-instructor-search' into 'master'"

This reverts merge request !48
parent ea27d283
Pipeline #4314 passed with stage
in 3 minutes and 33 seconds
{
"printWidth": 120,
"tabWidth": 4,
"singleQuote": true,
"useTabs": false,
"jsxBracketSameLine": true,
"trailingComma": "es5"
}
{
"presets": [
[
"env",
{
"modules": false,
"targets": {
"browsers": "> 1%",
"uglify": true
},
"useBuiltIns": true
}
],
"react"
["env", {
"modules": false,
"targets": {
"browsers": "> 1%",
"uglify": true
},
"useBuiltIns": true
}]
],
"plugins": [
"syntax-dynamic-import",
"transform-object-rest-spread",
[
"transform-class-properties",
{
"spec": true
}
]
["transform-class-properties", { "spec": true }]
]
}
......@@ -14,4 +14,3 @@ RUN export SECRET_KEY_BASE=$(rails secret)
RUN rails assets:precompile
RUN rails db:migrate
RUN rails db:seed
RUN rails runner db/load_course_ratings.rb
......@@ -21,9 +21,6 @@ gem 'uglifier'
gem 'webpacker', '~> 3.5'
# Access Ruby data from JavaScript
gem 'gon'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password
......@@ -77,3 +74,7 @@ gem 'apipie-rails'
# Markdown for API docs
gem 'maruku'
# gem 'jquery-rails'
# gem 'font-awesome-sass', '~> 5.3.1'
......@@ -64,10 +64,6 @@ GEM
ffi (1.9.25)
globalid (0.4.1)
activesupport (>= 4.2.0)
gon (6.2.1)
actionpack (>= 3.0)
multi_json
request_store (>= 1.0)
httparty (0.16.3)
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
......@@ -148,8 +144,6 @@ GEM
rb-fsevent (0.10.3)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
request_store (1.4.1)
rack (>= 1.4)
rubocop (0.58.2)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
......@@ -222,7 +216,6 @@ DEPENDENCIES
apipie-rails
byebug
capybara (~> 2.13)
gon
httparty
icalendar
jbuilder (~> 2.5)
......
// 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.
// Place all the styles related to the about controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
......@@ -20,11 +20,6 @@ body {
background-color: #E4E4E4;
}
#page {
padding-top: 16px;
padding-bottom: 16px;
}
.card {
margin-bottom: 12px;
......@@ -95,39 +90,3 @@ body {
.quick-add-header {
margin-top: 32px;
}
%flex-display {
display: inline-flex;
}
.star-rating {
@extend %flex-display;
align-items: center;
font-size: 1em;
justify-content: flex-start;
}
.back-stars {
@extend %flex-display;
position: relative;
text-shadow: 4px 4px 10px #843a3a;
}
.front-stars {
@extend %flex-display;
color: #FFBC0B;
overflow: hidden;
position: absolute;
text-shadow: 2px 2px 5px #d29b09;
top: 0;
}
.full-width {
width: 90vw;
position: relative;
left: 50%;
right: 50%;
margin-left: -45vw;
margin-right: -45vw;
}
// .jumbotron {
// color: #FFFFFF
// }
// Place all the styles related to the course_sections controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
......@@ -18,7 +18,6 @@
#semester-select {
min-width: 100px;
margin-right: 8px;
}
#cart-button {
......
class AboutController < ApplicationController
def index; end
end
......@@ -28,6 +28,7 @@ class API::CourseSectionsController < ApplicationController
@sections = @sections.where('UPPER(instructors.name) LIKE UPPER(?)', "%#{params[:instructor]}%")
end
# @sections = CourseSection.fetch(params).all
res = @sections.map do |s|
{
id: s.id,
......@@ -37,9 +38,6 @@ class API::CourseSectionsController < ApplicationController
crn: s.crn,
title: s.title,
instructor_name: s.instructor_name,
instructor_url: instructor_url(s.instructor),
teaching_rating: s.instructor.rating,
course_rating: s.course_rating,
section_type: s.section_type,
start_date: s.start_date,
end_date: s.end_date,
......
# Configures the application.
class ApplicationController < ActionController::Base
include BySemester
protect_from_forgery with: :null_session
# On each request, set the semester and cart.
before_action :set_semester, :set_cart
# Every page needs to know what semester it should load data from.
# set_semester checks both the semester_id query parameter and the user's cookies
# to look for a semester id and loads whatever it finds into @semester.
#
# By default, load the most recent semester.
def set_semester
if params.key?(:semester_id)
@semester = Semester.find_by_id params[:semester_id]
cookies[:semester_id] = @semester.id
elsif cookies[:semester_id].nil?
@semester = Semester.first
cookies[:semester_id] = @semester.id
else
@semester = Semester.find_by_id cookies[:semester_id]
end
end
# The user's cart is stored as a JSON-encoded list of CRNs.
# set_cart sets the @cart variable, which is a list of the sections represented by the CRNs.
def set_cart
# set the cart cookie to be empty if it doesn't already exist
cookies.permanent[:cart] = "[]" if cookies.permanent[:cart].nil?
# decode the JSON list into an array
@cart = JSON.parse(cookies.permanent[:cart])
# get rid of any invalid CRNs
@cart = @cart.reject { |crn| CourseSection.find_by_crn(crn).nil? }
# set the cookie to the JSON-encoded list of valid sections
cookies.permanent[:cart] = @cart.to_json
end
end
# BySemester contains logic for setting the current request's Semester.
module BySemester
extend ActiveSupport::Concern
included do
before_action :set_semester
end
# This page needs to know what semester it should load data from.
# set_semester checks both the semester_id query parameter and the current session
# to look for a semester id and loads whatever it finds into @semester.
#
# By default, load the most recent semester.
def set_semester
if params.key?(:semester_id)
@semester = Semester.find_by_id(params[:semester_id])
session[:semester_id] = @semester.id
elsif session[:semester_id].nil?
@semester = Semester.first
session[:semester_id] = @semester.id
else
@semester = Semester.find_by_id(session[:semester_id])
end
end
end
class CourseSectionsController < ApplicationController
def show
@section = CourseSection.find_by_id(params[:id])
end
end
......@@ -7,11 +7,14 @@ class InstructorsController < ApplicationController
@instructor = Instructor.find_by_id(params[:id])
# find the courses being taught this semester
sections = CourseSection.where(instructor: @instructor)
@semesters = sections.group_by do |s|
s.semester.to_s
end
sections = CourseSection.where(instructor: @instructor, semester: @semester)
@courses = Course.build_set(sections)
@rating = { teaching: @instructor.rating, respect: @instructor.rating(6) }
# build the list of courses the instructor has taught in the past
@past = []
@instructor.course_sections.map(&:course).each do |c|
@past << c unless @past.select { |past| past.full_name == c.full_name }.count.positive?
end
@past.sort_by!(&:full_name)
end
end
......@@ -2,32 +2,29 @@
class SchedulesController < ApplicationController
include SchedulesHelper
def show; end
def show
valid_crns = @cart.reject { |crn|
s = CourseSection.find_by_crn(crn)
s.nil?
}
def view
@all = params[:crns].split(',').map { |crn|
@all = valid_crns.map { |crn|
CourseSection.latest_by_crn(crn)
}
@all.reject!(&:nil?)
@without_online = @all.reject { |s|
s.start_time == "TBA" || s.end_time == "TBA"
}
@events = generate_fullcalender_events(@without_online)
end
def events
@cart = params[:crns].split(',')
.map { |crn| CourseSection.latest_by_crn(crn) }
.reject(&:nil?)
@without_online = @cart.reject { |s|
def view
@all = params[:crns].split(',').map { |crn|
CourseSection.latest_by_crn(crn)
}
@all.reject!(&:nil?)
@without_online = @all.reject { |s|
s.start_time == "TBA" || s.end_time == "TBA"
}
@events = generate_fullcalender_events(@without_online)
sections = @cart.map do |s|
s.serializable_hash.merge(instructor_name: s.instructor.name, instructor_url: instructor_url(s.instructor))
end
render json: { events: @events, sections: sections }
end
end
......@@ -2,57 +2,8 @@ class SearchController < ApplicationController
def index
redirect_to(home_url) unless params[:query].length > 1
if params[:query].casecmp('god').zero?
bell = Instructor.find_by_name('Jonathan Bell')
redirect_to(instructor_url(bell))
end
@instructors = nil
@courses = nil
/[[:alpha:]]{2,4} \d{3}/.match(params[:query]) do |m|
subj, num = m[0].split(' ')
course = Course.find_by(subject: subj.upcase, course_number: num)
redirect_to(course_url(course)) unless course.nil?
end
/[[:alpha:]]{2,4}/i.match(params[:query]) do |m|
@courses = Course.where(subject: m[0].upcase)
.joins(:course_sections)
.merge(CourseSection.in_semester(@semester))
.uniq
if @courses.empty?
@courses = Course.where("(courses.title LIKE ?)", "%#{params[:query]}%")
.joins(:course_sections)
.merge(CourseSection.in_semester(@semester))
.uniq
other = Course.where("(courses.description LIKE ?)", "%#{params[:query]}%")
.joins(:course_sections)
.merge(CourseSection.in_semester(@semester))
.uniq
@courses = [*@courses, *other].uniq
@instructors = Instructor.named(params[:query])
end
@courses.map! do |c|
c.serializable_hash.merge(url: course_url(c))
end
gon.courses = @courses
gon.instructors = @instructors
end
/[0-9]{5}/.match(params[:query]) do |m|
redirect_to(course_url(CourseSection.latest_by_crn(m[0]).course))
end
if @courses&.count == 1 && @instructors&.count&.zero?
redirect_to course_url(@courses.first["id"])
elsif @courses&.count&.zero? && @instructors&.count == 1
redirect_to instructor_url(@instructors.first)
end
results = SearchHelper::GenericItem.fetchall(String.new(params[:query]), semester: @semester).group_by(&:type)
@instructors = results[:instructor]&.map(&:data)
@courses = results[:course]&.map(&:data)
end
end
......@@ -19,9 +19,7 @@ module SchedulesHelper
{
title: s.name,
start: "#{formatted_date}T#{time}",
end: "#{formatted_date}T#{endtime}",
crn: s.crn,
active: true
end: "#{formatted_date}T#{endtime}"
}
end
end.flatten
......
module SearchHelper
def in_cart?(crn)
@cart.include? crn.to_s
end
class GenericQueryData
attr_reader :semester
attr_reader :sort_mode
......
......@@ -10,9 +10,6 @@
import '@babel/polyfill';
import 'url-polyfill';
import React from 'react';
import Cart from 'src/Cart';
const elementFromString = string => {
const html = new DOMParser().parseFromString(string, 'text/html');
return html.body.firstChild;
......
import React from 'react';
import ReactDOM from 'react-dom';
import Cart from 'src/Cart';
import QuickAdd from 'src/QuickAdd';
document.addEventListener('DOMContentLoaded', () => {
//const calendarUrl = `${window.location.protocol}//${window.location.hostname}${window.location.port == 3000 ? ':3000' : ''}/schedule`;
const calendarUrl = '/schedule';
ReactDOM.render(
<QuickAdd
loadCalendar={() => {
window.location.href = calendarUrl;
}}
/>,
document.getElementById('quick-add')
);
});
// /**
// * Either adds or removes a section from the cart depending on
// * if it is currently in the cart.
// */
import $ from 'jquery';
import Cart from 'src/Cart';
const addOrRemoveFromCart = async (event, sectionNode) => {
event && event.stopPropagation();
if (event.target.tagName === 'A') return;
const section = { ...sectionNode.dataset };
Cart.toggleCrn(section.crn);
const icon = $(sectionNode.querySelector('.add-remove-btn #icon'));
const text = sectionNode.querySelector('.add-remove-btn .text');
if (Cart.includesCrn(section.crn)) {
icon.addClass('fa-minus').removeClass('fa-plus');
text.innerText = 'Remove';
} else {
icon.addClass('fa-plus').removeClass('fa-minus');
text.innerText = 'Add';
}
};
const initSearchListeners = () => {
const sectionItems = Array.from(document.querySelectorAll('.section-item'));
sectionItems.forEach(item => {
item.onclick = event => addOrRemoveFromCart(event, item);
});
setTimeout(() => {
sectionItems.forEach(item => {
const icon = $(item.querySelector('.add-remove-btn #icon'));
const text = item.querySelector('.add-remove-btn .text');
if (Cart.includesCrn(item.dataset.crn)) {
icon.addClass('fa-minus').removeClass('fa-plus');
text.innerText = 'Remove';
} else {
icon.addClass('fa-plus').removeClass('fa-minus');
text.innerText = 'Add';
}
});
}, 100);
};
document.addEventListener('DOMContentLoaded', initSearchListeners);
import React from 'react';
import ReactDOM from 'react-dom';
import Cart from 'src/Cart';
import Cart from 'src/cart';
import { saveAs } from 'file-saver';
import html2canvas from 'html2canvas';
import $ from 'jquery';
import CalendarPage from 'src/CalendarPage';
import 'fullcalendar';
import 'moment';
document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(<CalendarPage />, document.getElementById('root'));
const eventsTemplate = document.querySelector('#events');
if (eventsTemplate) {
const eventsJSON = eventsTemplate.dataset.events;
const events = JSON.parse(eventsJSON);
window.events = events;
$('#calendar').fullCalendar({
defaultDate: new Date(2019, 0, 14),
defaultView: 'agendaWeek',
header: false,
events: renderEvents,
columnHeaderFormat: 'dddd',
allDaySlot: false,
});
}
initListeners();
});
const renderEvents = (start, end, timezone, callback) => {
callback(window.events);
};
const remove = async item => {
await Cart.toggleSection({ ...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?crns=${Cart._courses.join(',')}`;
};
const downloadIcs = async () => {
const response = await fetch(`${window.location.protocol}//${window.location.hostname}/api/schedules?crns=${Cart._courses.join(',')}`);
const text = await response.text();
......@@ -39,7 +69,7 @@ const initListeners = () => {
document.getElementById('save-image').onclick = saveImage;
document.getElementById('share-url').innerText = `${window.location.protocol}//${window.location.hostname}/schedule/view?crns=${Cart._courses.join(',')}`;
document.getElementById('share-url').href = `${window.location.protocol}//${window.location.hostname}/schedule/view?crns=${Cart._courses.join(',')}`;
document.getElementById('share-url').href= `${window.location.protocol}//${window.location.hostname}/schedule/view?crns=${Cart._courses.join(',')}`;
};
if (!HTMLCanvasElement.prototype.toBlob) {
......
import Cart from 'src/Cart';
import Cart from 'src/cart';
import { saveAs } from 'file-saver';
import html2canvas from 'html2canvas';
import $ from 'jquery';
......
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
// import Cart from 'src/cart';
import Cart from 'src/cart';
// /**
// * Toggles the display of the schedule
// */
// const toggleSections = course => {
// const sections = course.querySelector('.sections');
// const chev = $(course.querySelector('#course-chevron'));
// const label = course.querySelector('#chevron-label');
/**
* 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 };
// if (sections.style.display === 'flex') {
// sections.style.display = 'none';
// chev.addClass('fa-chevron-down').removeClass('fa-chevron-up');
// label.innerText = 'Expand';
// } else {
// sections.style.display = 'flex';
// chev.addClass('fa-chevron-up').removeClass('fa-chevron-down');
// label.innerText = 'Minimize';
// }
// };
await Cart.toggleSection(section);
const icon = $(sectionNode.querySelector('.add-remove-btn #icon'));
const text = sectionNode.querySelector('.add-remove-btn .text');
if (Cart.includesSection(section)) {
icon.addClass('fa-minus').removeClass('fa-plus');
text.innerText = 'Remove';
} else {
icon.addClass('fa-plus').removeClass('fa-minus');
text.innerText = 'Add';
}
};
import React from 'react';
import ReactDOM from 'react-dom';
import SearchList from 'src/SearchList';
/**
* Toggles the display of the schedule
*/
const toggleSections = course => {
const sections = course.querySelector('.sections');
const chev = $(course.querySelector('#course-chevron'));
const label = course.querySelector('#chevron-label');
document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(<SearchList courses={gon.courses} instructors={gon.instructors} />, document.getElementById('root'));
});
if (sections.style.display === 'flex') {
sections.style.display = 'none';
chev.addClass('fa-chevron-down').removeClass('fa-chevron-up');
label.innerText = 'Expand';
} else {
sections.style.display = 'flex';
chev.addClass('fa-chevron-up').removeClass('fa-chevron-down');
label.innerText = 'Minimize';
}
};
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);
import React from 'react';
import BigCalendar from 'react-big-calendar';
import Toolbar from 'src/Toolbar';
import moment from 'moment';
import '!style-loader!css-loader!react-big-calendar/lib/css/react-big-calendar.css';
import withSizes from 'react-sizes';
const localizer = BigCalendar.momentLocalizer(moment);
const Calendar = props => (
<div className="full-width" style={{ backgroundColor: 'white', padding: '24px' }}>
<BigCalendar
localizer={localizer}
events={props.events}
title=""
components={{ toolbar: Toolbar }}
defaultView="week"
views={['week', 'day']}
startAccessor="start"
endAccessor="end"