Commit 98c6b181 authored by Zac Wood's avatar Zac Wood

Start redesign

parent 98455010
Pipeline #4989 failed with stage
in 2 minutes and 47 seconds
.vscode .vscode
schedules.code-workspace schedules.code-workspace
schedules/config/test.html
...@@ -108,6 +108,7 @@ GEM ...@@ -108,6 +108,7 @@ GEM
parser (2.6.4.1) parser (2.6.4.1)
ast (~> 2.4.0) ast (~> 2.4.0)
powerpack (0.1.2) powerpack (0.1.2)
prettier (0.15.0)
pry (0.12.2) pry (0.12.2)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.9.0) method_source (~> 0.9.0)
...@@ -240,6 +241,7 @@ DEPENDENCIES ...@@ -240,6 +241,7 @@ DEPENDENCIES
listen (>= 3.0.5, < 3.2) listen (>= 3.0.5, < 3.2)
maruku maruku
nokogiri nokogiri
prettier
pry pry
pry-doc pry-doc
puma (~> 3.7) puma (~> 3.7)
...@@ -258,4 +260,4 @@ DEPENDENCIES ...@@ -258,4 +260,4 @@ DEPENDENCIES
webpacker (~> 3.5) webpacker (~> 3.5)
BUNDLED WITH BUNDLED WITH
1.16.3 1.17.3
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
//
//= require action_cable
//= require_self
//= require_tree ./channels
(function() {
this.App || (this.App = {});
App.cable = ActionCable.createConsumer();
}).call(this);
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
...@@ -10,90 +10,146 @@ ...@@ -10,90 +10,146 @@
* files in this directory. Styles in this file should be added after the last require_* statement. * 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. * It is generally better to create a new file per style scope.
* *
* require_tree . *= require_tree .
*= require cart
*= require navbar
*= require_self *= require_self
*/ */
body { $gray: #4a4a4a;
background-color: #E4E4E4; $gold: #febf10;
} $green: #01693f;
#page { * {
padding-top: 16px; font-family: 'Roboto', sans-serif;
padding-bottom: 16px;
} }
.card { h1,
margin-bottom: 12px; h2,
h3,
background-color: white; h4,
h5,
border-radius: 8px; h6 {
font-family: 'Open Sans', sans-serif;
box-shadow: 0 0 5px rgba(0,0,0,0.2); }
transition: 0.3s;
.card-header { .hero {
display: flex; margin-top: 30%;
flex-direction: column; h1 {
margin-top: 1em;
margin-bottom: 1em;
} }
} }
.attr-list { #search-container {
margin: 0 auto;
border-radius: 5px;
box-shadow: 0 0 4px $gray;
width: 75%;
border: none;
height: 2em;
outline: none;
display: flex; display: flex;
flex-direction: row;
& input {
.attr { flex-grow: 1;
.icon { outline: none;
padding-right: 4px; border: none;
} height: 100%;
align-items: center; margin-left: 1em;
display: inline-flex; margin-right: 1em;
white-space: nowrap; font-family: 'Roboto', sans-serif;
} }
}
.unpadded { & i {
padding: 0px; width: 2em;
} margin: auto;
color: #297dc5;
}
/* On mouse-over, add a deeper shadow */ & button {
.card:hover { background-color: transparent;
box-shadow: 0 0 20px rgba(0,0,0,0.4); border: none;
}
} }
.align-vertical { nav {
display: flex; display: flex;
align-items: center; padding-top: 1em;
} font-family: 'Open Sans', sans-serif;
.align-left { margin-bottom: 2em;
display: flex;
justify-content: flex-start; // schedules logo
align-items: center; > span:first-child {
white-space: nowrap;
font-size: 1.5em;
a {
color: black;
}
}
} }
.align-center { @media only screen and (max-width: 600px) {
display: flex; nav {
justify-content: center; text-align: center;
align-items: center; flex-direction: column;
> span:first-child {
margin-bottom: 0.5em;
}
}
#search-container {
width: 90%;
}
} }
.align-right { .course {
display: flex; box-shadow: 0 3px 5px -2px $gray;
justify-content: flex-end; border-radius: 5px;
align-items: center; padding: 1em;
margin-bottom: 1em;
border-top: 5px solid $gold;
h4 {
margin-bottom: 0.3em;
a {
color: $green;
font-weight: bolder;
font-family: 'Open Sans', sans-serif;
}
}
h5 {
color: $gray;
font-weight: 400;
}
.description {
display: block;
margin-top: 1em;
line-height: 1.7em;
font-family: 'Roboto', sans-serif;
color: $gray;
font-size: 0.9em;
}
.see-more {
display: block;
text-align: center;
color: $green;
font-weight: bold;
margin-top: 1em;
text-decoration: underline;
}
} }
.form-control:focus { .card {
border-color: transparent; border-radius: 5px;
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.075) inset, 0px 0px 0px rgba(0, 0, 255, 0.5);
} }
.quick-add-header { a {
margin-top: 32px; color: $green;
} }
%flex-display { %flex-display {
...@@ -108,26 +164,35 @@ body { ...@@ -108,26 +164,35 @@ body {
.back-stars { .back-stars {
@extend %flex-display; @extend %flex-display;
position: relative; position: relative;
text-shadow: 4px 4px 10px #843a3a;
} }
.front-stars { .front-stars {
@extend %flex-display; @extend %flex-display;
color: #FFBC0B; color: $gold;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
text-shadow: 2px 2px 5px #d29b09;
top: 0; top: 0;
} }
.full-width { .details {
width: 90vw; margin-top: 1em;
position: relative; p {
left: 50%; margin-bottom: 0.2em;
right: 50%; }
margin-left: -45vw; }
margin-right: -45vw;
.semester-select {
margin-bottom: 1em;
}
.section-type-label {
margin-top: 1em;
} }
// .jumbotron { .section-list {
// color: #FFFFFF list-style: none;
// } margin: 0;
padding: 0;
> * {
margin-bottom: 0.5em;
}
}
...@@ -12,14 +12,10 @@ module BySemester ...@@ -12,14 +12,10 @@ module BySemester
# #
# By default, load the most recent semester. # By default, load the most recent semester.
def set_semester def set_semester
if params.key?(:semester_id) @semester = if params.key?(:semester_id)
@semester = Semester.find_by_id(params[:semester_id]) Semester.find_by_id(params[:semester_id])
session[:semester_id] = @semester.id else
elsif session[:semester_id].nil? Semester.first
@semester = Semester.first end
session[:semester_id] = @semester.id
else
@semester = Semester.find_by_id(session[:semester_id])
end
end end
end end
...@@ -3,6 +3,34 @@ class CoursesController < ApplicationController ...@@ -3,6 +3,34 @@ class CoursesController < ApplicationController
# Load the course with the id passed in the URL. # Load the course with the id passed in the URL.
@course = Course.find_by_id(params[:id]) @course = Course.find_by_id(params[:id])
@rating = @course.rating @rating = @course.rating
@sections = @course.course_sections.where(semester: @semester)
semester_ids = @course.course_sections
.joins(:semester)
.select("semesters.id")
@semesters = Semester.where(id: semester_ids.map(&:id))
@semesters = Semester.sorted_by_date(@semesters)
@taught_in = Set.new(@semesters.map(&:season))
@taught_in = sort_seasons(@taught_in.to_a).join(", ")
@sections = @course.course_sections.where(semester: @semester).group_by { |s| s.section_type }
end end
end
private
def sort_seasons(seasons)
seasons.sort do |s1, s2|
case
when s1 == "Fall"
-1
when s1 == "Summer" && s2 == "Fall"
1
when s1 == "Spring"
1
else
0
end
end
end
end
\ No newline at end of file
...@@ -8,9 +8,14 @@ class InstructorsController < ApplicationController ...@@ -8,9 +8,14 @@ class InstructorsController < ApplicationController
# find the courses being taught this semester # find the courses being taught this semester
sections = CourseSection.where(instructor: @instructor) sections = CourseSection.where(instructor: @instructor)
@semesters = sections.group_by do |s| semester_ids = sections
s.semester.to_s .joins(:semester)
end .select("semesters.id")
@semesters = Semester.where(id: semester_ids.map(&:id))
@semesters = Semester.sorted_by_date(@semesters)
@sections = sections.where(semester: @semester).group_by { |s| s.section_type }
@rating = { teaching: @instructor.rating, respect: @instructor.rating(6) } @rating = { teaching: @instructor.rating, respect: @instructor.rating(6) }
end end
......
...@@ -5,10 +5,11 @@ class SchedulesController < ApplicationController ...@@ -5,10 +5,11 @@ class SchedulesController < ApplicationController
def show; end def show; end
def view def view
@all = params[:crns].split(',').map { |crn| @all = params[:crns]
CourseSection.latest_by_crn(crn) .split(',')
} .map { |crn| CourseSection.latest_by_crn(crn) }
@all.reject!(&:nil?) .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"
} }
......
class SearchController < ApplicationController class SearchController < ApplicationController
def index def index
params[:query].strip!
redirect_to(home_url) unless params[:query].length > 1 redirect_to(home_url) unless params[:query].length > 1
if params[:query].casecmp('god').zero? if params[:query].casecmp('god').zero?
...@@ -41,8 +42,6 @@ class SearchController < ApplicationController ...@@ -41,8 +42,6 @@ class SearchController < ApplicationController
@courses.map! do |c| @courses.map! do |c|
c.serializable_hash.merge(url: course_url(c)) c.serializable_hash.merge(url: course_url(c))
end end
gon.courses = @courses
gon.instructors = @instructors
end end
/[0-9]{5}/.match(params[:query]) do |m| /[0-9]{5}/.match(params[:query]) do |m|
......
...@@ -7,32 +7,13 @@ ...@@ -7,32 +7,13 @@
// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate
// layout file, like app/views/layouts/application.html.erb // layout file, like app/views/layouts/application.html.erb
import '@babel/polyfill';
import 'url-polyfill'; import 'url-polyfill';
import React from 'react';
import Cart from 'src/Cart';
const elementFromString = string => { const elementFromString = string => {
const html = new DOMParser().parseFromString(string, 'text/html'); const html = new DOMParser().parseFromString(string, 'text/html');
return html.body.firstChild; return html.body.firstChild;
}; };
document.addEventListener('DOMContentLoaded', () => {
initGlobalListeners();
});
const setSemester = async select => {
const url = new URL(window.location.href);
url.searchParams.set('semester_id', select.value);
window.open(url.toString(), '_self');
};
const initGlobalListeners = () => {
const semesterSelect = document.getElementById('semester-select');
semesterSelect.onchange = () => setSemester(semesterSelect);
};
if (!HTMLCanvasElement.prototype.toBlob) { if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function(callback, type, quality) { value: function(callback, type, quality) {
......
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 CalendarPage from 'src/CalendarPage';
document.addEventListener