From 1d12391c5c8d051950607277f833e86f7015600a Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 15 Jun 2021 13:46:44 +0000 Subject: [PATCH] Indicate session and timeslot conflicts more prominently in schedule editor. Fixes #3221. Commit ready for merge. - Legacy-Id: 19133 --- ietf/meeting/tests_js.py | 24 +++++- ietf/static/ietf/css/ietf.css | 14 +++- ietf/static/ietf/js/edit-meeting-schedule.js | 80 ++++++++++++++++---- 3 files changed, 102 insertions(+), 16 deletions(-) diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index b9af23065..7d4dcc312 100644 --- a/ietf/meeting/tests_js.py +++ b/ietf/meeting/tests_js.py @@ -171,10 +171,31 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase): s1_element = self.driver.find_element_by_css_selector('#session{}'.format(s1.pk)) s1_element.click() - # violated due to constraints + # violated due to constraints - both the timeslot and its timeslot label self.assertTrue(self.driver.find_elements_by_css_selector('#timeslot{}.would-violate-hint'.format(slot1.pk))) + # Find the timeslot label for slot1 - it's the first timeslot in the first room group + slot1_roomgroup_elt = self.driver.find_element_by_css_selector( + '.day-flow .day:first-child .room-group:nth-child(2)' # count from 2 - first-child is the day label + ) + self.assertTrue( + slot1_roomgroup_elt.find_elements_by_css_selector( + '.time-header > .time-label.would-violate-hint:first-child' + ), + 'Timeslot header label should show a would-violate hint for a constraint violation' + ) + # violated due to missing capacity self.assertTrue(self.driver.find_elements_by_css_selector('#timeslot{}.would-violate-hint'.format(slot3.pk))) + # Find the timeslot label for slot3 - it's the second timeslot in the second room group + slot3_roomgroup_elt = self.driver.find_element_by_css_selector( + '.day-flow .day:first-child .room-group:nth-child(3)' # count from 2 - first-child is the day label + ) + self.assertFalse( + slot3_roomgroup_elt.find_elements_by_css_selector( + '.time-header > .time-label.would-violate-hint:nth-child(2)' + ), + 'Timeslot header label should not show a would-violate hint for room capacity violation' + ) # reschedule self.driver.execute_script("jQuery('#session{}').simulateDragDrop({{dropTarget: '#timeslot{} .drop-target'}});".format(s2.pk, slot2.pk)) @@ -192,6 +213,7 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase): # constraint hints s1_element.click() + self.assertIn('would-violate-hint', s2_element.get_attribute('class')) constraint_element = s2_element.find_element_by_css_selector(".constraints span[data-sessions=\"{}\"].would-violate-hint".format(s1.pk)) self.assertTrue(constraint_element.is_displayed()) diff --git a/ietf/static/ietf/css/ietf.css b/ietf/static/ietf/css/ietf.css index 21a9f5b65..f66e600f4 100644 --- a/ietf/static/ietf/css/ietf.css +++ b/ietf/static/ietf/css/ietf.css @@ -1080,6 +1080,11 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container { align-items: center; } +.edit-meeting-schedule .edit-grid .time-header .time-label.would-violate-hint { + background-color: #ffe0e0; + outline: #ffe0e0 solid 0.4em; +} + .edit-meeting-schedule .edit-grid .time-header .time-label span { display: inline-block; width: 100%; @@ -1124,11 +1129,12 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container { } .edit-meeting-schedule .edit-grid .timeslot.overfull { - border-right: 2px dashed #fff; /* cut-off illusion */ + border-right: 0.3em dashed #f55000; /* cut-off illusion */ } .edit-meeting-schedule .edit-grid .timeslot.would-violate-hint { background-color: #ffe0e0; + outline: #ffe0e0 solid 0.4em; } .edit-meeting-schedule .constraints .encircled, @@ -1177,6 +1183,11 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container { background-color: #f3f3f3; } +.edit-meeting-schedule .session.would-violate-hint { + outline: 0.3em solid #F55000; + z-index: 1; /* raise up so the outline is not overdrawn */ +} + .edit-meeting-schedule .session.highlight .session-label { font-weight: bold; } @@ -1259,6 +1270,7 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container { margin-bottom: 2em; background-color: #fff; opacity: 0.95; + z-index: 5; /* raise above edit-grid items */ } .edit-meeting-schedule .scheduling-panel .unassigned-container { diff --git a/ietf/static/ietf/js/edit-meeting-schedule.js b/ietf/static/ietf/js/edit-meeting-schedule.js index 65bf51214..9adf8a032 100644 --- a/ietf/static/ietf/js/edit-meeting-schedule.js +++ b/ietf/static/ietf/js/edit-meeting-schedule.js @@ -10,6 +10,7 @@ jQuery(document).ready(function () { let sessions = content.find(".session").not(".readonly"); let timeslots = content.find(".timeslot"); + let timeslotLabels = content.find(".time-label"); let days = content.find(".day-flow .day"); // hack to work around lack of position sticky support in old browsers, see https://caniuse.com/#feat=css-sticky @@ -68,25 +69,74 @@ jQuery(document).ready(function () { } } + /** + * Mark or unmark a session that conflicts with the selected session + * + * @param constraintElt The element corresponding to the specific constraint + * @param wouldViolate True to mark or false to unmark + */ + function setSessionWouldViolate(constraintElt, wouldViolate) { + constraintElt = jQuery(constraintElt); + let constraintDiv = constraintElt.closest('div.session'); // find enclosing session div + constraintDiv.toggleClass('would-violate-hint', wouldViolate); // mark the session container + constraintElt.toggleClass('would-violate-hint', wouldViolate); // and the specific constraint + } + + /** + * Mark or unmark a timeslot that conflicts with the selected session + * + * If wholeInterval is true, marks the entire column in addition to the timeslot. + * This currently works by setting the class for the timeslot and the time-label + * in its column. Because this is called for every timeslot in the interval, the + * overall effect is to highlight the entire column. + * + * @param timeslotElt Timeslot element to be marked/unmarked + * @param wouldViolate True to mark or false to unmark + * @param wholeInterval Should the entire time interval be flagged or just the timeslot? + */ + function setTimeslotWouldViolate(timeslotElt, wouldViolate, wholeInterval) { + timeslotElt = jQuery(timeslotElt); + timeslotElt.toggleClass('would-violate-hint', wouldViolate); + if (wholeInterval) { + let index = timeslotElt.index(); // position of this timeslot relative to its container + let label = timeslotElt + .closest('div.room-group') + .find('div.time-header .time-label') + .get(index); // get time-label corresponding to this timeslot + jQuery(label).toggleClass('would-violate-hint', wouldViolate); + } + } + + /** + * Remove all would-violate-hint classes on timeslots + */ + function resetTimeslotsWouldViolate() { + timeslots.removeClass("would-violate-hint"); + timeslotLabels.removeClass("would-violate-hint"); + } + function showConstraintHints(selectedSession) { let sessionId = selectedSession ? selectedSession.id.slice("session".length) : null; // hints on the sessions sessions.find(".constraints > span").each(function () { - if (!sessionId) { - jQuery(this).removeClass("would-violate-hint"); - return; + let wouldViolate = false; + let applyChange = true; + if (sessionId) { + let sessionIds = this.dataset.sessions; + if (!sessionIds) { + applyChange = False; + } else { + wouldViolate = sessionIds.split(",").indexOf(sessionId) !== -1; + } } - let sessionIds = this.dataset.sessions; - if (!sessionIds) - return; - - let wouldViolate = sessionIds.split(",").indexOf(sessionId) != -1; - jQuery(this).toggleClass("would-violate-hint", wouldViolate); + if (applyChange) { + setSessionWouldViolate(this, wouldViolate); + } }); // hints on timeslots - timeslots.removeClass("would-violate-hint"); + resetTimeslotsWouldViolate(); if (selectedSession) { let intervals = []; timeslots.filter(":has(.session .constraints > span.would-violate-hint)").each(function () { @@ -94,15 +144,17 @@ jQuery(document).ready(function () { }); let overlappingTimeslots = findTimeslotsOverlapping(intervals); - for (let i = 0; i < overlappingTimeslots.length; ++i) - overlappingTimeslots[i].addClass("would-violate-hint"); + for (let i = 0; i < overlappingTimeslots.length; ++i) { + setTimeslotWouldViolate(overlappingTimeslots[i], true, true); + } // check room sizes let attendees = +selectedSession.dataset.attendees; if (attendees) { timeslots.not(".would-violate-hint").each(function () { - if (attendees > +jQuery(this).closest(".timeslots").data("roomcapacity")) - jQuery(this).addClass("would-violate-hint"); + if (attendees > +jQuery(this).closest(".timeslots").data("roomcapacity")) { + setTimeslotWouldViolate(this, true, false); + } }); } }