From 6c48575042ea2fa3713b8b81557470c5984b1dd5 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Thu, 9 Apr 2020 18:16:56 +0000 Subject: [PATCH] Swap the axes in the meeting schedule editor and rework it to allow flowing the days. Add JS workaround for missing position sticky support, instead of the CSS workaround which added an annoying padding for everyone. - Legacy-Id: 17616 --- ietf/meeting/views.py | 86 +++--- ietf/static/ietf/css/ietf.css | 255 +++++++++--------- ietf/static/ietf/js/edit-meeting-schedule.js | 8 +- .../meeting/edit_meeting_schedule.html | 66 +++-- .../edit_meeting_schedule_session.html | 32 +-- 5 files changed, 234 insertions(+), 213 deletions(-) diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 9f8e066dd..8d94aa569 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -508,17 +508,14 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None): for a in assignments: assignments_by_session[a.session_id].append(a) - # Prepare timeslot layout. We arrange time slots in columns per - # room where everything inside is grouped by day. Things inside - # the days are then layouted proportionally to the actual time of - # day, to ensure that everything lines up, even if the time slots - # are not the same in the different rooms. + # Prepare timeslot layout, making a timeline per day scaled in + # browser em units to ensure that everything lines up even if the + # timeslots are not the same in the different rooms def timedelta_to_css_ems(timedelta): - css_ems_per_hour = 1.8 + css_ems_per_hour = 5 return timedelta.seconds / 60.0 / 60.0 * css_ems_per_hour - # time labels column timeslots_by_day = defaultdict(list) for t in timeslots_qs: timeslots_by_day[t.time.date()].append(t) @@ -526,10 +523,19 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None): day_min_max = [] for day, timeslots in sorted(timeslots_by_day.iteritems()): day_min_max.append((day, min(t.time for t in timeslots), max(t.end_time() for t in timeslots))) - - time_labels = [] + + timeslots_by_room_and_day = defaultdict(list) + room_has_timeslots = set() + for t in timeslots_qs: + room_has_timeslots.add(t.location_id) + timeslots_by_room_and_day[(t.location_id, t.time.date())].append(t) + + days = [] for day, day_min_time, day_max_time in day_min_max: day_labels = [] + day_width = timedelta_to_css_ems(day_max_time - day_min_time) + + label_width = 4 # em hourly_delta = 2 @@ -540,47 +546,42 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None): end = day_max_time.replace(hour=last_hour, minute=0, second=0, microsecond=0) while t <= end: - day_labels.append((t, 'top', timedelta_to_css_ems(t - day_min_time), 'left')) + left_offset = timedelta_to_css_ems(t - day_min_time) + right_offset = day_width - left_offset + if right_offset > label_width: + # there's room for the label + day_labels.append((t, 'left', left_offset)) + else: + day_labels.append((t, 'right', right_offset)) + t += datetime.timedelta(seconds=hourly_delta * 60 * 60) if not day_labels: - day_labels.append((day_min_time, 'top', 0, 'left')) + day_labels.append((day_min_time, 'left', 0)) - time_labels.append({ - 'day': day, - 'height': timedelta_to_css_ems(day_max_time - day_min_time), - 'labels': day_labels, - }) + room_timeslots = [] + for r in rooms: + if r.pk not in room_has_timeslots: + continue - # room columns - timeslots_by_room_and_day = defaultdict(list) - for t in timeslots_qs: - timeslots_by_room_and_day[(t.location_id, t.time.date())].append(t) - - room_columns = [] - for r in rooms: - room_days = [] - - for day, day_min_time, day_max_time in day_min_max: - day_timeslots = [] + timeslots = [] for t in timeslots_by_room_and_day.get((r.pk, day), []): - day_timeslots.append({ + timeslots.append({ 'timeslot': t, 'offset': timedelta_to_css_ems(t.time - day_min_time), - 'height': timedelta_to_css_ems(t.end_time() - t.time), + 'width': timedelta_to_css_ems(t.end_time() - t.time), }) - room_days.append({ - 'day': day, - 'timeslots': day_timeslots, - 'height': timedelta_to_css_ems(day_max_time - day_min_time), - }) + room_timeslots.append((r, timeslots)) - if any(d['timeslots'] for d in room_days): - room_columns.append({ - 'room': r, - 'days': room_days, - }) + days.append({ + 'day': day, + 'width': day_width, + 'time_labels': day_labels, + 'room_timeslots': room_timeslots, + }) + + room_labels = [[r for r in rooms if r.pk in room_has_timeslots] for i in range(len(days))] # prepare sessions for ts in timeslots_qs: @@ -701,7 +702,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None): s.requested_duration_in_hours = s.requested_duration.seconds / 60.0 / 60.0 session_layout_margin = 0.2 - s.layout_height = timedelta_to_css_ems(s.requested_duration) - 2 * session_layout_margin + s.layout_width = timedelta_to_css_ems(s.requested_duration) - 2 * session_layout_margin s.parent_acronym = s.group.parent.acronym if s.group and s.group.parent else "" s.historic_group_ad_name = ad_names.get(s.group_id) @@ -744,9 +745,8 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None): 'schedule': schedule, 'can_edit': can_edit, 'js_data': json.dumps(js_data, indent=2), - 'time_labels': time_labels, - 'rooms': rooms, - 'room_columns': room_columns, + 'days': days, + 'room_labels': room_labels, 'unassigned_sessions': unassigned_sessions, 'session_parents': session_parents, 'hide_menu': True, diff --git a/ietf/static/ietf/css/ietf.css b/ietf/static/ietf/css/ietf.css index d758e79d5..aed0d09d2 100644 --- a/ietf/static/ietf/css/ietf.css +++ b/ietf/static/ietf/css/ietf.css @@ -974,7 +974,7 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container { .fc-button { /* same as button-primary */ - background-image: linear-gradient(rgb(107, 91, 173) 0px, rgb(80, 68, 135) 100%) + background-image: linear-gradient(rgb(107, 91, 173) 0px, rgb(80, 68, 135) 100%); } /* === Edit Milestones============================================= */ @@ -987,62 +987,77 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container { /* === Edit Meeting Schedule ====================================== */ .edit-meeting-schedule .edit-grid { + position: relative; display: flex; } -.edit-meeting-schedule .schedule-column .room-name { - height: 2em; - font-weight: bold; - text-align: center; - margin: 0; - white-space: nowrap; +.edit-meeting-schedule .edit-grid .room-label-column { + /* make sure we cut this column off - the time slots will determine + how much of it is shown */ + position: absolute; + top: 0; + bottom: 0; + left: 0; + overflow: hidden; + width: 8em; } -.edit-meeting-schedule .schedule-column .day-label { - height: 2.5em; - max-width: 5em; /* let it stick out and overlap the other columns */ - white-space: nowrap; - font-style: italic; - margin-top: 1em; -} - -.edit-meeting-schedule .schedule-column > .day { - position: relative; +.edit-meeting-schedule .edit-grid .day { + margin-right: 2.5em; margin-bottom: 2em; } -.edit-meeting-schedule .schedule-column > .day > div { +.edit-meeting-schedule .edit-grid .day-label { + height: 3em; + border-bottom: 2px solid transparent; +} + +.edit-meeting-schedule .edit-grid .day-flow { + margin-left: 8em; + display: flex; + flex-wrap: wrap; + justify-content: space-between; +} + +.edit-meeting-schedule .edit-grid .day-flow .day-label { + border-bottom: 2px solid #eee; +} + +.edit-meeting-schedule .edit-grid .timeline { + position: relative; + height: 1.6em; +} + +.edit-meeting-schedule .edit-grid .timeline > div { position: absolute; } -.edit-meeting-schedule .time-labels-column > div { - min-width: 5em; - padding-right: 0.5em; +.edit-meeting-schedule .edit-grid .timeline.timeslots { + height: 3.3em; } -.edit-meeting-schedule .time-labels-column .time-label.top-aligned { - border-top: 1px solid #ccc; +.edit-meeting-schedule .edit-grid .timeline .time-label { + font-size: smaller; + border-left: 2px solid #eee; + border-right: 2px solid #eee; + padding: 0 0.2em; + height: 1.3em; } -.edit-meeting-schedule .time-labels-column .time-label.text-left span { - background-color: #fff; - padding-right: 0.2em; +.edit-meeting-schedule .edit-grid .timeline .time-label.text-left { + border-right: none; } -.edit-meeting-schedule .time-labels-column .time-label.bottom-aligned { - border-bottom: 1px solid #ccc; -} - -.edit-meeting-schedule .room-column { - flex-grow: 1; +.edit-meeting-schedule .edit-grid .timeline .time-label.text-right { + border-left: none; } .edit-meeting-schedule .timeslot { display: flex; - flex-direction: column; + flex-direction: row; background-color: #eee; - width: 100%; - border-left: 0.15em solid #fff; + height: 100%; + border-bottom: 0.15em solid #fff; overflow: hidden; } @@ -1052,99 +1067,23 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container { } .edit-meeting-schedule .timeslot.overfull { - border-bottom: 2px dashed #fff; /* cut-off illusion */ -} - -.edit-meeting-schedule { - /* this is backwards-compatible measure - if the browser doesn't - support position: sticky but only position: fixed, we ensure there's room for the scheduling - panel */ - padding-bottom: 5em; -} - -.edit-meeting-schedule .scheduling-panel { - position: fixed; /* backwards compatibility */ - z-index: 1; - - position: sticky; - display: flex; - bottom: 0; - left: 0; - width: 100%; - border-top: 0.2em solid #ccc; - background-color: #fff; - opacity: 0.95; -} - -.edit-meeting-schedule .scheduling-panel .unassigned-container { - flex-grow: 1; -} - -.edit-meeting-schedule .unassigned-sessions { - min-height: 4em; - max-height: 13em; - overflow-y: auto; - display: flex; - flex-wrap: wrap; - background-color: #eee; - margin-top: 0.5em; -} - -.edit-meeting-schedule .unassigned-sessions.dropping { - background-color: #e5e5e5; - transition: background-color 0.2s; -} - -.edit-meeting-schedule .scheduling-panel .preferences { - margin: 0.5em 0; -} - -.edit-meeting-schedule .scheduling-panel .preferences > span { - margin-right: 1em; -} - -.edit-meeting-schedule .sort-unassigned select { - width: auto; - display: inline-block; -} - -.edit-meeting-schedule .session-parent-toggles { - margin-top: 1em; -} - -.edit-meeting-schedule .session-parent-toggles label { - font-weight: normal; - margin-right: 1em; - padding: 0 1em; - border: 0.1em solid #eee; - cursor: pointer; -} - -.edit-meeting-schedule .scheduling-panel .session-info-container { - padding-left: 0.5em; - flex: 0 0 20em; - max-height: 15em; - overflow-y: auto; -} - -.edit-meeting-schedule .scheduling-panel .session-info-container .comments { - font-style: italic; + border-right: 2px dashed #fff; /* cut-off illusion */ } /* sessions */ .edit-meeting-schedule .session { - display: flex; background-color: #fff; - padding: 0 0.2em; - padding-left: 0.5em; margin: 0.2em; + padding-right: 0.2em; + padding-left: 0.5em; + line-height: 1.3em; border-radius: 0.4em; overflow: hidden; cursor: pointer; } .edit-meeting-schedule .session.selected { - border: 1px solid #bbb; + background-color: #fcfcfc; } .edit-meeting-schedule .session.dragging { @@ -1153,13 +1092,8 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container { } .edit-meeting-schedule .timeslot.overfull .session { - border-radius: 0.4em 0.4em 0 0; /* remove bottom rounding to illude to being cut off */ - margin-bottom: 0; -} - -.edit-meeting-schedule .unassigned-sessions .session { - min-width: 6em; - margin-right: 0.3em; + border-radius: 0.4em 0 0 0.4em; /* remove bottom rounding to illude to being cut off */ + margin-right: 0; } .edit-meeting-schedule .session .session-label { @@ -1208,12 +1142,75 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container { display: none; } - -.edit-meeting-schedule .session .comments { - font-size: smaller; - margin-right: 0.1em; -} - .edit-meeting-schedule .session .session-info { display: none; } + +/* scheduling panel */ +.edit-meeting-schedule .scheduling-panel { + position: sticky; + display: flex; + bottom: 0; + left: 0; + width: 100%; + border-top: 0.2em solid #ccc; + background-color: #fff; + opacity: 0.95; +} + +.edit-meeting-schedule .scheduling-panel .unassigned-container { + flex-grow: 1; +} + +.edit-meeting-schedule .unassigned-sessions { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + margin-top: 0.5em; + min-height: 4em; + max-height: 13em; + overflow-y: auto; + background-color: #eee; +} + +.edit-meeting-schedule .unassigned-sessions.dropping { + background-color: #e5e5e5; + transition: background-color 0.2s; +} + +.edit-meeting-schedule .scheduling-panel .preferences { + margin: 0.5em 0; +} + +.edit-meeting-schedule .scheduling-panel .preferences > span { + margin-right: 1em; +} + +.edit-meeting-schedule .sort-unassigned select { + width: auto; + display: inline-block; +} + +.edit-meeting-schedule .session-parent-toggles { + margin-top: 1em; +} + +.edit-meeting-schedule .session-parent-toggles label { + font-weight: normal; + margin-right: 1em; + padding: 0 1em; + border: 0.1em solid #eee; + cursor: pointer; +} + +.edit-meeting-schedule .scheduling-panel .session-info-container { + padding-left: 0.5em; + flex: 0 0 20em; + max-height: 15em; + overflow-y: auto; +} + +.edit-meeting-schedule .scheduling-panel .session-info-container .comments { + font-style: italic; +} + diff --git a/ietf/static/ietf/js/edit-meeting-schedule.js b/ietf/static/ietf/js/edit-meeting-schedule.js index 7a92c17d0..98eea1026 100644 --- a/ietf/static/ietf/js/edit-meeting-schedule.js +++ b/ietf/static/ietf/js/edit-meeting-schedule.js @@ -11,6 +11,12 @@ jQuery(document).ready(function () { let sessions = content.find(".session"); let timeslots = content.find(".timeslot"); + // 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") { + content.find(".scheduling-panel").css("position", "fixed"); + content.css("padding-bottom", "14em"); + } + // selecting function selectSessionElement(element) { if (element) { @@ -220,7 +226,7 @@ jQuery(document).ready(function () { function updateAttendeesViolations() { sessions.each(function () { - let roomCapacity = jQuery(this).closest(".room-column").data("roomcapacity"); + let roomCapacity = jQuery(this).closest(".timeline").data("roomcapacity"); if (roomCapacity && this.dataset.attendees) jQuery(this).toggleClass("too-many-attendees", +this.dataset.attendees > +roomCapacity); }); diff --git a/ietf/templates/meeting/edit_meeting_schedule.html b/ietf/templates/meeting/edit_meeting_schedule.html index df745c431..0008653e6 100644 --- a/ietf/templates/meeting/edit_meeting_schedule.html +++ b/ietf/templates/meeting/edit_meeting_schedule.html @@ -48,43 +48,59 @@

- {# in order for all this to align properly vertically, we have the same structure in all columns #} -
-
+ {# using the same markup in both room labels and the actual days ensures they are aligned #} +
+ {% for labels in room_labels %} +
+
+  
+   +
- {% for d in time_labels %} -
{{ d.day|date:"l, F j, Y" }}
+
-
- {% for t, vertical_alignment, vertical_offset, horizontal_alignment in d.labels %} -
- {{ t|date:"H:i" }} + {% for room in labels %} +
+
+ {{ room.name }}
+ {% if room.capacity %}{{ room.capacity }} {% endif %} +
{% endfor %}
{% endfor %}
- {% for r in room_columns %} -
-
{{ r.room.name }}{% if r.room.capacity %} ({{ r.room.capacity }} {% endif %})
+
+ {% for day in days %} +
+
+ {{ day.day|date:"l" }}
+ {{ day.day|date:"N j, Y" }} +
- {% for d in r.days %} -
{# for spacing purposes #} - -
- {% for t in d.timeslots %} -
- {% for assignment, session in t.timeslot.session_assignments %} - {% include "meeting/edit_meeting_schedule_session.html" %} - {% endfor %} -
+
+ {% for t, left_or_right, offset in day.time_labels %} +
{{ t|date:"H:i" }}
{% endfor %}
- {% endfor %} -
- {% endfor %} + + {% for room, timeslots in day.room_timeslots %} +
+ + {% for t in timeslots %} +
+ {% for assignment, session in t.timeslot.session_assignments %} + {% include "meeting/edit_meeting_schedule_session.html" %} + {% endfor %} +
+ {% endfor %} +
+ {% endfor %} +
+ {% endfor %} +
diff --git a/ietf/templates/meeting/edit_meeting_schedule_session.html b/ietf/templates/meeting/edit_meeting_schedule_session.html index 07f5190a1..d835cad4f 100644 --- a/ietf/templates/meeting/edit_meeting_schedule_session.html +++ b/ietf/templates/meeting/edit_meeting_schedule_session.html @@ -1,25 +1,27 @@ -
+
{{ session.scheduling_label }}
- {% if session.constrained_sessions %} -
- {% for explanation, sessions in session.constrained_sessions %} - {{ explanation }} - {% endfor %} -
- {% endif %} +
+ {% if session.attendees != None %} + {{ session.attendees }} + {% endif %} - {% if session.comments %} -
- {% endif %} - - {% if session.attendees != None %} -
{{ session.attendees }}
- {% endif %} + {% if session.comments %} + + {% endif %} + {% if session.constrained_sessions %} + + {% for explanation, sessions in session.constrained_sessions %} + {{ explanation }} + {% endfor %} + + {% endif %} +
+ {# this is shown elsewhere on the page with JS - we just include it here for convenience #}