360 lines
17 KiB
HTML
360 lines
17 KiB
HTML
{% extends "base.html" %}
|
|
{# Copyright The IETF Trust 2015, 2020, All Rights Reserved #}
|
|
{% load origin %}
|
|
{% load cache %}
|
|
{% load ietf_filters static classname %}
|
|
|
|
{% block pagehead %}
|
|
<link rel="stylesheet" href="{% static "ietf/css/datatables.css" %}">
|
|
<link rel="stylesheet" href="{% static "fullcalendar/core/main.css" %}">
|
|
<link rel="stylesheet" href="{% static "fullcalendar/daygrid/main.css" %}">
|
|
|
|
{% endblock %}
|
|
|
|
{% block bodyAttrs %}data-bs-spy="scroll" data-bs-target="#affix"{% endblock %}
|
|
|
|
{% block title %}Upcoming Meetings{% endblock %}
|
|
|
|
{% block morecss %}
|
|
div.title-buttons {
|
|
margin-bottom: 0.5em;
|
|
margin-top: 1em;
|
|
text-align: right;
|
|
}
|
|
.tz-display {
|
|
margin-top: 0.5em;
|
|
}
|
|
.tz-display label {
|
|
font-weight: normal;
|
|
}
|
|
.tz-display a {
|
|
cursor: pointer;
|
|
}
|
|
select.tz-select {
|
|
min-width: 15em;
|
|
margin-bottom: 0.3em;
|
|
}
|
|
{% endblock %}
|
|
{% block content %}
|
|
{% origin %}
|
|
<div class="row">
|
|
<div class="col-md-10">
|
|
<div class="row">
|
|
<div class="col-xs-6">
|
|
<h1>Upcoming Meetings</h1>
|
|
</div>
|
|
<div class="title-buttons col-xs-6">
|
|
<div>
|
|
<a title="iCalendar subscription for upcoming meetings" href="webcal://{{request.get_host}}{% url 'ietf.meeting.views.upcoming_ical' %}">
|
|
<i class="bi bi-share"></i>
|
|
</a>
|
|
<a title="iCalendar entry for upcoming meetings" href="{% url 'ietf.meeting.views.upcoming_ical' %}">
|
|
<i class="bi bi-calendar"></i>
|
|
</a>
|
|
</div>
|
|
<div class="tz-display">
|
|
<label for="timezone-select">Time zone:</label>
|
|
<small>
|
|
<a id="local-timezone" onclick="ietf_timezone.use('local')">Local</a> |
|
|
<a id="utc-timezone" onclick="ietf_timezone.use('UTC')">UTC</a>
|
|
</small>
|
|
<select class="tz-select" id="timezone-select" autocomplete="off">
|
|
<!-- Avoid blank while loading. Needs to agree with native times in the table
|
|
so the display is correct if JS is not enabled -->
|
|
<option selected>UTC</option>
|
|
</select>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p>For more on regular IETF meetings see <a href="https://www.ietf.org/meeting/upcoming.html">here</a></p>
|
|
<p>Meeting important dates are not included in upcoming meeting calendars. They have <a href="{% url 'ietf.meeting.views.important_dates' %}">their own calendar</a></p>
|
|
|
|
{% include 'meeting/agenda_filter.html' with filter_categories=filter_categories customize_button_text="Customize the meeting list..." only%}
|
|
|
|
{% if menu_entries %}
|
|
<ul class="nav nav-tabs">
|
|
{% for name, url in menu_entries %}
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if selected_menu_entry == name.lower %}active{% endif %}" href="{{ url }}">{{ name }}</a>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% endif %}
|
|
|
|
{% if menu_actions %}
|
|
<div id="menu-actions" class="buttonlist">
|
|
{% for action in menu_actions %}
|
|
<a class="btn btn-primary {% if action.append_filter %}agenda-link filterable{% endif %}"
|
|
href="{{ action.url }}">{{ action.label }}</a>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% cache 600 upcoming-meetings entries.count %}
|
|
{% if entries %}
|
|
<table id="upcoming-meeting-table" class="table table-sm table-striped tablesorter">
|
|
<thead>
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>Group</th>
|
|
<th>Meeting</th>
|
|
<th class="sorter-false text-right"> </th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for entry in entries %}
|
|
<tr class="entry"
|
|
{% if entry|classname == 'Session' %}data-filter-keywords="{{ entry.filter_keywords|join:',' }}"{% endif %}>
|
|
{% if entry|classname == 'Meeting' %}
|
|
{% with meeting=entry %}
|
|
<td class="meeting-time"
|
|
data-start-date="{{ meeting.date }}" {# dates local to meeting #}
|
|
data-end-date="{{ meeting.end }}"
|
|
data-time-zone="{{ meeting.time_zone }}">
|
|
{{ meeting.date }} - {{ meeting.end }}
|
|
</td>
|
|
<td>ietf</td>
|
|
<td><a class="ietf-meeting-link" href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">IETF {{ meeting.number }}</a></td>
|
|
<td></td>
|
|
{% endwith %}
|
|
{% elif entry|classname == 'Session' %}
|
|
{% with session=entry group=entry.group meeting=entry.meeting%}
|
|
<td class="session-time"
|
|
data-start-utc="{{ session.official_timeslotassignment.timeslot.utc_start_time | date:'Y-m-d H:i' }}Z"
|
|
data-end-utc="{{ session.official_timeslotassignment.timeslot.utc_end_time | date:'Y-m-d H:i' }}Z">
|
|
{{ session.official_timeslotassignment.timeslot.utc_start_time | date:"Y-m-d H:i"}} - {{ session.official_timeslotassignment.timeslot.utc_end_time | date:"H:i" }}
|
|
</td>
|
|
<td><a href="{% url 'ietf.group.views.group_home' acronym=group.acronym %}">{{ group.acronym }}</a></td>
|
|
<td>
|
|
<a class="interim-meeting-link" href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=group.acronym %}"> {{ meeting.number }}</a>
|
|
</td>
|
|
{% if session.current_status == 'canceled' %}
|
|
<td class='text-right'>
|
|
<span class="badge bg-warning">CANCELLED</span>
|
|
</td>
|
|
{% else %}
|
|
<td class='text-right'>
|
|
{% include "meeting/interim_session_buttons.html" with show_agenda=True %}
|
|
</td>
|
|
{% endif %}
|
|
{% endwith %}
|
|
{% else %}
|
|
<td><span class="badge bg-warning">Unexpected entry type: {{entry|classname}}</span></td>
|
|
<td></td>
|
|
<td></td>
|
|
<td></td>
|
|
{% endif %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<h3>No upcoming meetings</h3>
|
|
{% endif %}
|
|
{% endcache %}
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-10">
|
|
<div class="tz-display text-right">
|
|
<label for="timezone-select-bottom">Time zone: </label>
|
|
<small>
|
|
<a id="local-timezone-bottom" onclick="ietf_timezone.use('local')">Local</a> |
|
|
<a id="utc-timezone-bottom" onclick="ietf_timezone.use('UTC')">UTC</a>
|
|
</small>
|
|
<select class="tz-select" id="timezone-select-bottom"></select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-10">
|
|
<div id="calendar"></div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script src="{% static "ietf/js/datatables.js" %}"></script>
|
|
<script src="{% static 'fullcalendar/core/main.js' %}"></script>
|
|
<script src="{% static 'fullcalendar/daygrid/main.js' %}"></script>
|
|
<script src="{% static 'ietf/js/moment.js' %}"></script>
|
|
<script src="{% static 'ietf/js/moment-timezone-with-data-10-year-range.js' %}"></script>
|
|
<script src="{% static 'ietf/js/agenda_filter.js' %}"></script>
|
|
<script src="{% static 'ietf/js/agenda_materials.js' %}"></script>
|
|
<script src="{% static 'ietf/js/timezone.js' %}"></script>
|
|
<script>
|
|
// List of all events with meta-info needed for filtering
|
|
var all_event_list = [{% for entry in entries %}
|
|
{% if entry|classname == 'Meeting' %}
|
|
{% with meeting=entry %}
|
|
{
|
|
ietf_meeting_number: '{{ meeting.number }}',
|
|
start_moment: moment.tz('{{meeting.date}}', '{{ meeting.time_zone }}').startOf('day'),
|
|
end_moment: moment.tz('{{meeting.end}}', '{{ meeting.time_zone }}').endOf('day'),
|
|
url: '{% url 'ietf.meeting.views.agenda' num=meeting.number %}'
|
|
}{% if not forloop.last %}, {% endif %}
|
|
{% endwith %}
|
|
{% else %} {# if it's not a Meeting, it's a Session #}
|
|
{% with session=entry %}
|
|
{
|
|
group: '{% if session.group %}{{session.group.acronym}}{% endif %}',
|
|
filter_keywords: ["{{ session.filter_keywords|join:'","' }}"],
|
|
start_moment: moment.utc('{{session.official_timeslotassignment.timeslot.utc_start_time | date:"Y-m-d H:i"}}'),
|
|
end_moment: moment.utc('{{session.official_timeslotassignment.timeslot.utc_end_time | date:"Y-m-d H:i"}}'),
|
|
url: '{% url 'ietf.meeting.views.session_details' num=session.meeting.number acronym=session.group.acronym %}'
|
|
}
|
|
{% endwith %}
|
|
{% if not forloop.last %}, {% endif %}
|
|
{% endif %}
|
|
{% endfor %}];
|
|
var filtered_event_list = []; // currently visible list
|
|
var display_events = []; // filtered events, processed for calendar display
|
|
var event_calendar; // handle on the calendar object
|
|
var current_tz = 'UTC';
|
|
|
|
// Test whether an event should be visible given a set of filter parameters
|
|
function calendar_event_visible(filter_params, event) {
|
|
// Visible if filtering is disabled or event has no keywords
|
|
if (!agenda_filter.filtering_is_enabled(filter_params) || !event.filter_keywords) {
|
|
return true;
|
|
}
|
|
|
|
// Visible if shown and not hidden
|
|
return (!agenda_filter.keyword_match(filter_params.hide, event.filter_keywords)
|
|
&& agenda_filter.keyword_match(filter_params.show, event.filter_keywords));
|
|
}
|
|
|
|
/* Apply filter_params to the event list */
|
|
function filter_calendar_events(filter_params, event_list) {
|
|
var filtered_output = [];
|
|
for (var ii = 0; ii < event_list.length; ii++) {
|
|
var this_event = event_list[ii];
|
|
if (calendar_event_visible(filter_params, this_event)) {
|
|
filtered_output.push(this_event);
|
|
}
|
|
}
|
|
return filtered_output;
|
|
}
|
|
|
|
// format a moment in a tz
|
|
var moment_formats = {time: 'HH:mm', date: 'YYYY-MM-DD', datetime: 'YYYY-MM-DD HH:mm'};
|
|
function format_moment(t_moment, tz, fmt_type) {
|
|
return t_moment.tz(tz).format(moment_formats[fmt_type]);
|
|
}
|
|
|
|
function make_display_events(event_data, tz) {
|
|
var output = [];
|
|
var calendarEl = document.getElementById('calendar');
|
|
var glue = calendarEl.clientWidth > 720 ? ' ' : '\n';
|
|
return $.map(event_data, function(src_event) {
|
|
var title;
|
|
// Render IETF meetings with meeting dates, sessions with actual times
|
|
if (src_event.ietf_meeting_number) {
|
|
title = 'IETF ' + src_event.ietf_meeting_number;
|
|
} else {
|
|
title = (format_moment(src_event.start_moment, tz, 'time') + '-'
|
|
+ format_moment(src_event.end_moment, tz, 'time')
|
|
+ glue + (src_event.group || 'Invalid event'));
|
|
}
|
|
return {
|
|
title: title,
|
|
start: format_moment(src_event.start_moment, tz, 'datetime'),
|
|
end: format_moment(src_event.end_moment, tz, 'datetime'),
|
|
url: src_event.url
|
|
}; // all events have the URL
|
|
});
|
|
}
|
|
|
|
// Initialize or update the calendar, updating the filtered event list and/or timezone
|
|
function update_calendar(tz, filter_params) {
|
|
if (filter_params) {
|
|
// Update event list if we were called with filter params
|
|
filtered_event_list = filter_calendar_events(filter_params, all_event_list);
|
|
}
|
|
display_events = make_display_events(filtered_event_list, tz);
|
|
|
|
if (event_calendar) {
|
|
event_calendar.refetchEvents()
|
|
} else {
|
|
/* Initialize the calendar object.
|
|
* The event source is a function that simply returns the current global list of
|
|
* filtered events.
|
|
*/
|
|
var calendarEl = document.getElementById('calendar')
|
|
event_calendar = new FullCalendar.Calendar(calendarEl, {
|
|
plugins: ['dayGrid'],
|
|
displayEventTime: false,
|
|
events: function (fInfo, success) {success(display_events)},
|
|
eventRender: function (info) {
|
|
$(info.el).tooltip({ title: info.event.title })
|
|
},
|
|
timeFormat: 'H:mm',
|
|
})
|
|
event_calendar.render()
|
|
}
|
|
}
|
|
|
|
function update_meeting_display(filter_params) {
|
|
var meeting_rows = $("#upcoming-meeting-table tr.entry");
|
|
if (!agenda_filter.filtering_is_enabled(filter_params)) {
|
|
meeting_rows.show();
|
|
return;
|
|
}
|
|
|
|
// hide everything that has keywords
|
|
meeting_rows.filter(function(index, row){
|
|
return !!$(row).attr('data-filter-keywords');
|
|
}).hide();
|
|
|
|
$.each(filter_params['show'], function (i, v) {
|
|
agenda_filter.rows_matching_filter_keyword(meeting_rows, v).show();
|
|
});
|
|
$.each(filter_params['hide'], function (i, v) {
|
|
agenda_filter.rows_matching_filter_keyword(meeting_rows, v).hide();
|
|
});
|
|
}
|
|
|
|
function update_view(filter_params) {
|
|
update_meeting_display(filter_params);
|
|
update_calendar(current_tz, filter_params);
|
|
}
|
|
|
|
// Set up the filtering - the callback will be called when the page loads and on any filter changes
|
|
agenda_filter.set_update_callback(update_view);
|
|
agenda_filter.enable();
|
|
|
|
function format_session_time(session_elt, tz) {
|
|
var start = moment.utc($(session_elt).attr('data-start-utc'));
|
|
var end = moment.utc($(session_elt).attr('data-end-utc'));
|
|
return format_moment(start, tz, 'datetime') + ' - ' + format_moment(end, tz, 'time');
|
|
}
|
|
|
|
function format_meeting_time(meeting_elt, tz) {
|
|
var meeting_tz = $(meeting_elt).attr('data-time-zone');
|
|
var start = moment.tz($(meeting_elt).attr('data-start-date'), meeting_tz).startOf('day');
|
|
var end = moment.tz($(meeting_elt).attr('data-end-date'), meeting_tz).endOf('day');
|
|
return format_moment(start, tz, 'date') + ' - ' + format_moment(end, tz, 'date');
|
|
}
|
|
|
|
function timezone_changed(newtz) {
|
|
// update times for events in the table
|
|
if (current_tz !== newtz) {
|
|
current_tz = newtz;
|
|
$('.session-time').each(function () {
|
|
$(this).html(format_session_time(this, newtz));
|
|
});
|
|
$('.meeting-time').each(function () {
|
|
$(this).html(format_meeting_time(this, newtz));
|
|
});
|
|
}
|
|
|
|
update_calendar(newtz);
|
|
}
|
|
|
|
|
|
// Init with best guess at local timezone.
|
|
ietf_timezone.set_tz_change_callback(timezone_changed);
|
|
ietf_timezone.initialize('local');
|
|
</script>
|
|
{% endblock %} |