Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ gem 'country_select'
gem 'devise'
gem 'devise_invitable'
gem 'eventbrite_sdk'
gem 'ffi-icu'
gem 'font-awesome-sass', '~> 4.7.0' # Prefer V4 icon styles
gem 'friendly_id'
gem 'geocoder'
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ GEM
faraday-net_http (3.4.2)
net-http (~> 0.5)
ffi (1.15.5)
ffi-icu (0.5.3)
ffi (~> 1.0, >= 1.0.9)
font-awesome-sass (4.7.0)
sass (>= 3.2)
friendly_id (5.5.0)
Expand Down Expand Up @@ -857,6 +859,7 @@ DEPENDENCIES
devise
devise_invitable
eventbrite_sdk
ffi-icu
font-awesome-sass (~> 4.7.0)
friendly_id
geocoder
Expand Down
43 changes: 43 additions & 0 deletions app/assets/javascripts/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,46 @@ var Events = {
$('.address_content').show()
}
}
},

updateDateTimes: function (render_controls = false) {
// Assemble the data parameters for the query string
var event_ids = $.map($('.time-with-zone'), function(div) {
return $(div).data('event-id')
})
var timezone = $('input[type=radio][name=timezone-choice]:checked').val();
var data = { event_ids: event_ids, tz: timezone}
if (render_controls) {
data['browser_timezone'] = Intl.DateTimeFormat().resolvedOptions().timeZone;
}

// Server renders the various DOM ids that need updating
$.ajax({
url: "/events/event_time_data.json",
data: data,
Comment on lines +43 to +45
context: document.body
}).done(function(elements) {
// Replace content of server-rendered ids with new content
elements.forEach(function(element) {
$(element['id']).replaceWith(element['html'])
})

if (render_controls) {
// This is just run on initial page load, set up controls
// (controls aren't rendered on page initially served)
$('#timezone-controls .close-timezone-controls').click(function() {
$('#timezone-select').collapse('hide')
$('#timezone-controls .show-timezone-controls').show();
});
$('#timezone-controls .show-timezone-controls').click(function() {
$(this).hide();
});
$('input[type=radio][name=timezone-choice]').change(function() {
Events.updateDateTimes();
$('.timezone-display-value').toggleClass('unselected')
})
}
});
}
}

Expand All @@ -40,4 +80,7 @@ $(document).on('change', '.event_cost_basis', function () {
$(document).on('ready turbolinks:load', function () {
Events.switchCostFields();
Events.switchAddressFields();
if ($('.time-with-zone').length) {
Events.updateDateTimes(true);
}
});
10 changes: 10 additions & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1121,3 +1121,13 @@ td.day .calendar-text {
.tag-topic, .tag-operation {
text-decoration: none;
}

#timezone-controls {
overflow: auto;
// font-size: 0.7em;
label { font-weight: inherit; }
fieldset legend { font-size: inherit; }
#timezone-display .unselected { display: none; }
.center-block { width: 80%; }
.close-timezone-controls { float: right; }
}
15 changes: 15 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class ApplicationController < ActionController::Base
include PublicActivity::StoreController

before_action :configure_permitted_parameters, if: :devise_controller?
before_action :timezone_from_params_or_session

# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
Expand Down Expand Up @@ -159,4 +160,18 @@ def configure_permitted_parameters
def allow_embedding
response.headers.delete 'X-Frame-Options'
end

def timezone_from_params_or_session
# TODO? have a list of acceptable time zones, maybe via config?
tz = params[:tz] || session['tz']

session['tz'] = if (tz.blank? || tz == 'reset')
nil
elsif ActiveSupport::TimeZone[tz].present?
tz
end

true
end

end
51 changes: 50 additions & 1 deletion app/controllers/events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def clone
# GET /events/1/edit
def edit
authorize @event
shift_times_to_timezone_for_editing
end

# GET /events/1/report
Expand Down Expand Up @@ -155,6 +156,8 @@ def check_exists
def create
authorize Event
@event = Event.new(event_params)
# TODO: should time only shift for timezone if format is HTML?
shift_times_to_utc_for_saving
@event.user = current_user
@event.space = current_space

Expand All @@ -174,8 +177,11 @@ def create
# PATCH/PUT /events/1.json
def update
authorize @event
@event.assign_attributes(event_params)
# TODO: should time only shift for timezone if format is HTML?
shift_times_to_utc_for_saving
respond_to do |format|
if @event.update(event_params)
if @event.save
@event.create_activity(:update, owner: current_user) if @event.log_update_activity?
format.html { redirect_to @event, notice: 'Event was successfully updated.' }
format.json { render :show, status: :ok, location: @event }
Expand Down Expand Up @@ -226,13 +232,56 @@ def redirect
redirect_to @event.url, allow_other_host: true
end

def event_time_data
# Serve JSON of some DOM ids that need updating, with rendered HTML content
respond_to do |format|
event_ids = params[:event_ids]
timezone = session[:tz]
browser_timezone = params[:browser_timezone]

# Render times for the following event ids
out = (event_ids || []).map do |event_id|
event = Event.find(event_id.to_i)
html = render_to_string partial: 'events/event_time_div',
formats: [:html],
locals: { event: event, timezone: timezone }
{ id: "#event-time-#{event.id}",
html: html}
end
Comment on lines +242 to +250
if browser_timezone
# Render timezone controls
control_html = render_to_string partial: 'events/event_timezone_control',
formats: [:html],
locals: { browser_timezone: browser_timezone,
timezone: browser_timezone }
out << { id: '#timezone-controls',
html: control_html }
end
format.json { render json: out}
end
end

private

# Use callbacks to share common setup or constraints between actions.
def set_event
@event = Event.friendly.find(params[:id])
end

def shift_times_to_timezone_for_editing
if (@event&.timezone)
@event.start = @event.start&.in_time_zone(@event.timezone.to_s)
@event.end = @event.end&.in_time_zone(@event.timezone.to_s)
end
end

def shift_times_to_utc_for_saving
if (@event&.timezone)
@event.start = @event.start&.change(zone: @event.timezone.to_s)
@event.end = @event.end&.change(zone: @event.timezone.to_s)
end
end
Comment on lines +271 to +283

# Never trust parameters from the scary internet, only allow the white list through.
def event_params
params.require(:event).permit(:external_id, :title, :subtitle, :url, :organizer, :last_scraped, :scraper_record,
Expand Down
28 changes: 24 additions & 4 deletions app/helpers/events_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,19 +148,39 @@ def neatly_printed_date_range(start, finish = nil)
end

if finish.blank? || differing.empty?
out = start.strftime(DATE_STRF)
out = l(start, format: DATE_STRF)
# Don't show time component if they are set to midnight since that is the default if no time specified.
# Revisit this decision if any events start occurring at midnight (timezone issue?)!

show_time = (start.hour != 0 || start.min != 0) || (finish.present? && (finish.hour != 0 || finish.min != 0))
if show_time
out << " @ #{start.strftime(TIME_STRF)}"
out << " - #{finish.strftime(TIME_STRF)}" if finish && (finish.hour != start.hour || finish.min != start.min)
date_time_separator = if (I18n.locale.to_s == 'fr')
', '
else
' @ '
end
out << "#{date_time_separator}#{l(start, format: TIME_STRF)}"
out << " - #{l(finish, format: TIME_STRF)}" if finish && (finish.hour != start.hour || finish.min != start.min)
end
out
elsif differing.any?
"#{start.strftime(differing.join(' '))} - #{finish.strftime(DATE_STRF)}"
"#{l(start, format: differing.join(' '))} - #{l(finish, format: DATE_STRF)}"
end
end
end

def normalized_icu_timezone(name)
# Get more standardized name from TZinfo...
tz_name = ActiveSupport::TimeZone[name]&.tzinfo&.name

return nil unless tz_name

# The ICU library we use want's underscores not spaces ...
tz_name = tz_name.gsub(' ', '_')
Comment on lines +178 to +179
formatter = ICU::TimeFormatting.create(locale: I18n.locale.to_s,
zone: tz_name,
skeleton: '%zzzz')
formatter.format(Time.now)
end

end
4 changes: 2 additions & 2 deletions app/models/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,11 @@ def end_utc
end

def start_local
set_to_local start
start&.in_time_zone(self&.timezone)
end
Comment on lines 188 to 190

def end_local
set_to_local self.end
self.end&.in_time_zone(self&.timezone)
end
Comment on lines 192 to 194

def started?
Expand Down
4 changes: 4 additions & 0 deletions app/views/common/_associated_events.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,9 @@
<%= link_to "View all #{name.pluralize(shown_count)} & filter", inc_expired_link, class: 'btn btn-xs btn-default' %>
<% end %>

<% if TeSS::Config.feature.fetch('timezone_controls') %>
<%= render partial: 'events/event_timezone_control' %>
<% end %>

<%= render partial: 'common/masonry_grid', locals: { objects: resources } %>
</div>
6 changes: 5 additions & 1 deletion app/views/events/_event.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@
<%= elixir_node_icon %>
<% end -%>

<p><%= neatly_printed_date_range(event.start, event.end) %></p>
<% if TeSS::Config.feature.fetch('timezone_controls') %>
<%= render partial: 'events/event_time_div', locals: {event: event} %>
<% else %>
<p><%= neatly_printed_date_range(event.start, event.end) %></p>
<% end %>

<% if event.onsite? %>
<% location = [event.city, event.country].reject { |field_value| field_value.blank? }.join(", ") %>
Expand Down
16 changes: 16 additions & 0 deletions app/views/events/_event_time_div.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<% # Fake the timezone if passed as an argument
if local_assigns[:timezone]
saved_timezone = event.timezone
event.timezone = timezone
end %>
<div id="event-time-<%= event.id %>" data-event-id="<%= event.id %>" class="time-with-zone">
<p class="date no-spacing">
<strong class="text-primary"><%= t('events.date') %>: </strong>
<%= neatly_printed_date_range(event.start_local, event.end_local) %>
<% unless event.timezone %>
<%= t('events.no_timezone_provided_gmt_assumed') %>
<% end %>
Comment on lines +10 to +12
</p>
<%= display_attribute(event, :timezone) {|v| normalized_icu_timezone(v) } %>
</div>
<% event.timezone = saved_timezone if local_assigns[:timezone] %>
58 changes: 58 additions & 0 deletions app/views/events/_event_timezone_control.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<%# If browser_timezone is not set, do not render user interface %>
<div id="timezone-controls">
<div class="center-block well">

<p class='text-center'>
<span id="timezone-display">
<span class="timezone-display-value <%= 'unselected' if session[:tz].present? %>">
<%= t('events.timezone_controls.all_events_in_local_html') %>
</span>
<% if local_assigns[:browser_timezone] %>
<span class="timezone-display-value <%= 'unselected' if session[:tz].blank? %>">
<%= t('events.timezone_controls.all_events_in_timezone_html',
timezone: normalized_icu_timezone(browser_timezone)) %>
</span>
<% end %>
</span>

<% if local_assigns[:browser_timezone] %>
<a data-toggle="collapse" href="#timezone-select" class="show-timezone-controls"
role="button" aria-expanded="false" aria-controls="collapseExample">
<%= t('events.timezone_controls.controls_show') %>
</a>
<% end %>
</p>

<% if local_assigns[:browser_timezone] %>
<div class="collapse" id="timezone-select">
<hr>
<i class="fa fa-window-close close-timezone-controls"
aria-label="<%= t('events.timezone_controls.close') %>"></i>

Comment on lines +19 to +31
<fieldset>
<legend><%= t('events.timezone_controls.controls_legend') %></legend>

<div>
<input type="radio" id="event-timezone-choice"
name="timezone-choice" value="reset"
<%= 'checked' if session[:tz].blank? %> />&nbsp;
<label for="event-timezone-choice">
<%= t('events.timezone_controls.local_time_select') %>
</label>
</div>

<div>
<input type="radio" id="browser-timezone-choice"
name="timezone-choice" value="<%= browser_timezone %>"
<%= 'checked' if session[:tz].present? %> />&nbsp;
<label for="browser-timezone-choice">
<%= t('events.timezone_controls.timezone_select_html',
timezone: normalized_icu_timezone(browser_timezone)) %>
</label>
</div>
</fieldset>
</div>
<% end %>

</div>
</div>
16 changes: 10 additions & 6 deletions app/views/events/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,20 @@
<%= f.input :description, as: :markdown_area, input_html: { rows: '5', title: t('events.hints.description') },
field_lock: true %>

<!-- Field: Start -->
<%= f.input :start, as: :datetime_picker, field_lock: true, input_html: { title: t('events.hints.start') } %>

<!-- Field: End -->
<%= f.input :end, as: :datetime_picker, field_lock: true, input_html: { title: t('events.hints.end') } %>

<!-- Field: Timezone -->
<%= f.input :timezone, as: :time_zone, field_lock: true, priority: priority_time_zones,
input_html: { class: 'js-select2', title: t('events.hints.timezone') } %>

<%# We need to assume that these start/end times are in the selected timezone.
# This will need some shifting in the controller when saving, and some unshifting when loading %>
<!-- Field: Start -->
<%= f.input :start, as: :datetime_picker, field_lock: true, label: t('events.labels.start'),
input_html: { title: t('events.hints.start') } %>

<!-- Field: End -->
<%= f.input :end, as: :datetime_picker, field_lock: true, label: t('events.labels.end'),
input_html: { title: t('events.hints.end') } %>

<!-- Field: Duration -->
<%= f.input :duration, as: :string, input_html: { title: t('events.hints.duration') } %>

Expand Down
Loading
Loading