Merged in [19878] from jennifer@painless-security.com:
Hide timeslots type is disabled plus other schedule editor debugging/improvements. Fixes #3510. Fixes #3430.
- Legacy-Id: 19882
Note: SVN reference [19878] has been migrated to Git commit 795d861d96
This commit is contained in:
commit
b51d490110
|
@ -662,11 +662,15 @@ class SessionDetailsForm(forms.ModelForm):
|
|||
|
||||
def __init__(self, group, *args, **kwargs):
|
||||
session_purposes = group.features.session_purposes
|
||||
kwargs.setdefault('initial', {})
|
||||
kwargs['initial'].setdefault(
|
||||
'purpose',
|
||||
session_purposes[0] if len(session_purposes) > 0 else None,
|
||||
)
|
||||
# Default to the first allowed session_purposes. Do not do this if we have an instance,
|
||||
# though, because ModelForm will override instance data with initial data if it gets both.
|
||||
# When we have an instance we want to keep its value.
|
||||
if 'instance' not in kwargs:
|
||||
kwargs.setdefault('initial', {})
|
||||
kwargs['initial'].setdefault(
|
||||
'purpose',
|
||||
session_purposes[0] if len(session_purposes) > 0 else None,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['type'].widget.attrs.update({
|
||||
|
@ -682,7 +686,7 @@ class SessionDetailsForm(forms.ModelForm):
|
|||
class Meta:
|
||||
model = Session
|
||||
fields = (
|
||||
'name', 'short', 'purpose', 'type', 'requested_duration',
|
||||
'purpose', 'name', 'short', 'type', 'requested_duration',
|
||||
'on_agenda', 'remote_instructions', 'attendees', 'comments',
|
||||
)
|
||||
labels = {'requested_duration': 'Length'}
|
||||
|
|
|
@ -541,21 +541,21 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
|
|||
|
||||
past_swap_ts_buttons = self.driver.find_elements(By.CSS_SELECTOR,
|
||||
','.join(
|
||||
'.swap-timeslot-col[data-start="{}"]'.format(ts.utc_start_time().isoformat()) for ts in past_timeslots
|
||||
'*[data-start="{}"] .swap-timeslot-col'.format(ts.utc_start_time().isoformat()) for ts in past_timeslots
|
||||
)
|
||||
)
|
||||
self.assertEqual(len(past_swap_ts_buttons), len(past_timeslots), 'Missing past swap timeslot col buttons')
|
||||
|
||||
future_swap_ts_buttons = self.driver.find_elements(By.CSS_SELECTOR,
|
||||
','.join(
|
||||
'.swap-timeslot-col[data-start="{}"]'.format(ts.utc_start_time().isoformat()) for ts in future_timeslots
|
||||
'*[data-start="{}"] .swap-timeslot-col'.format(ts.utc_start_time().isoformat()) for ts in future_timeslots
|
||||
)
|
||||
)
|
||||
self.assertEqual(len(future_swap_ts_buttons), len(future_timeslots), 'Missing future swap timeslot col buttons')
|
||||
|
||||
now_swap_ts_buttons = self.driver.find_elements(By.CSS_SELECTOR,
|
||||
','.join(
|
||||
'.swap-timeslot-col[data-start="{}"]'.format(ts.utc_start_time().isoformat()) for ts in now_timeslots
|
||||
'[data-start="{}"] .swap-timeslot-col'.format(ts.utc_start_time().isoformat()) for ts in now_timeslots
|
||||
)
|
||||
)
|
||||
self.assertEqual(len(now_swap_ts_buttons), len(now_timeslots), 'Missing "now" swap timeslot col buttons')
|
||||
|
|
|
@ -536,15 +536,17 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
for s in sessions:
|
||||
s.requested_by_person = requested_by_lookup.get(s.requested_by)
|
||||
|
||||
s.scheduling_label = "???"
|
||||
s.purpose_label = None
|
||||
if (s.purpose.slug in ('none', 'regular')) and s.group:
|
||||
s.scheduling_label = s.group.acronym
|
||||
s.purpose_label = 'BoF' if s.group.is_bof() else s.group.type.name
|
||||
if s.group:
|
||||
if (s.purpose.slug in ('none', 'regular')):
|
||||
s.scheduling_label = s.group.acronym
|
||||
s.purpose_label = 'BoF' if s.group.is_bof() else s.group.type.name
|
||||
else:
|
||||
s.scheduling_label = s.name if s.name else f'??? [{s.group.acronym}]'
|
||||
s.purpose_label = s.purpose.name
|
||||
else:
|
||||
s.scheduling_label = s.name if s.name else '???'
|
||||
s.purpose_label = s.purpose.name
|
||||
if s.name:
|
||||
s.scheduling_label = s.name
|
||||
|
||||
s.requested_duration_in_hours = round(s.requested_duration.seconds / 60.0 / 60.0, 1)
|
||||
|
||||
|
|
|
@ -190,7 +190,7 @@ class MiscSessionForm(TimeSlotForm):
|
|||
Plenary = IETF''',
|
||||
required=False)
|
||||
location = forms.ModelChoiceField(queryset=Room.objects, required=False)
|
||||
remote_instructions = forms.CharField(max_length=255)
|
||||
remote_instructions = forms.CharField(max_length=255, required=False)
|
||||
show_location = forms.BooleanField(required=False)
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/* globals alert, jQuery, moment */
|
||||
jQuery(document).ready(function () {
|
||||
'use strict';
|
||||
|
||||
let content = jQuery(".edit-meeting-schedule");
|
||||
/* Drag data stored via the drag event dataTransfer interface is only accessible on
|
||||
* dragstart and dragend events. Other drag events can see only the MIME types that have
|
||||
|
@ -12,8 +14,9 @@ jQuery(document).ready(function () {
|
|||
|
||||
function reportServerError(xhr, textStatus, error) {
|
||||
let errorText = error || textStatus;
|
||||
if (xhr && xhr.responseText)
|
||||
errorText += "\n\n" + xhr.responseText;
|
||||
if (xhr && xhr.responseText) {
|
||||
errorText += '\n\n' + xhr.responseText;
|
||||
}
|
||||
alert("Error: " + errorText);
|
||||
}
|
||||
|
||||
|
@ -33,9 +36,14 @@ jQuery(document).ready(function () {
|
|||
let swapTimeslotButtons = content.find('.swap-timeslot-col');
|
||||
let days = content.find(".day-flow .day");
|
||||
let officialSchedule = content.hasClass('official-schedule');
|
||||
let timeSlotTypeInputs = content.find('.timeslot-type-toggles input');
|
||||
let sessionPurposeInputs = content.find('.session-purpose-toggles input');
|
||||
let timeSlotGroupInputs = content.find("#timeslot-group-toggles-modal .modal-body .individual-timeslots input");
|
||||
let sessionParentInputs = content.find(".session-parent-toggles input");
|
||||
const classes_to_hide = '.hidden-timeslot-group,.hidden-timeslot-type';
|
||||
|
||||
// hack to work around lack of position sticky support in old browsers, see https://caniuse.com/#feat=css-sticky
|
||||
if (content.find(".scheduling-panel").css("position") != "sticky") {
|
||||
if (content.find(".scheduling-panel").css("position") !== "sticky") {
|
||||
content.find(".scheduling-panel").css("position", "fixed");
|
||||
content.css("padding-bottom", "14em");
|
||||
}
|
||||
|
@ -59,7 +67,7 @@ jQuery(document).ready(function () {
|
|||
let res = [];
|
||||
|
||||
timeslots.each(function () {
|
||||
var timeslot = jQuery(this);
|
||||
const timeslot = jQuery(this);
|
||||
let start = startMoment(timeslot);
|
||||
let end = endMoment(timeslot);
|
||||
|
||||
|
@ -97,10 +105,11 @@ jQuery(document).ready(function () {
|
|||
let timeElement = jQuery(this).find(".time");
|
||||
|
||||
otherSessionElement.addClass("other-session-selected");
|
||||
if (scheduledAt)
|
||||
timeElement.text(timeElement.data("scheduled").replace("{time}", scheduledAt));
|
||||
else
|
||||
timeElement.text(timeElement.data("notscheduled"));
|
||||
if (scheduledAt) {
|
||||
timeElement.text(timeElement.data('scheduled').replace('{time}', scheduledAt));
|
||||
} else {
|
||||
timeElement.text(timeElement.data('notscheduled'));
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
@ -256,10 +265,10 @@ jQuery(document).ready(function () {
|
|||
// hide swap day/timeslot column buttons
|
||||
if (officialSchedule) {
|
||||
swapDaysButtons.filter(
|
||||
(_, elt) => parseISOTimestamp(elt.dataset.start).isSameOrBefore(now, 'day')
|
||||
(_, elt) => parseISOTimestamp(elt.closest('*[data-start]').dataset.start).isSameOrBefore(now, 'day')
|
||||
).hide();
|
||||
swapTimeslotButtons.filter(
|
||||
(_, elt) => parseISOTimestamp(elt.dataset.start).isSameOrBefore(now, 'minute')
|
||||
(_, elt) => parseISOTimestamp(elt.closest('*[data-start]').dataset.start).isSameOrBefore(now, 'minute')
|
||||
).hide();
|
||||
}
|
||||
}
|
||||
|
@ -278,9 +287,12 @@ jQuery(document).ready(function () {
|
|||
}
|
||||
|
||||
content.on("click", function (event) {
|
||||
if (jQuery(event.target).is(".session-info-container") || jQuery(event.target).closest(".session-info-container").length > 0)
|
||||
return;
|
||||
selectSessionElement(null);
|
||||
if (!(
|
||||
jQuery(event.target).is('.session-info-container') ||
|
||||
jQuery(event.target).closest('.session-info-container').length > 0
|
||||
)) {
|
||||
selectSessionElement(null);
|
||||
}
|
||||
});
|
||||
|
||||
sessions.on("click", function (event) {
|
||||
|
@ -418,12 +430,14 @@ jQuery(document).ready(function () {
|
|||
}
|
||||
|
||||
dropElement.append(sessionElement); // move element
|
||||
if (response.tombstone)
|
||||
if (response.tombstone) {
|
||||
dragParent.append(response.tombstone);
|
||||
}
|
||||
|
||||
updateCurrentSchedulingHints();
|
||||
if (dropParent.hasClass("unassigned-sessions"))
|
||||
if (dropParent.hasClass("unassigned-sessions")) {
|
||||
sortUnassigned();
|
||||
}
|
||||
}
|
||||
|
||||
if (dropParent.hasClass("unassigned-sessions")) {
|
||||
|
@ -436,8 +450,7 @@ jQuery(document).ready(function () {
|
|||
session: sessionElement.id.slice("session".length)
|
||||
}
|
||||
}).fail(failHandler).done(done);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
jQuery.ajax({
|
||||
url: window.location.href,
|
||||
method: "post",
|
||||
|
@ -476,7 +489,7 @@ jQuery(document).ready(function () {
|
|||
// disable any that have passed
|
||||
const now=effectiveNow();
|
||||
const past_radios = radios.filter(
|
||||
(_, radio) => parseISOTimestamp(radio.dataset.start).isSameOrBefore(now, datePrecision)
|
||||
(_, radio) => parseISOTimestamp(radio.closest('*[data-start]').dataset.start).isSameOrBefore(now, datePrecision)
|
||||
);
|
||||
past_radios.parent().addClass('text-muted');
|
||||
past_radios.prop('disabled', true);
|
||||
|
@ -489,7 +502,7 @@ jQuery(document).ready(function () {
|
|||
let swapDaysLabels = swapDaysModal.find(".modal-body label");
|
||||
let swapDaysRadios = swapDaysLabels.find('input[name=target_day]');
|
||||
let updateSwapDaysSubmitButton = function () {
|
||||
updateSwapSubmitButton(swapDaysModal, 'target_day')
|
||||
updateSwapSubmitButton(swapDaysModal, 'target_day');
|
||||
};
|
||||
// handler to prep and open the modal
|
||||
content.find(".swap-days").on("click", function () {
|
||||
|
@ -505,7 +518,7 @@ jQuery(document).ready(function () {
|
|||
updateSwapDaysSubmitButton();
|
||||
swapDaysModal.modal('show'); // show via JS so it won't open until it is initialized
|
||||
});
|
||||
swapDaysRadios.on("change", function () {updateSwapDaysSubmitButton()});
|
||||
swapDaysRadios.on("change", function () {updateSwapDaysSubmitButton();});
|
||||
|
||||
// swap timeslot columns
|
||||
let swapTimeslotsModal = content.find('#swap-timeslot-col-modal');
|
||||
|
@ -534,7 +547,7 @@ jQuery(document).ready(function () {
|
|||
updateSwapTimeslotsSubmitButton();
|
||||
swapTimeslotsModal.modal('show');
|
||||
});
|
||||
swapTimeslotsRadios.on("change", function () {updateSwapTimeslotsSubmitButton()});
|
||||
swapTimeslotsRadios.on("change", function () {updateSwapTimeslotsSubmitButton();});
|
||||
}
|
||||
|
||||
// hints for the current schedule
|
||||
|
@ -557,22 +570,44 @@ jQuery(document).ready(function () {
|
|||
});
|
||||
|
||||
scheduledSessions.sort(function (a, b) {
|
||||
if (a.start < b.start)
|
||||
if (a.start < b.start) {
|
||||
return -1;
|
||||
if (a.start > b.start)
|
||||
}
|
||||
if (a.start > b.start) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
let currentlyOpen = {};
|
||||
let openedIndex = 0;
|
||||
let markSessionConstraintViolations = function (sess, currentlyOpen) {
|
||||
sess.element.find(".constraints > span").each(function() {
|
||||
let sessionIds = this.dataset.sessions;
|
||||
|
||||
let violated = sessionIds && sessionIds.split(",").filter(function (v) {
|
||||
return (
|
||||
v !== sess.id &&
|
||||
v in currentlyOpen &&
|
||||
// ignore errors within the same timeslot
|
||||
// under the assumption that the sessions
|
||||
// in the timeslot happen sequentially
|
||||
sess.timeslot !== currentlyOpen[v].timeslot
|
||||
);
|
||||
}).length > 0;
|
||||
|
||||
jQuery(this).toggleClass("violated-hint", violated);
|
||||
});
|
||||
};
|
||||
|
||||
for (let i = 0; i < scheduledSessions.length; ++i) {
|
||||
let s = scheduledSessions[i];
|
||||
|
||||
// prune
|
||||
for (let sessionIdStr in currentlyOpen) {
|
||||
if (currentlyOpen[sessionIdStr].end <= s.start)
|
||||
if (currentlyOpen[sessionIdStr].end <= s.start) {
|
||||
delete currentlyOpen[sessionIdStr];
|
||||
}
|
||||
}
|
||||
|
||||
// expand
|
||||
|
@ -583,20 +618,7 @@ jQuery(document).ready(function () {
|
|||
}
|
||||
|
||||
// check for violated constraints
|
||||
s.element.find(".constraints > span").each(function () {
|
||||
let sessionIds = this.dataset.sessions;
|
||||
|
||||
let violated = sessionIds && sessionIds.split(",").filter(function (v) {
|
||||
return (v != s.id
|
||||
&& v in currentlyOpen
|
||||
// ignore errors within the same timeslot
|
||||
// under the assumption that the sessions
|
||||
// in the timeslot happen sequentially
|
||||
&& s.timeslot != currentlyOpen[v].timeslot);
|
||||
}).length > 0;
|
||||
|
||||
jQuery(this).toggleClass("violated-hint", violated);
|
||||
});
|
||||
markSessionConstraintViolations(s, currentlyOpen);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -614,8 +636,9 @@ jQuery(document).ready(function () {
|
|||
function updateAttendeesViolations() {
|
||||
sessions.each(function () {
|
||||
let roomCapacity = jQuery(this).closest(".timeslots").data("roomcapacity");
|
||||
if (roomCapacity && this.dataset.attendees)
|
||||
if (roomCapacity && this.dataset.attendees) {
|
||||
jQuery(this).toggleClass("too-many-attendees", +this.dataset.attendees > +roomCapacity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -634,10 +657,12 @@ jQuery(document).ready(function () {
|
|||
let ai = a[i];
|
||||
let bi = b[i];
|
||||
|
||||
if (ai > bi)
|
||||
if (ai > bi) {
|
||||
return 1;
|
||||
else if (ai < bi)
|
||||
}
|
||||
else if (ai < bi) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -645,8 +670,9 @@ jQuery(document).ready(function () {
|
|||
|
||||
let arrayWithSortKeys = array.map(function (a) {
|
||||
let res = [a];
|
||||
for (let i = 0; i < keyFunctions.length; ++i)
|
||||
for (let i = 0; i < keyFunctions.length; ++i) {
|
||||
res.push(keyFunctions[i](a));
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
|
@ -693,8 +719,9 @@ jQuery(document).ready(function () {
|
|||
let unassignedSessionsContainer = content.find(".unassigned-sessions .drop-target");
|
||||
|
||||
let sortedSessions = sortArrayWithKeyFunctions(unassignedSessionsContainer.children(".session").toArray(), keyFunctions);
|
||||
for (let i = 0; i < sortedSessions.length; ++i)
|
||||
for (let i = 0; i < sortedSessions.length; ++i) {
|
||||
unassignedSessionsContainer.append(sortedSessions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
content.find("select[name=sort_unassigned]").on("change click", function () {
|
||||
|
@ -704,7 +731,6 @@ jQuery(document).ready(function () {
|
|||
sortUnassigned();
|
||||
|
||||
// toggling visible sessions by session parents
|
||||
let sessionParentInputs = content.find(".session-parent-toggles input");
|
||||
|
||||
function setSessionHiddenParent(sess, hide) {
|
||||
sess.toggleClass('hidden-parent', hide);
|
||||
|
@ -725,7 +751,6 @@ jQuery(document).ready(function () {
|
|||
updateSessionParentToggling();
|
||||
|
||||
// Toggling timeslot types
|
||||
let timeSlotTypeInputs = content.find('.timeslot-type-toggles input');
|
||||
function updateTimeSlotTypeToggling() {
|
||||
let checked = [];
|
||||
timeSlotTypeInputs.filter(":checked").each(function () {
|
||||
|
@ -736,27 +761,12 @@ jQuery(document).ready(function () {
|
|||
sessions.not(checked.join(",")).addClass('hidden-timeslot-type');
|
||||
timeslots.filter(checked.join(",")).removeClass('hidden-timeslot-type');
|
||||
timeslots.not(checked.join(",")).addClass('hidden-timeslot-type');
|
||||
}
|
||||
if (timeSlotTypeInputs.length > 0) {
|
||||
timeSlotTypeInputs.on("change", updateTimeSlotTypeToggling);
|
||||
updateTimeSlotTypeToggling();
|
||||
content.find('#timeslot-group-toggles-modal .timeslot-type-toggles .select-all').get(0).addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
timeSlotTypeInputs.prop('checked', true);
|
||||
updateTimeSlotTypeToggling();
|
||||
});
|
||||
content.find('#timeslot-group-toggles-modal .timeslot-type-toggles .clear-all').get(0).addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
timeSlotTypeInputs.prop('checked', false);
|
||||
updateTimeSlotTypeToggling();
|
||||
});
|
||||
updateGridVisibility();
|
||||
}
|
||||
|
||||
|
||||
// Toggling session purposes
|
||||
let sessionPurposeInputs = content.find('.session-purpose-toggles input');
|
||||
function updateSessionPurposeToggling(evt) {
|
||||
function updateSessionPurposeToggling() {
|
||||
let checked = [];
|
||||
sessionPurposeInputs.filter(":checked").each(function () {
|
||||
checked.push(".purpose-" + this.value);
|
||||
|
@ -765,50 +775,193 @@ jQuery(document).ready(function () {
|
|||
sessions.filter(checked.join(",")).removeClass('hidden-purpose');
|
||||
sessions.not(checked.join(",")).addClass('hidden-purpose');
|
||||
}
|
||||
|
||||
if (timeSlotTypeInputs.length > 0) {
|
||||
timeSlotTypeInputs.on("change", updateTimeSlotTypeToggling);
|
||||
updateTimeSlotTypeToggling();
|
||||
content.find('#timeslot-type-toggles-modal .timeslot-type-toggles .select-all').get(0).addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
timeSlotTypeInputs.prop('checked', true);
|
||||
updateTimeSlotTypeToggling();
|
||||
});
|
||||
content.find('#timeslot-type-toggles-modal .timeslot-type-toggles .clear-all').get(0).addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
timeSlotTypeInputs.prop('checked', false);
|
||||
updateTimeSlotTypeToggling();
|
||||
});
|
||||
}
|
||||
|
||||
if (sessionPurposeInputs.length > 0) {
|
||||
sessionPurposeInputs.on("change", updateSessionPurposeToggling);
|
||||
updateSessionPurposeToggling();
|
||||
content.find('#session-toggles-modal .select-all').get(0).addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
sessionPurposeInputs.prop('checked', true);
|
||||
sessionPurposeInputs.not(':disabled').prop('checked', true);
|
||||
updateSessionPurposeToggling();
|
||||
});
|
||||
content.find('#session-toggles-modal .clear-all').get(0).addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
sessionPurposeInputs.prop('checked', false);
|
||||
sessionPurposeInputs.not(':disabled').prop('checked', false);
|
||||
updateSessionPurposeToggling();
|
||||
});
|
||||
}
|
||||
|
||||
// toggling visible timeslots
|
||||
let timeSlotGroupInputs = content.find("#timeslot-group-toggles-modal .modal-body .individual-timeslots input");
|
||||
function updateTimeSlotGroupToggling() {
|
||||
let checked = [];
|
||||
timeSlotGroupInputs.filter(":checked").each(function () {
|
||||
checked.push("." + this.value);
|
||||
});
|
||||
|
||||
timeslots.filter(checked.join(",")).removeClass("hidden");
|
||||
timeslots.not(checked.join(",")).addClass("hidden");
|
||||
timeslots.filter(checked.join(",")).removeClass("hidden-timeslot-group");
|
||||
timeslots.not(checked.join(",")).addClass("hidden-timeslot-group");
|
||||
updateGridVisibility();
|
||||
}
|
||||
|
||||
days.each(function () {
|
||||
jQuery(this).toggle(jQuery(this).find(".timeslot:not(.hidden)").length > 0);
|
||||
function updateSessionPurposeOptions() {
|
||||
sessionPurposeInputs.each((_, purpose_input) => {
|
||||
if (sessions
|
||||
.filter('.purpose-' + purpose_input.value)
|
||||
.not('.hidden')
|
||||
.length === 0) {
|
||||
purpose_input.setAttribute('disabled', 'disabled');
|
||||
purpose_input.closest('.session-purpose-toggle').classList.add('text-muted');
|
||||
} else {
|
||||
purpose_input.removeAttribute('disabled');
|
||||
purpose_input.closest('.session-purpose-toggle').classList.remove('text-muted');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide timeslot toggles for hidden timeslots
|
||||
*/
|
||||
function updateTimeSlotOptions() {
|
||||
timeSlotGroupInputs.each((_, timeslot_input) => {
|
||||
if (timeslots
|
||||
.filter('.' + timeslot_input.value)
|
||||
.not('.hidden-timeslot-type')
|
||||
.length === 0) {
|
||||
timeslot_input.setAttribute('disabled', 'disabled');
|
||||
} else {
|
||||
timeslot_input.removeAttribute('disabled');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Make timeslots visible/invisible/hidden
|
||||
*
|
||||
* Responsible for final determination of whether a timeslot is visible, invisible, or hidden.
|
||||
*/
|
||||
function updateTimeSlotVisibility() {
|
||||
timeslots.not(classes_to_hide).removeClass('hidden');
|
||||
timeslots.filter(classes_to_hide).addClass('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sessions visible/invisible/hidden
|
||||
*
|
||||
* Responsible for final determination of whether a session is visible or hidden.
|
||||
*/
|
||||
function updateSessionVisibility() {
|
||||
sessions.not(classes_to_hide).removeClass('hidden');
|
||||
sessions.filter(classes_to_hide).addClass('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make day / time headers visible / hidden to match visible grid contents
|
||||
*/
|
||||
function updateHeaderVisibility() {
|
||||
days.each(function () {
|
||||
jQuery(this).toggle(jQuery(this).find(".timeslot").not(".hidden").length > 0);
|
||||
});
|
||||
|
||||
const rgs = content.find('.day-flow .room-group');
|
||||
rgs.each(function (index, roomGroup) {
|
||||
const headerLabels = jQuery(roomGroup).find('.time-header .time-label');
|
||||
const rgTimeslots = jQuery(roomGroup).find('.timeslot');
|
||||
headerLabels.each(function(index, label) {
|
||||
jQuery(label).toggle(
|
||||
rgTimeslots
|
||||
.filter('[data-start="' + label.dataset.start + '"][data-end="' + label.dataset.end + '"]')
|
||||
.not('.hidden')
|
||||
.length > 0
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update visibility of room rows
|
||||
*/
|
||||
function updateRoomVisibility() {
|
||||
const tsContainers = { toShow: [], toHide: [] };
|
||||
const roomGroups = { toShow: [], toHide: [] };
|
||||
// roomsWithVisibleSlots is an array of room IDs that have at least one visible timeslot
|
||||
let roomsWithVisibleSlots = content.find('.day-flow .timeslots')
|
||||
.has('.timeslot:not(.hidden)')
|
||||
.map((_, e) => e.dataset.roomId).get();
|
||||
roomsWithVisibleSlots = [...new Set(roomsWithVisibleSlots)]; // unique-ify by converting to Set and back
|
||||
|
||||
/* The "timeslots" class identifies elements (now and probably always <div>s) that are containers (i.e.,
|
||||
* parents) of timeslots (elements with the "timeslot" class). Sort these containers based on whether
|
||||
* their room has at least one timeslot visible - if so, we will show it, if not it will be hidden.
|
||||
* This will hide containers both in the day-flow and room label sections, so it will hide the room
|
||||
* labels for rooms with no visible timeslots. */
|
||||
content.find('.timeslots').each((_, e) => {
|
||||
if (roomsWithVisibleSlots.indexOf(e.dataset.roomId) === -1) {
|
||||
tsContainers.toHide.push(e);
|
||||
} else {
|
||||
tsContainers.toShow.push(e);
|
||||
}
|
||||
});
|
||||
|
||||
/* Now check whether each room group has any rooms not being hidden. If not, entirely hide the
|
||||
* room group so that all its headers, etc, do not take up space. */
|
||||
content.find('.room-group').each((_, e) => {
|
||||
if (jQuery(e).has(tsContainers.toShow).length > 0) {
|
||||
roomGroups.toShow.push(e);
|
||||
} else {
|
||||
roomGroups.toHide.push(e);
|
||||
}
|
||||
});
|
||||
jQuery(roomGroups.toShow).show();
|
||||
jQuery(roomGroups.toHide).hide();
|
||||
jQuery(tsContainers.toShow).show();
|
||||
jQuery(tsContainers.toHide).hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update visibility of UI elements
|
||||
*
|
||||
* Call this after changing 'hidden-*' classes on timeslots
|
||||
*/
|
||||
function updateGridVisibility() {
|
||||
updateTimeSlotVisibility();
|
||||
updateSessionVisibility();
|
||||
updateHeaderVisibility();
|
||||
updateRoomVisibility();
|
||||
updateTimeSlotOptions();
|
||||
updateSessionPurposeOptions();
|
||||
content.find('div.edit-grid').removeClass('hidden');
|
||||
}
|
||||
|
||||
timeSlotGroupInputs.on("click change", updateTimeSlotGroupToggling);
|
||||
content.find('#timeslot-group-toggles-modal .timeslot-group-buttons .select-all').get(0).addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
timeSlotGroupInputs.prop('checked', true);
|
||||
timeSlotGroupInputs.not(':disabled').prop('checked', true);
|
||||
updateTimeSlotGroupToggling();
|
||||
});
|
||||
content.find('#timeslot-group-toggles-modal .timeslot-group-buttons .clear-all').get(0).addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
timeSlotGroupInputs.prop('checked', false);
|
||||
timeSlotGroupInputs.not(':disabled').prop('checked', false);
|
||||
updateTimeSlotGroupToggling();
|
||||
});
|
||||
|
||||
|
@ -817,9 +970,9 @@ jQuery(document).ready(function () {
|
|||
setInterval(updatePastTimeslots, 10 * 1000 /* ms */);
|
||||
|
||||
// session info
|
||||
content.find(".session-info-container").on("mouseover", ".other-session", function (event) {
|
||||
content.find(".session-info-container").on("mouseover", ".other-session", function () {
|
||||
sessions.filter("#session" + this.dataset.othersessionid).addClass("highlight");
|
||||
}).on("mouseleave", ".other-session", function (event) {
|
||||
}).on("mouseleave", ".other-session", function () {
|
||||
sessions.filter("#session" + this.dataset.othersessionid).removeClass("highlight");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright The IETF Trust 2021, All Rights Reserved
|
||||
/* Copyright The IETF Trust 2021-2022, All Rights Reserved
|
||||
*
|
||||
* JS support for the SessionDetailsForm
|
||||
* */
|
||||
|
@ -37,30 +37,27 @@
|
|||
}
|
||||
|
||||
/* Update visibility of 'type' select so it is only shown when multiple options are available */
|
||||
function update_widget_visibility(elt, purpose, allowed_types) {
|
||||
function update_type_field_visibility(elt, purpose, allowed_types) {
|
||||
const valid_types = allowed_types[purpose] || [];
|
||||
if (valid_types.length > 1) {
|
||||
elt.removeAttribute('hidden'); // make visible
|
||||
elt.classList.remove('hidden'); // make visible
|
||||
} else {
|
||||
elt.setAttribute('hidden', 'hidden'); // make invisible
|
||||
elt.classList.add('hidden'); // make invisible
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the 'type' select to reflect a change in the selected purpose */
|
||||
function update_type_element(type_elt, purpose, type_options, allowed_types) {
|
||||
update_widget_visibility(type_elt, purpose, allowed_types);
|
||||
update_type_field_visibility(type_elt.closest('.form-group'), purpose, allowed_types);
|
||||
update_type_option_visibility(type_options, purpose, allowed_types);
|
||||
set_valid_type(type_elt, purpose, allowed_types);
|
||||
}
|
||||
|
||||
function update_name_field_visibility(name_elt, purpose) {
|
||||
const row = name_elt.closest('tr');
|
||||
if (row) {
|
||||
if (purpose === 'regular') {
|
||||
row.setAttribute('hidden', 'hidden');
|
||||
} else {
|
||||
row.removeAttribute('hidden');
|
||||
}
|
||||
function update_name_field_visibility(name_elts, purpose) {
|
||||
if (!purpose || purpose === 'regular') {
|
||||
name_elts.forEach(e => e.closest('.form-group').classList.add('hidden'));
|
||||
} else {
|
||||
name_elts.forEach(e => e.closest('.form-group').classList.remove('hidden'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +76,10 @@
|
|||
if (purpose_elt.type === 'hidden') {
|
||||
return; // element is hidden, so nothing to do
|
||||
}
|
||||
const name_elt = document.getElementById(id_prefix + 'name');
|
||||
const name_elts = [
|
||||
document.getElementById(id_prefix + 'name'),
|
||||
document.getElementById(id_prefix + 'short'),
|
||||
];
|
||||
const type_elt = document.getElementById(id_prefix + 'type');
|
||||
const type_options = type_elt.getElementsByTagName('option');
|
||||
const allowed_types = (type_elt.dataset.allowedOptions) ?
|
||||
|
@ -88,17 +88,17 @@
|
|||
// update on future changes
|
||||
purpose_elt.addEventListener(
|
||||
'change',
|
||||
purpose_change_handler(name_elt, type_elt, type_options, allowed_types)
|
||||
purpose_change_handler(name_elts, type_elt, type_options, allowed_types)
|
||||
);
|
||||
|
||||
// update immediately
|
||||
update_type_element(type_elt, purpose_elt.value, type_options, allowed_types);
|
||||
update_name_field_visibility(name_elt, purpose_elt.value);
|
||||
update_name_field_visibility(name_elts, purpose_elt.value);
|
||||
|
||||
// hide the purpose selector if only one option
|
||||
const purpose_options = purpose_elt.querySelectorAll('option:not([value=""])');
|
||||
if (purpose_options.length < 2) {
|
||||
purpose_elt.closest('tr').setAttribute('hidden', 'hidden');
|
||||
purpose_elt.closest('.form-group').classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,12 +19,9 @@
|
|||
{# style off-agenda sessions to indicate this #}
|
||||
.edit-meeting-schedule .session.off-agenda { filter: brightness(0.9); }
|
||||
{# type and purpose styling #}
|
||||
.edit-meeting-schedule .edit-grid .timeslot.wrong-timeslot-type,
|
||||
.edit-meeting-schedule .edit-grid .timeslot.hidden-timeslot-type { background-color: transparent; ); }
|
||||
.edit-meeting-schedule .edit-grid .timeslot.wrong-timeslot-type .time-label,
|
||||
.edit-meeting-schedule .edit-grid .timeslot.hidden-timeslot-type .time-label { color: transparent; ); }
|
||||
.edit-meeting-schedule .session.hidden-purpose,
|
||||
.edit-meeting-schedule .session.hidden-timeslot-type { filter: blur(3px); }
|
||||
.edit-meeting-schedule .edit-grid .timeslot.wrong-timeslot-type { background-color: transparent; ); }
|
||||
.edit-meeting-schedule .edit-grid .timeslot.wrong-timeslot-type .time-label { color: transparent; ); }
|
||||
.edit-meeting-schedule .session.hidden-purpose { filter: blur(3px); }
|
||||
{% endblock morecss %}
|
||||
|
||||
{% block title %}{{ schedule.name }}: IETF {{ meeting.number }} meeting agenda{% endblock %}
|
||||
|
@ -45,14 +42,17 @@
|
|||
<p class="pull-right">
|
||||
{% if can_edit_properties %}
|
||||
<a href="{% url "ietf.meeting.views.edit_schedule_properties" schedule.meeting.number schedule.owner_email schedule.name %}">Edit properties</a>
|
||||
|
||||
·
|
||||
{% endif %}{% if user|has_role:"Secretariat" %}
|
||||
<a href="{% url "ietf.meeting.views.edit_timeslots" num=meeting.number %}">
|
||||
Edit timeslots
|
||||
</a>
|
||||
·
|
||||
{% endif %}
|
||||
|
||||
<a href="{% url "ietf.meeting.views.new_meeting_schedule" num=meeting.number owner=schedule.owner_email name=schedule.name %}">Copy agenda</a>
|
||||
·
|
||||
|
||||
<a href="{% url "ietf.meeting.views.list_schedules" num=meeting.number %}">Other Agendas</a>
|
||||
<a href="{% url "ietf.meeting.views.list_schedules" num=meeting.number %}">Other agendas</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
@ -85,7 +85,7 @@
|
|||
</a>
|
||||
</p>
|
||||
{% else %}
|
||||
<div class="edit-grid {% if not can_edit %}read-only{% endif %}">
|
||||
<div class="edit-grid hidden {% if not can_edit %}read-only{% endif %}">
|
||||
{# using the same markup in both room labels and the actual days ensures they are aligned #}
|
||||
<div class="room-label-column">
|
||||
{% for day_data in days.values %}
|
||||
|
@ -99,7 +99,7 @@
|
|||
<div class="room-group">
|
||||
<div class="time-header"><div class="time-label"></div></div>
|
||||
{% for room_data in rgroup %}{% with room_data.room as room %}
|
||||
<div class="timeslots">
|
||||
<div class="timeslots" data-room-id="{{ room.pk }}">
|
||||
<div class="room-name">
|
||||
<strong>{{ room.name }}</strong><br>
|
||||
{% if room.capacity %}{{ room.capacity }} <i class="fa fa-user-o"></i>{% endif %}
|
||||
|
@ -131,19 +131,23 @@
|
|||
<div class="time-header">
|
||||
{# All rooms in a group have same timeslots; grab the first for the labels #}
|
||||
{% for t in rgroup.0.timeslots %}
|
||||
<div class="time-label" style="width: {{ t.layout_width }}rem">
|
||||
<div class="time-label"
|
||||
style="width: {{ t.layout_width }}rem"
|
||||
data-start="{{ t.utc_start_time.isoformat }}"
|
||||
data-end="{{ t.utc_end_time.isoformat }}">
|
||||
<span>
|
||||
{{ t.time|date:"G:i" }} - {{ t.end_time|date:"G:i" }}
|
||||
<i class="fa fa-exchange swap-timeslot-col"
|
||||
data-origin-label="{{ day|date:"l, N j" }}, {{ t.time|date:"G:i" }}-{{ t.end_time|date:"G:i" }}"
|
||||
data-start="{{ t.utc_start_time.isoformat }}"
|
||||
data-timeslot-pk="{{ t.pk }}"></i>
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% for room_data in rgroup %}{% with room_data.room as room %}
|
||||
<div class="timeslots" data-roomcapacity="{{ room.capacity }}">
|
||||
<div class="timeslots"
|
||||
data-room-id="{{ room.pk }}"
|
||||
data-roomcapacity="{{ room.capacity }}">
|
||||
{% for t in room_data.timeslots %}
|
||||
<div id="timeslot{{ t.pk }}"
|
||||
class="timeslot {{ t.start_end_group }}"
|
||||
|
@ -204,10 +208,26 @@
|
|||
{% endfor %}
|
||||
</span>
|
||||
|
||||
{% if session_purposes|length > 1 %}
|
||||
<button id="session-toggle-modal-open" class="btn btn-default" data-toggle="modal" data-target="#session-toggles-modal"><input type="checkbox" checked="checked" disabled> Sessions</button>
|
||||
{% endif %}
|
||||
<button id="timeslot-toggle-modal-open" class="btn btn-default" data-toggle="modal" data-target="#timeslot-group-toggles-modal"><input type="checkbox" checked="checked" disabled> Timeslots</button>
|
||||
<button id="session-toggle-modal-open"
|
||||
class="btn btn-default"
|
||||
data-toggle="modal"
|
||||
data-target="#session-toggles-modal">
|
||||
<span class="fa fa-check-square"></span> Session Purposes
|
||||
</button>
|
||||
|
||||
<button id="timeslot-toggle-modal-open"
|
||||
class="btn btn-default"
|
||||
data-toggle="modal"
|
||||
data-target="#timeslot-group-toggles-modal">
|
||||
<span class="fa fa-check-square"></span> Timeslots
|
||||
</button>
|
||||
|
||||
<button id="timeslot-type-modal-open"
|
||||
class="btn btn-default"
|
||||
data-toggle="modal"
|
||||
data-target="#timeslot-type-toggles-modal">
|
||||
<span class="fa fa-check-square"></span> Timeslot Types
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -225,39 +245,65 @@
|
|||
<h4 class="modal-title" id="timeslot-group-toggles-modal-title">Displayed timeslots</h4>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="timeslot-group-buttons">
|
||||
<button type="button" class="btn btn-default select-all">Select all times</button>
|
||||
<button type="button" class="btn btn-default clear-all">Clear times</button>
|
||||
</div>
|
||||
<div class="individual-timeslots">
|
||||
<div class="modal-body">
|
||||
<div class="individual-timeslots">
|
||||
|
||||
{% for day, t_groups in timeslot_groups %}
|
||||
<div>
|
||||
<div><strong>{{ day|date:"M. d" }}</strong></div>
|
||||
{% for start, end, key in t_groups %}
|
||||
<label><input type="checkbox" name="timeslot-group" value="{{ key }}" checked="checked"> {{ start|date:"H:i" }} - {{ end|date:"H:i" }}</label>
|
||||
{% endfor %}
|
||||
{% for day, t_groups in timeslot_groups %}
|
||||
<div>
|
||||
<div><strong>{{ day|date:"M. d" }}</strong></div>
|
||||
{% for start, end, key in t_groups %}
|
||||
<label><input type="checkbox" name="timeslot-group" value="{{ key }}" checked="checked"> {{ start|date:"H:i" }} - {{ end|date:"H:i" }}</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="timeslot-group-buttons">
|
||||
<button type="button" class="btn btn-default select-all">Select all</button>
|
||||
<button type="button" class="btn btn-default clear-all">Clear all</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="timeslots-by-type timeslot-type-toggles">
|
||||
Type:
|
||||
{% for type in timeslot_types %}
|
||||
<label class="timeslot-type-{{ type.slug }}"><input type="checkbox" checked value="{{ type.slug }}"> {{ type }}</label>
|
||||
{% endfor %}
|
||||
<button type="button" class="btn btn-default select-all">Select all types</button>
|
||||
<button type="button" class="btn btn-default clear-all">Clear types</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="timeslot-type-toggles-modal" class="modal" role="dialog" aria-labelledby="timeslot-type-toggles-modal-title">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span class="sr-only">Close</span>
|
||||
</button>
|
||||
<h4 class="modal-title" id="timeslot-type-toggles-modal-title">Displayed types</h4>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="timeslots-by-type timeslot-type-toggles">
|
||||
{% for type in timeslot_types %}
|
||||
<div class="timeslot-type-toggle">
|
||||
<label class="timeslot-type-{{ type.slug }}">
|
||||
<input type="checkbox"
|
||||
value="{{ type.slug }}" {% if type.slug == 'regular' %}checked{% endif %}>
|
||||
{{ type }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<button type="button" class="btn btn-default select-all">Select all</button>
|
||||
<button type="button" class="btn btn-default clear-all">Clear all</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="session-toggles-modal" class="modal" role="dialog" aria-labelledby="session-toggles-modal-title">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
|
@ -272,7 +318,7 @@
|
|||
<div class="modal-body">
|
||||
<div class="session-purpose-toggles">
|
||||
{% for purpose in session_purposes %}
|
||||
<div>
|
||||
<div class="session-purpose-toggle">
|
||||
<label class="purpose-{{ purpose.slug }}"><input type="checkbox" checked value="{% firstof purpose.slug 'none' %}"> {{ purpose }}</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
|
|
@ -41,6 +41,8 @@
|
|||
{% else %} {# secr app room view requires a schedule - show something for now (should try to avoid this possibility) #}
|
||||
<span title="Must create meeting schedule to edit rooms">Edit rooms</span>
|
||||
{% endif %}
|
||||
·
|
||||
<a href="{% url "ietf.meeting.views.list_schedules" num=meeting.number %}">Agenda list</a>
|
||||
</p>
|
||||
|
||||
<p> IETF {{ meeting.number }} Meeting Agenda: Timeslots and Room Availability</p>
|
||||
|
|
Loading…
Reference in a new issue