Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
SRCT
schedules
Commits
d3970aa1
Commit
d3970aa1
authored
Apr 17, 2019
by
Zac Wood
Browse files
Rewrote search + schedule pages with React
parent
080037ca
Pipeline
#4235
failed with stage
in 3 minutes and 32 seconds
Changes
49
Pipelines
1
Show whitespace changes
Inline
Side-by-side
schedules/.babelrc
View file @
d3970aa1
{
"presets": [
["env", {
[
"env",
{
"modules": false,
"targets": {
"browsers": "> 1%",
"uglify": true
},
"useBuiltIns": true
}]
}
],
"react"
],
"plugins": [
"syntax-dynamic-import",
"transform-object-rest-spread",
["transform-class-properties", { "spec": true }]
[
"transform-class-properties",
{
"spec": true
}
]
]
}
schedules/Dockerfile
View file @
d3970aa1
...
...
@@ -14,3 +14,4 @@ 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
schedules/Gemfile
View file @
d3970aa1
...
...
@@ -21,6 +21,9 @@ 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
...
...
@@ -74,7 +77,3 @@ gem 'apipie-rails'
# Markdown for API docs
gem
'maruku'
# gem 'jquery-rails'
# gem 'font-awesome-sass', '~> 5.3.1'
schedules/Gemfile.lock
View file @
d3970aa1
...
...
@@ -64,6 +64,10 @@ 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)
...
...
@@ -144,6 +148,8 @@ 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)
...
...
@@ -216,6 +222,7 @@ DEPENDENCIES
apipie-rails
byebug
capybara (~> 2.13)
gon
httparty
icalendar
jbuilder (~> 2.5)
...
...
schedules/app/assets/javascripts/about.js
0 → 100644
View file @
d3970aa1
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
schedules/app/assets/stylesheets/about.scss
0 → 100644
View file @
d3970aa1
// 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/
schedules/app/controllers/about_controller.rb
0 → 100644
View file @
d3970aa1
class
AboutController
<
ApplicationController
def
index
;
end
end
schedules/app/controllers/api/course_sections_controller.rb
View file @
d3970aa1
...
...
@@ -28,7 +28,6 @@ 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
,
...
...
@@ -38,6 +37,9 @@ 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
,
...
...
schedules/app/controllers/application_controller.rb
View file @
d3970aa1
# Configures the application.
class
ApplicationController
<
ActionController
::
Base
include
BySemester
# On each request, set the semester and cart.
before_action
:set_cart
# 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
schedules/app/controllers/concerns/by_semester.rb
0 → 100644
View file @
d3970aa1
# BySemester contains logic for setting the current request's
# Semester. This is not needed by every page as it used to be, so it
# now lives in this concern instead of ApplicationController.
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 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
end
schedules/app/controllers/schedules_controller.rb
View file @
d3970aa1
...
...
@@ -2,29 +2,32 @@
class
SchedulesController
<
ApplicationController
include
SchedulesHelper
def
show
valid_crns
=
@cart
.
reject
{
|
crn
|
s
=
CourseSection
.
find_by_crn
(
crn
)
s
.
nil?
}
def
show
;
end
@all
=
valid_crns
.
map
{
|
crn
|
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
)
end
def
view
@
all
=
params
[
:crns
].
split
(
','
)
.
map
{
|
crn
|
CourseSection
.
latest_by_crn
(
crn
)
}
@all
.
reject!
(
&
:nil?
)
@without_online
=
@
all
.
reject
{
|
s
|
def
events
@
cart
=
params
[
:crns
].
split
(
','
)
.
map
{
|
crn
|
CourseSection
.
latest_by_crn
(
crn
)
}
.
reject
(
&
:nil?
)
@without_online
=
@
cart
.
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
schedules/app/controllers/search_controller.rb
View file @
d3970aa1
...
...
@@ -13,7 +13,7 @@ class SearchController < ApplicationController
/[[: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?
redirect_to
(
course_url
(
course
)
)
unless
course
.
nil?
end
/[[:alpha:]]{2,4}/i
.
match
(
params
[
:query
])
do
|
m
|
...
...
@@ -37,19 +37,21 @@ class SearchController < ApplicationController
@instructors
=
Instructor
.
named
(
params
[
:query
])
end
@courses
.
map!
do
|
c
|
c
.
serializable_hash
.
merge
(
url:
course_url
(
c
))
end
gon
.
courses
=
@courses
gon
.
courses
.
map!
end
/[0-9]{5}/
.
match
(
params
[
:query
])
do
|
m
|
redirect_to
(
course_url
CourseSection
.
latest_by_crn
(
m
[
0
]).
course
)
redirect_to
(
course_url
(
CourseSection
.
latest_by_crn
(
m
[
0
]).
course
)
)
end
# results = SearchHelper::GenericItem.fetchall(String.new(params[:query]), semester: @semester).group_by(&:type)
# @instructors = results[:instructor]&.map(&:data)
# @courses = results[:course]&.map(&:data)
if
@courses
&
.
count
==
1
&&
@instructors
&
.
count
==
0
redirect_to
course_url
(
@courses
.
first
)
elsif
@courses
&
.
count
==
0
&&
@instructors
&
.
count
==
1
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
end
...
...
schedules/app/helpers/about_helper.rb
0 → 100644
View file @
d3970aa1
module
AboutHelper
end
schedules/app/helpers/schedules_helper.rb
View file @
d3970aa1
...
...
@@ -19,7 +19,9 @@ module SchedulesHelper
{
title:
s
.
name
,
start:
"
#{
formatted_date
}
T
#{
time
}
"
,
end:
"
#{
formatted_date
}
T
#{
endtime
}
"
end:
"
#{
formatted_date
}
T
#{
endtime
}
"
,
crn:
s
.
crn
,
active:
true
}
end
end
.
flatten
...
...
schedules/app/helpers/search_helper.rb
View file @
d3970aa1
module
SearchHelper
def
in_cart?
(
crn
)
@cart
.
include?
crn
.
to_s
end
class
GenericQueryData
attr_reader
:semester
attr_reader
:sort_mode
...
...
schedules/app/javascript/jsconfig.json
0 → 100644
View file @
d3970aa1
schedules/app/javascript/packs/application.js
View file @
d3970aa1
...
...
@@ -10,6 +10,9 @@
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
;
...
...
schedules/app/javascript/packs/home.js
0 → 100644
View file @
d3970aa1
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`
;
ReactDOM
.
render
(
<
QuickAdd
loadCalendar
=
{()
=>
{
window
.
location
.
href
=
calendarUrl
;
}}
/>
,
document
.
getElementById
(
'
quick-add
'
)
);
});
schedules/app/javascript/packs/instructor.js
0 → 100644
View file @
d3970aa1
// /**
// * 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
'
);
console
.
log
(
item
.
dataset
.
crn
);
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
);
schedules/app/javascript/packs/schedules.js
View file @
d3970aa1
import
Cart
from
'
src/cart
'
;
import
React
from
'
react
'
;
import
ReactDOM
from
'
react-dom
'
;
import
Cart
from
'
src/Cart
'
;
import
{
saveAs
}
from
'
file-saver
'
;
import
html2canvas
from
'
html2canvas
'
;
import
$
from
'
jquery
'
;
import
'
fullcalendar
'
;
import
'
moment
'
;
import
CalendarPage
from
'
src/CalendarPage
'
;
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
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
();
ReactDOM
.
render
(
<
CalendarPage
/>
,
document
.
getElementById
(
'
root
'
));
});
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
();
...
...
@@ -69,7 +39,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
)
{
...
...
Prev
1
2
3
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment