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
02b56664
Commit
02b56664
authored
Nov 06, 2019
by
Zac Wood
Browse files
Add stimululs.js, turbolinks EVERYTHING
parent
db5fd951
Pipeline
#5117
passed with stage
in 2 minutes and 25 seconds
Changes
28
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
.gitignore
View file @
02b56664
.vscode
schedules.code-workspace
schedules/config/test.html
schedules/db/data/last_update.txt
schedules/Gemfile
View file @
02b56664
...
...
@@ -20,9 +20,6 @@ gem 'uglifier'
gem
'webpacker'
,
'~> 3.5'
# Access Ruby data from JavaScript
gem
'gon'
group
:development
,
:test
do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem
'byebug'
,
platforms:
[
:mri
,
:mingw
,
:x64_mingw
]
...
...
@@ -66,3 +63,6 @@ gem 'apipie-rails'
# Markdown for API docs
gem
'maruku'
# super fast page loads
gem
'turbolinks'
\ No newline at end of file
schedules/Gemfile.lock
View file @
02b56664
...
...
@@ -108,7 +108,6 @@ GEM
parser (2.6.4.1)
ast (~> 2.4.0)
powerpack (0.1.2)
prettier (0.15.0)
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
...
...
@@ -206,6 +205,9 @@ GEM
thor (0.20.3)
thread_safe (0.3.6)
tilt (2.0.10)
turbolinks (5.2.1)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
tzinfo (1.2.5)
thread_safe (~> 0.1)
uglifier (4.2.0)
...
...
@@ -241,7 +243,6 @@ DEPENDENCIES
listen (>= 3.0.5, < 3.2)
maruku
nokogiri
prettier
pry
pry-doc
puma (~> 3.7)
...
...
@@ -254,6 +255,7 @@ DEPENDENCIES
spring
spring-watcher-listen (~> 2.0.0)
sqlite3 (= 1.3.13)
turbolinks
tzinfo-data
uglifier
web-console (>= 3.3.0)
...
...
schedules/app/assets/stylesheets/application.scss
View file @
02b56664
...
...
@@ -32,6 +32,10 @@ h6 {
font-family
:
'Open Sans'
,
sans-serif
;
}
.hidden
{
display
:
none
;
}
.hero
{
margin-top
:
30%
;
h1
{
...
...
schedules/app/controllers/application_controller.rb
View file @
02b56664
# Configures the application.
class
ApplicationController
<
ActionController
::
Base
include
BySemester
before_action
:set_render_page
def
set_render_page
@render_page
=
true
end
end
schedules/app/controllers/course_sections_controller.rb
View file @
02b56664
class
CourseSectionsController
<
ApplicationController
def
index
@render_page
=
false
crns
=
params
[
:crns
].
split
(
','
)
@sections
=
crns
.
map
{
|
crn
|
CourseSection
.
latest_by_crn
(
crn
)
}
@days
=
{
...
...
@@ -26,6 +25,8 @@ class CourseSectionsController < ApplicationController
Time
.
new
(
a
.
start_time
)
<=>
Time
.
new
(
b
.
start_time
)
end
end
render
(
layout:
false
)
end
def
show
...
...
schedules/app/controllers/schedules_controller.rb
deleted
100644 → 0
View file @
db5fd951
# Contains functionality for generating schedules.
class
SchedulesController
<
ApplicationController
include
SchedulesHelper
def
show
;
end
def
view
@all
=
params
[
:crns
]
.
split
(
','
)
.
map
{
|
crn
|
CourseSection
.
latest_by_crn
(
crn
)
}
.
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
|
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/sessions_controller.rb
deleted
100644 → 0
View file @
db5fd951
class
SessionsController
<
ApplicationController
def
cart
section_crn
=
params
[
:crn
]
if
@cart
.
include?
(
section_crn
.
to_s
)
@cart
.
reject!
{
|
crn
|
section_crn
.
to_s
==
crn
.
to_s
}
else
@cart
<<
section_crn
end
cookies
.
permanent
[
:cart
]
=
@cart
.
to_json
render
json:
@cart
.
to_json
end
def
add_bulk
crns
=
params
[
:crns
].
split
(
','
)
crns
.
each
do
|
crn
|
s
=
CourseSection
.
latest_by_crn
(
crn
)
next
if
s
.
nil?
@cart
<<
crn
.
to_s
unless
@cart
.
include?
(
crn
.
to_s
)
end
cookies
.
permanent
[
:cart
]
=
@cart
.
to_json
redirect_to
(
schedule_path
)
end
end
schedules/app/javascript/packs/application.js
View file @
02b56664
...
...
@@ -8,70 +8,21 @@
import
'
url-polyfill
'
// Turbolinks for super fast page loads
import
Turbolinks
from
'
turbolinks
'
Turbolinks
.
start
()
window
.
addEventListener
(
'
turbolinks:load
'
,
()
=>
{
setInitialLinks
()
addListeners
()
document
.
querySelector
(
'
#count
'
).
innerText
=
getCart
().
length
})
function
setInitialLinks
()
{
getCart
().
forEach
(
writeLink
)
}
function
addListeners
()
{
const
links
=
Array
.
from
(
document
.
querySelectorAll
(
'
.add-section
'
))
for
(
const
link
of
links
)
{
link
.
addEventListener
(
'
click
'
,
e
=>
{
e
.
preventDefault
()
const
crn
=
link
.
dataset
.
crn
toggleSection
(
crn
)
writeLink
(
crn
)
})
}
}
function
getCart
()
{
return
JSON
.
parse
(
localStorage
.
getItem
(
'
cart
'
)
||
'
[]
'
)
}
function
toggleSection
(
crn
)
{
if
(
getCart
().
includes
(
crn
))
{
removeSection
(
crn
)
}
else
{
addSection
(
crn
)
}
console
.
log
(
getCart
())
document
.
querySelector
(
'
#count
'
).
innerText
=
getCart
().
length
}
function
addSection
(
crn
)
{
const
newCart
=
[...
getCart
(),
crn
]
localStorage
.
setItem
(
'
cart
'
,
JSON
.
stringify
(
newCart
))
}
function
removeSection
(
crn
)
{
const
newCart
=
getCart
().
filter
(
c
=>
c
!==
crn
)
localStorage
.
setItem
(
'
cart
'
,
JSON
.
stringify
(
newCart
))
}
// Load stimulus for application
// Schedules uses stimulus for JavaScript components, which is a much
// more lightweight solution than React, for example
//
// Read more here: https://stimulusjs.org/handbook/origin
// It's really cool.
function
writeLink
(
crn
)
{
const
item
=
document
.
querySelector
(
`[data-crn="
${
crn
}
"]`
)
if
(
!
item
)
return
const
icon
=
item
.
querySelector
(
'
.add-remove-link a i
'
)
const
link
=
item
.
querySelector
(
'
.add-remove-link a span
'
)
if
(
getCart
().
includes
(
crn
))
{
link
.
innerText
=
'
Remove from cart
'
icon
.
className
=
'
fas fa-minus
'
}
else
{
link
.
innerText
=
'
Add Section to Cart
'
icon
.
className
=
'
fas fa-plus
'
}
}
import
{
Application
}
from
'
stimulus
'
import
{
definitionsFromContext
}
from
'
stimulus/webpack-helpers
'
const
elementFromString
=
string
=>
{
const
html
=
new
DOMParser
().
parseFromString
(
string
,
'
text/html
'
)
return
html
.
body
.
firstChild
}
const
application
=
Application
.
start
()
const
context
=
require
.
context
(
'
src/controllers
'
,
true
,
/
\.
js$/
)
application
.
load
(
definitionsFromContext
(
context
))
schedules/app/javascript/packs/schedules.js
deleted
100644 → 0
View file @
db5fd951
import
{
saveAs
}
from
'
file-saver
'
const
initPage
=
()
=>
{
if
(
getCart
().
length
!=
0
)
{
document
.
getElementById
(
'
root
'
).
innerHTML
=
'
<i class="fas fa-spinner fa-spin"></i>
'
fetch
(
`/course_sections?crns=
${
getCart
().
join
(
'
,
'
)}
`
)
.
then
(
resp
=>
resp
.
text
())
.
then
(
text
=>
{
const
tree
=
elementFromString
(
text
)
const
body
=
tree
.
querySelector
(
'
.page
'
)
document
.
getElementById
(
'
root
'
).
innerHTML
=
body
.
innerHTML
setInitialLinks
()
addListeners
()
})
}
else
{
document
.
getElementById
(
'
root
'
).
innerHTML
=
'
Add classes to your cart to see them here!
'
}
document
.
querySelector
(
'
#count
'
).
innerText
=
getCart
().
length
document
.
getElementById
(
'
add-to-system
'
).
addEventListener
(
'
click
'
,
()
=>
{
window
.
open
(
`webcal://
${
window
.
location
.
hostname
}${
window
.
location
.
port
===
'
3000
'
?
'
:3000
'
:
''
}
/api/schedules?crns=
${
getCart
().
join
(
'
,
'
)}
`
)
})
document
.
getElementById
(
'
download-ics
'
).
addEventListener
(
'
click
'
,
()
=>
{
fetch
(
`
${
window
.
location
.
protocol
}
//
${
window
.
location
.
hostname
}${
window
.
location
.
port
===
'
3000
'
?
'
:3000
'
:
''
}
/api/schedules?crns=
${
getCart
().
join
(
'
,
'
)}
`
)
.
then
(
resp
=>
resp
.
text
())
.
then
(
text
=>
{
const
blob
=
new
Blob
([
text
],
{
type
:
'
text/calendar;charset=utf-8
'
})
saveAs
(
blob
,
'
GMU Schedule.ics
'
)
})
})
}
window
.
addEventListener
(
'
DOMContentLoaded
'
,
initPage
)
function
setInitialLinks
()
{
getCart
().
forEach
(
writeLink
)
}
function
addListeners
()
{
const
links
=
Array
.
from
(
document
.
querySelectorAll
(
'
.add-section
'
))
for
(
const
link
of
links
)
{
link
.
addEventListener
(
'
click
'
,
e
=>
{
e
.
preventDefault
()
const
crn
=
link
.
dataset
.
crn
toggleSection
(
crn
)
writeLink
(
crn
)
})
}
}
function
getCart
()
{
return
JSON
.
parse
(
localStorage
.
getItem
(
'
cart
'
)
||
'
[]
'
)
}
function
toggleSection
(
crn
)
{
if
(
getCart
().
includes
(
crn
))
{
removeSection
(
crn
)
}
else
{
addSection
(
crn
)
}
console
.
log
(
getCart
())
document
.
querySelector
(
'
#count
'
).
innerText
=
getCart
().
length
}
function
addSection
(
crn
)
{
const
newCart
=
[...
getCart
(),
crn
]
localStorage
.
setItem
(
'
cart
'
,
JSON
.
stringify
(
newCart
))
}
function
removeSection
(
crn
)
{
const
newCart
=
getCart
().
filter
(
c
=>
c
!==
crn
)
localStorage
.
setItem
(
'
cart
'
,
JSON
.
stringify
(
newCart
))
}
function
writeLink
(
crn
)
{
const
item
=
document
.
querySelectorAll
(
`[data-crn="
${
crn
}
"]`
)
if
(
item
.
length
==
0
)
return
item
.
forEach
(
item
=>
{
const
icon
=
item
.
querySelector
(
'
.add-remove-link a i
'
)
const
link
=
item
.
querySelector
(
'
.add-remove-link a span
'
)
if
(
getCart
().
includes
(
crn
))
{
link
.
innerText
=
'
Remove from cart
'
icon
.
className
=
'
fas fa-minus
'
}
else
{
link
.
innerText
=
'
Add Section to Cart
'
icon
.
className
=
'
fas fa-plus
'
}
})
}
const
elementFromString
=
string
=>
{
const
html
=
new
DOMParser
().
parseFromString
(
string
,
'
text/html
'
)
return
html
.
body
}
schedules/app/javascript/src/cart.js
0 → 100644
View file @
02b56664
const
subscribers
=
[]
export
function
subscribe
(
callback
)
{
subscribers
.
push
(
callback
)
}
export
function
getCart
()
{
return
JSON
.
parse
(
localStorage
.
getItem
(
'
cart
'
)
||
'
[]
'
).
filter
(
val
=>
!!
val
)
}
export
function
hasSection
(
crn
)
{
return
getCart
().
includes
(
crn
)
}
export
function
toggleSection
(
crn
)
{
if
(
getCart
().
includes
(
crn
))
{
removeSection
(
crn
)
}
else
{
addSection
(
crn
)
}
for
(
const
callback
of
subscribers
)
{
callback
()
}
}
function
addSection
(
crn
)
{
const
newCart
=
[...
getCart
(),
crn
]
localStorage
.
setItem
(
'
cart
'
,
JSON
.
stringify
(
newCart
))
}
function
removeSection
(
crn
)
{
const
newCart
=
getCart
().
filter
(
c
=>
c
!==
crn
)
localStorage
.
setItem
(
'
cart
'
,
JSON
.
stringify
(
newCart
))
}
schedules/app/javascript/src/controllers/cart_controller.js
0 → 100644
View file @
02b56664
import
{
Controller
}
from
'
stimulus
'
import
{
getCart
,
subscribe
}
from
'
src/cart
'
export
default
class
extends
Controller
{
static
targets
=
[
'
counter
'
]
connect
()
{
subscribe
(()
=>
this
.
draw
())
this
.
draw
()
}
draw
()
{
this
.
counterTarget
.
innerText
=
getCart
().
length
}
}
schedules/app/javascript/src/controllers/schedule_controller.js
0 → 100644
View file @
02b56664
import
{
Controller
}
from
'
stimulus
'
import
{
getCart
}
from
'
src/cart
'
import
{
buildUrl
,
downloadIcal
}
from
'
../utils
'
export
default
class
extends
Controller
{
static
targets
=
[
'
schedule
'
,
'
loader
'
,
'
export
'
]
connect
()
{
if
(
getCart
().
length
==
0
)
{
this
.
exportTarget
.
classList
.
add
(
'
hidden
'
)
this
.
loaderTarget
.
classList
.
add
(
'
hidden
'
)
this
.
scheduleTarget
.
innerHTML
=
'
Add courses to your cart to see them here!
'
}
else
{
this
.
exportTarget
.
classList
.
remove
(
'
hidden
'
)
this
.
loaderTarget
.
classList
.
remove
(
'
hidden
'
)
this
.
scheduleTarget
.
innerHTML
=
''
fetch
(
`/course_sections?crns=
${
getCart
().
join
(
'
,
'
)}
`
)
.
then
(
resp
=>
resp
.
text
())
.
then
(
text
=>
{
this
.
scheduleTarget
.
innerHTML
=
text
this
.
loaderTarget
.
classList
.
add
(
'
hidden
'
)
})
}
}
downloadIcs
()
{
downloadIcal
(
buildUrl
(
`/api/schedules?crns=
${
getCart
().
join
(
'
,
'
)}
`
),
'
GMU Schedule.ics
'
)
}
openWebcal
()
{
window
.
open
(
buildUrl
(
`/api/schedules?crns=
${
getCart
().
join
(
'
,
'
)}
`
,
'
webcal:
'
))
}
}
schedules/app/javascript/src/controllers/search_controller.js
0 → 100644
View file @
02b56664
import
{
Controller
}
from
'
stimulus
'
import
Turbolinks
from
'
turbolinks
'
import
{
buildUrl
}
from
'
../utils
'
export
default
class
extends
Controller
{
static
targets
=
[
'
input
'
]
search
(
event
)
{
event
.
preventDefault
()
Turbolinks
.
visit
(
buildUrl
(
`/search?query=
${
this
.
inputTarget
.
value
}
`
))
}
}
schedules/app/javascript/src/controllers/semester_select_controller.js
0 → 100644
View file @
02b56664
import
{
Controller
}
from
'
stimulus
'
import
{
buildUrl
}
from
'
../utils
'
import
Turbolinks
from
'
turbolinks
'
export
default
class
extends
Controller
{
changeSemester
(
event
)
{
event
.
preventDefault
()
const
id
=
event
.
target
.
value
Turbolinks
.
visit
(
buildUrl
(
window
.
location
.
pathname
+
'
?
'
+
event
.
target
.
name
+
'
=
'
+
id
))
}
}
schedules/app/javascript/src/controllers/toggle_section_controller.js
0 → 100644
View file @
02b56664
import
{
Controller
}
from
'
stimulus
'
import
{
subscribe
,
toggleSection
,
hasSection
}
from
'
src/cart
'
export
default
class
extends
Controller
{
static
targets
=
[
'
icon
'
,
'
text
'
]
connect
()
{
subscribe
(()
=>
this
.
draw
())
this
.
draw
()
}
toggle
()
{
toggleSection
(
this
.
crn
)
}
draw
()
{
if
(
hasSection
(
this
.
crn
))
{
this
.
iconTarget
.
className
=
'
fas fa-minus add-remove-icon
'
this
.
textTarget
.
innerText
=
'
Remove from Cart
'
}
else
{
this
.
iconTarget
.
className
=
'
fas fa-plus add-remove-icon
'
this
.
textTarget
.
innerText
=
'
Add Section to Cart
'
}
}
get
crn
()
{
return
this
.
data
.
get
(
'
crn
'
)
}
}
schedules/app/javascript/src/utils.js
0 → 100644
View file @
02b56664
export
function
buildUrl
(
url
,
protocol
=
window
.
location
.
protocol
)
{
const
port
=
window
.
location
.
port
===
'
3000
'
?
'
:3000
'
:
''
return
`
${
protocol
}
//
${
window
.
location
.
hostname
}${
port
}${
url
}
`
}
export
function
downloadIcal
(
url
,
filename
)
{
fetch
(
url
)
.
then
(
resp
=>
resp
.
text
())
.
then
(
text
=>
{
const
blob
=
new
Blob
([
text
],
{
type
:
'
text/calendar;charset=utf-8
'
})
saveAs
(
blob
,
filename
)
})
}
schedules/app/views/courses/show.html.erb
View file @
02b56664
...
...
@@ -24,8 +24,8 @@
</div>
<div
class=
"col-12 col-lg"
>
<form
class=
"semester-select"
>
<select
name=
"semester_id"
class=
"form-control"
i
d=
"semesterselect
"
on
change
=
"this.form.submit()
"
aria-label=
"Semester"
>
<form
class=
"semester-select"
data-controller=
"semester-select"
>
<select
name=
"semester_id"
class=
"form-control"
d
ata-action
=
"semester
-
select
#
change
Semester
"
aria-label=
"Semester"
>
<%
@semesters
.
each
do
|
sem
|
%>
<option
id=
"
<%=
sem
.
id
%>
"
...
...
schedules/app/views/home/index.html.erb
View file @
02b56664
<div
class=
"text-center hero"
>
<h1><i
class=
"fas fa-calendar-alt"
></i>
<span
style=
""
>
SRCT
</span>
<strong
style=
""
>
Schedules
</strong></h1>
<form
id=
"search-container"
action=
"/search"
>
<form
data-controller=
"search"
data-action=
"search#search"
id=
"search-container"
action=
"/search"
>
<input
data-target=
"search.input"
name=
"query"
value=
"
<%=
params
[
:query
]
%>
"
placeholder=
"PSYC, CS 112, 71926, Jonathan Bell, ..."
/>
<button
type=
"submit"
>
<i
class=
"fas fa-search"
></i>
...
...
schedules/app/views/instructors/show.html.erb
View file @
02b56664
...
...
@@ -11,8 +11,8 @@
</div>
<div
class=
"col-lg-8 col-12"
>
<form
class=
"semester-select"
>
<select
name=
"semester_id"
class=
"form-control"
i
d=
"semesterselect
"
on
change
=
"this.form.submit()
"
aria-label=
"Semester"
>
<form
class=
"semester-select"
data-controller=
"semester-select"
>
<select
name=
"semester_id"
class=
"form-control"
d
ata-action
=
"semester
-
select
#
change
Semester
"
aria-label=
"Semester"
>
<%
@semesters
.
each
do
|
sem
|
%>
<option
id=
"
<%=
sem
.
id
%>
"
...
...
Prev
1
2
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