Merge pull request #76 from CactusPuppy/176894996-vertical-schedule
Vertical schedule
This commit is contained in:
commit
f60b07eb32
19 changed files with 245 additions and 3 deletions
6
.github/workflows/spec.yml
vendored
6
.github/workflows/spec.yml
vendored
|
|
@ -38,6 +38,12 @@ jobs:
|
||||||
with:
|
with:
|
||||||
ruby-version: 2.6.6
|
ruby-version: 2.6.6
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
- name: Use Node.js
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: '12.x'
|
||||||
|
- name: Install JavaScript libraries via npm
|
||||||
|
run: npm install
|
||||||
- name: Prepare spec
|
- name: Prepare spec
|
||||||
run: |
|
run: |
|
||||||
rm -f osem_test osem_development
|
rm -f osem_test osem_development
|
||||||
|
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -121,3 +121,6 @@ build-iPhoneSimulator/
|
||||||
|
|
||||||
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
|
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
|
||||||
.rubocop-https?--*
|
.rubocop-https?--*
|
||||||
|
|
||||||
|
# Ignore Javascript package binaries
|
||||||
|
/node_modules
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ jobs:
|
||||||
- gem install bundler -v $(tail -n 1 Gemfile.lock)
|
- gem install bundler -v $(tail -n 1 Gemfile.lock)
|
||||||
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > $CCTR
|
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > $CCTR
|
||||||
- chmod +x $CCTR
|
- chmod +x $CCTR
|
||||||
|
- npm install
|
||||||
before_script:
|
before_script:
|
||||||
- RAILS_ENV=test bundle exec rake db:bootstrap --trace
|
- RAILS_ENV=test bundle exec rake db:bootstrap --trace
|
||||||
- RAILS_ENV=test bundle exec bin/rails webdrivers:chromedriver:update
|
- RAILS_ENV=test bundle exec bin/rails webdrivers:chromedriver:update
|
||||||
|
|
|
||||||
2
Gemfile
2
Gemfile
|
|
@ -27,7 +27,7 @@ gem 'paper_trail'
|
||||||
# for upload management
|
# for upload management
|
||||||
gem 'carrierwave'
|
gem 'carrierwave'
|
||||||
gem 'carrierwave-bombshelter'
|
gem 'carrierwave-bombshelter'
|
||||||
gem 'mimemagic', '~> 0.3.6'
|
gem 'mimemagic'
|
||||||
gem 'mini_magick'
|
gem 'mini_magick'
|
||||||
|
|
||||||
# for internationalizing
|
# for internationalizing
|
||||||
|
|
|
||||||
|
|
@ -723,7 +723,7 @@ DEPENDENCIES
|
||||||
leaflet-rails
|
leaflet-rails
|
||||||
letter_opener
|
letter_opener
|
||||||
letter_opener_web (~> 1.0)
|
letter_opener_web (~> 1.0)
|
||||||
mimemagic (~> 0.3.6)
|
mimemagic
|
||||||
mina
|
mina
|
||||||
mini_magick
|
mini_magick
|
||||||
money-rails
|
money-rails
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,8 @@
|
||||||
//= require bootstrap-select
|
//= require bootstrap-select
|
||||||
//= require osem-survey
|
//= require osem-survey
|
||||||
//= require pagy
|
//= require pagy
|
||||||
|
//= require fullcalendar-scheduler/main.js
|
||||||
|
//= require fullcalendar
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('a[disabled=disabled]').click(function(event){
|
$('a[disabled=disabled]').click(function(event){
|
||||||
|
|
|
||||||
30
app/assets/javascripts/fullcalendar.js.erb
Normal file
30
app/assets/javascripts/fullcalendar.js.erb
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
$( document ).ready(function() {
|
||||||
|
let calendarEl = document.getElementById('vert-schedule-full-calendar');
|
||||||
|
if (!calendarEl) return; //check that we need a vertical schedule
|
||||||
|
let fullcalData = $('#fullcalendar');
|
||||||
|
|
||||||
|
let license_key = "<%= Rails.configuration.fullcalendar[:license_key] %>";
|
||||||
|
|
||||||
|
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||||
|
schedulerLicenseKey: license_key,
|
||||||
|
nowIndicator: true,
|
||||||
|
now: fullcalData.data('now'),
|
||||||
|
expandRows: true,
|
||||||
|
allDaySlot: false,
|
||||||
|
slotMinTime: fullcalData.data('startHour') + ':00:00',
|
||||||
|
slotMaxTime: fullcalData.data('endHour') + ':00:00',
|
||||||
|
validRange: {
|
||||||
|
start: fullcalData.data('startDate'),
|
||||||
|
end: fullcalData.data('endDate')
|
||||||
|
},
|
||||||
|
timeZone: fullcalData.data('timezone'),
|
||||||
|
initialDate: fullcalData.data('day'),
|
||||||
|
initialView: 'resourceTimeGridDay',
|
||||||
|
resources: fullcalData.data('rooms'),
|
||||||
|
events: fullcalData.data('events')
|
||||||
|
});
|
||||||
|
|
||||||
|
calendar.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,4 +26,6 @@
|
||||||
*= require osem-navbar
|
*= require osem-navbar
|
||||||
*= require mastodon
|
*= require mastodon
|
||||||
*= require conferences
|
*= require conferences
|
||||||
|
|
||||||
|
*= require fullcalendar-scheduler/main.css
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,25 @@ class SchedulesController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def vertical_schedule
|
||||||
|
dates = @conference.start_date..@conference.end_date
|
||||||
|
# the schedule takes you to today if it is a date of the schedule
|
||||||
|
current_day = @conference.current_conference_day
|
||||||
|
@day = current_day.present? ? current_day : dates.first
|
||||||
|
event_schedules = @program.selected_event_schedules(
|
||||||
|
includes: [{ event: %i[event_type speakers submitter] }]
|
||||||
|
)
|
||||||
|
|
||||||
|
unless event_schedules
|
||||||
|
redirect_to events_conference_schedule_path(@conference.short_title)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
@rooms = FullCalendarFormatter.rooms_to_resources(@conference.venue.rooms) if @conference.venue
|
||||||
|
@event_schedules = FullCalendarFormatter.event_schedules_to_resources(event_schedules)
|
||||||
|
@now = Time.now.in_time_zone(@conference.timezone).strftime('%FT%T%:z')
|
||||||
|
end
|
||||||
|
|
||||||
def app
|
def app
|
||||||
@qr_code = RQRCode::QRCode.new(conference_schedule_url).as_svg(offset: 20, color: '000', shape_rendering: 'crispEdges', module_size: 11)
|
@qr_code = RQRCode::QRCode.new(conference_schedule_url).as_svg(offset: 20, color: '000', shape_rendering: 'crispEdges', module_size: 11)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class Ability
|
||||||
event.state == 'confirmed'
|
event.state == 'confirmed'
|
||||||
end
|
end
|
||||||
|
|
||||||
can [:show, :events, :happening_now, :app], Schedule do |schedule|
|
can [:show, :events, :happening_now, :app, :vertical_schedule], Schedule do |schedule|
|
||||||
schedule.program.schedule_public
|
schedule.program.schedule_public
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,14 @@ class EventSchedule < ApplicationRecord
|
||||||
other.schedule_id == schedule_id
|
other.schedule_id == schedule_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def start_time_in_conference_timezone
|
||||||
|
time_in_conference_timezone(start_time)
|
||||||
|
end
|
||||||
|
|
||||||
|
def end_time_in_conference_timezone
|
||||||
|
time_in_conference_timezone(end_time)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def replaced_event_schedules
|
def replaced_event_schedules
|
||||||
|
|
@ -187,4 +195,10 @@ class EventSchedule < ApplicationRecord
|
||||||
|
|
||||||
errors.add(:schedule, "must be one of #{event.track.name} track's schedules") unless event.track.schedules.include?(schedule)
|
errors.add(:schedule, "must be one of #{event.track.name} track's schedules") unless event.track.schedules.include?(schedule)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def time_in_conference_timezone(time)
|
||||||
|
time_in_tz = time.in_time_zone(timezone)
|
||||||
|
time_in_tz -= time_in_tz.utc_offset
|
||||||
|
time_in_tz
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
42
app/services/full_calendar_formatter.rb
Normal file
42
app/services/full_calendar_formatter.rb
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
class FullCalendarFormatter
|
||||||
|
def self.rooms_to_resources(rooms)
|
||||||
|
rooms.map { |room| room_to_resource(room) }.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.event_schedules_to_resources(event_schedules)
|
||||||
|
return '[]' if event_schedules.empty?
|
||||||
|
|
||||||
|
conference = event_schedules.first.schedule.program.conference
|
||||||
|
event_schedules.map { |event_schedule| event_schedule_to_resource(conference, event_schedule) }.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
include FormatHelper
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def room_to_resource(room)
|
||||||
|
{
|
||||||
|
id: room.guid,
|
||||||
|
title: room.name
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def event_schedule_to_resource(conference, event_schedule)
|
||||||
|
event_type_color = event_schedule.event.event_type.color
|
||||||
|
url = Rails.application.routes.url_helpers.conference_program_proposal_path(conference.short_title, event_schedule.event.id)
|
||||||
|
|
||||||
|
{
|
||||||
|
id: event_schedule.event.guid,
|
||||||
|
title: event_schedule.event.title,
|
||||||
|
start: event_schedule.start_time_in_conference_timezone,
|
||||||
|
end: event_schedule.end_time_in_conference_timezone,
|
||||||
|
resourceId: event_schedule.room.guid,
|
||||||
|
url: url,
|
||||||
|
borderColor: event_type_color,
|
||||||
|
backgroundColor: event_type_color,
|
||||||
|
textColor: contrast_color(event_type_color)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
12
app/views/schedules/vertical_schedule.haml
Normal file
12
app/views/schedules/vertical_schedule.haml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
.container#program
|
||||||
|
= render 'schedule_tabs', active: 'vertical_schedule'
|
||||||
|
|
||||||
|
%h1.text-center
|
||||||
|
Vertical Schedule for
|
||||||
|
= @conference.title
|
||||||
|
|
||||||
|
#vert-schedule-full-calendar
|
||||||
|
#fullcalendar{ data: { rooms: @rooms, events: @event_schedules, day: @day,
|
||||||
|
'start-hour': @conference.start_hour, 'end-hour': @conference.end_hour,
|
||||||
|
'start-date': @conference.start_date, 'end-date': @conference.end_date + 1.day,
|
||||||
|
'timezone': @conference.timezone, 'now': @now } }
|
||||||
|
|
@ -80,5 +80,11 @@ module Osem
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
config.assets.paths << Rails.root.join('node_modules')
|
||||||
|
|
||||||
|
config.fullcalendar = {
|
||||||
|
license_key: ENV['FULL_CALENDAR_LICENSE_KEY']
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,7 @@ Osem::Application.routes.draw do
|
||||||
member do
|
member do
|
||||||
get :events
|
get :events
|
||||||
get :happening_now
|
get :happening_now
|
||||||
|
get :vertical_schedule
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
11
package-lock.json
generated
Normal file
11
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"requires": true,
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"fullcalendar-scheduler": {
|
||||||
|
"version": "5.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fullcalendar-scheduler/-/fullcalendar-scheduler-5.6.0.tgz",
|
||||||
|
"integrity": "sha512-0jx/Q+6QqzvIGWU/52OE27UVjVAiM9E0o/qRwRcEhyuKlL54ypTF/yXP/wo0ctUtui5dDsilGNsSsba1xmARJA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
package.json
Normal file
5
package.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"fullcalendar-scheduler": "^5.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -72,4 +72,20 @@ describe SchedulesController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'GET #vertical_schedule' do
|
||||||
|
let!(:program) { conference.program }
|
||||||
|
|
||||||
|
context 'as a conference participant' do
|
||||||
|
context 'who visits the schedule page' do
|
||||||
|
before(:each) do
|
||||||
|
get :vertical_schedule, params: { conference_id: conference.short_title }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a successful response' do
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
72
spec/services/full_calendar_formatter_spec.rb
Normal file
72
spec/services/full_calendar_formatter_spec.rb
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe FullCalendarFormatter do
|
||||||
|
let!(:room1) { create(:room) }
|
||||||
|
let!(:room2) { create(:room) }
|
||||||
|
let!(:rooms) { [room1, room2] }
|
||||||
|
let!(:conference) { create(:full_conference) }
|
||||||
|
let!(:program) { conference.program }
|
||||||
|
let!(:selected_schedule) { create(:schedule, program: program) }
|
||||||
|
let!(:event_type1) { create(:event_type, color: '#ffffff') }
|
||||||
|
let!(:event1) do
|
||||||
|
program.update_attributes!(selected_schedule: selected_schedule)
|
||||||
|
create(:event, program: program, event_type: event_type1)
|
||||||
|
end
|
||||||
|
let!(:event_schedule1) { create(:event_schedule, event: event1, schedule: selected_schedule, room: room1) }
|
||||||
|
let!(:event_type2) { create(:event_type, color: '#000000') }
|
||||||
|
let!(:event2) do
|
||||||
|
program.update_attributes!(selected_schedule: selected_schedule)
|
||||||
|
create(:event, program: program, room: room2, event_type: event_type2)
|
||||||
|
end
|
||||||
|
let!(:event_schedule2) { create(:event_schedule, event: event2, schedule: selected_schedule, room: room2) }
|
||||||
|
let!(:event_schedules) { [event_schedule1, event_schedule2] }
|
||||||
|
|
||||||
|
describe 'JSON formatting for FullCalendar' do
|
||||||
|
it 'translates rooms to resources' do
|
||||||
|
resources = described_class.rooms_to_resources(rooms)
|
||||||
|
expected_json = [
|
||||||
|
{
|
||||||
|
id: room1.guid,
|
||||||
|
title: room1.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: room2.guid,
|
||||||
|
title: room2.name
|
||||||
|
}
|
||||||
|
].to_json
|
||||||
|
expect(resources).to eq(expected_json)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'translates event schedules to resources' do
|
||||||
|
resources = described_class.event_schedules_to_resources(event_schedules)
|
||||||
|
expected_json = [
|
||||||
|
{
|
||||||
|
id: event_schedule1.event.guid,
|
||||||
|
title: event_schedule1.event.title,
|
||||||
|
start: event_schedule1.start_time_in_conference_timezone,
|
||||||
|
end: event_schedule1.end_time_in_conference_timezone,
|
||||||
|
resourceId: room1.guid,
|
||||||
|
url: Rails.application.routes.url_helpers.conference_program_proposal_path(conference.short_title, event1.id),
|
||||||
|
borderColor: event1.event_type.color,
|
||||||
|
backgroundColor: event1.event_type.color,
|
||||||
|
textColor: 'black'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: event_schedule2.event.guid,
|
||||||
|
title: event_schedule2.event.title,
|
||||||
|
start: event_schedule2.start_time_in_conference_timezone,
|
||||||
|
end: event_schedule2.end_time_in_conference_timezone,
|
||||||
|
resourceId: room2.guid,
|
||||||
|
url: Rails.application.routes.url_helpers.conference_program_proposal_path(conference.short_title, event2.id),
|
||||||
|
borderColor: event2.event_type.color,
|
||||||
|
backgroundColor: event2.event_type.color,
|
||||||
|
textColor: 'white'
|
||||||
|
}
|
||||||
|
].to_json
|
||||||
|
|
||||||
|
expect(resources).to eq(expected_json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue