Skip to content

Commit 5faccf5

Browse files
committed
Put the time slots in the new meeting schedule editor on a time scale
per day. This opens the possibility of having the slots not match across the different rooms. Add colors to session in areas/IRTF. Add support for toggling display of areas/IRTF. Show graphical hint when a time slot is overfull. - Legacy-Id: 17415
1 parent 393ee64 commit 5faccf5

6 files changed

Lines changed: 291 additions & 96 deletions

File tree

ietf/meeting/tests_views.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -927,12 +927,11 @@ def test_edit_meeting_schedule(self):
927927
q = PyQuery(r.content)
928928

929929
room = Room.objects.get(meeting=meeting, session_types='regular')
930-
self.assertTrue(q("th:contains(\"{}\")".format(room.name)))
931-
self.assertTrue(q("th:contains(\"{}\")".format(room.capacity)))
930+
self.assertTrue(q("h5:contains(\"{}\")".format(room.name)))
931+
self.assertTrue(q("h5:contains(\"{}\")".format(room.capacity)))
932932

933933
timeslots = TimeSlot.objects.filter(meeting=meeting, type='regular')
934-
self.assertTrue(q("td:contains(\"{}\")".format(timeslots[0].time.strftime("%H:%M"))))
935-
self.assertTrue(q("td.timeslot[data-timeslot=\"{}\"]".format(timeslots[0].pk)))
934+
self.assertTrue(q("div.timeslot[data-timeslot=\"{}\"]".format(timeslots[0].pk)))
936935

937936
sessions = Session.objects.filter(meeting=meeting, type='regular')
938937
for s in sessions:

ietf/meeting/views.py

Lines changed: 103 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io
1212
import itertools
1313
import json
14+
import math
1415
import os
1516
import pytz
1617
import re
@@ -456,14 +457,8 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
456457

457458
assignments = get_all_assignments_from_schedule(schedule)
458459

459-
# FIXME
460-
#areas = get_areas()
461-
#ads = find_ads_for_meeting(meeting)
462-
463-
css_ems_per_hour = 1.5
464-
465460
rooms = meeting.room_set.filter(session_types__slug='regular').distinct().order_by("capacity")
466-
timeslots_qs = meeting.timeslot_set.filter(type='regular').prefetch_related('type', 'sessions').order_by('time', 'location', 'name')
461+
timeslots_qs = meeting.timeslot_set.filter(type='regular').prefetch_related('type', 'sessions').order_by('location', 'time', 'name')
467462

468463
sessions = add_event_info_to_session_qs(
469464
Session.objects.filter(
@@ -511,25 +506,106 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
511506
for a in assignments:
512507
assignments_by_session[a.session_id].append(a)
513508

514-
# prepare timeslot matrix
515-
times = {} # start time -> end time
516-
timeslots = {}
517-
timeslots_by_pk = {}
518-
for ts in timeslots_qs:
519-
ts_end_time = ts.end_time()
520-
if ts_end_time < times.get(ts.time, datetime.datetime.max):
521-
times[ts.time] = ts_end_time
509+
# Prepare timeslot layout. We arrange time slots in columns per
510+
# room where everything inside is grouped by day. Things inside
511+
# the days are then layouted proportionally to the actual time of
512+
# day, to ensure that everything lines up, even if the time slots
513+
# are not the same in the different rooms.
522514

523-
timeslots[(ts.location_id, ts.time)] = ts
524-
timeslots_by_pk[ts.pk] = ts
525-
ts.session_assignments = []
515+
def timedelta_to_css_ems(timedelta):
516+
css_ems_per_hour = 1.8
517+
return timedelta.seconds / 60.0 / 60.0 * css_ems_per_hour
518+
519+
# time labels column
520+
timeslots_by_day = defaultdict(list)
521+
for t in timeslots_qs:
522+
timeslots_by_day[t.time.date()].append(t)
523+
524+
day_min_max = []
525+
for day, timeslots in sorted(timeslots_by_day.iteritems()):
526+
day_min_max.append((day, min(t.time for t in timeslots), max(t.end_time() for t in timeslots)))
527+
528+
time_labels = []
529+
for day, day_min_time, day_max_time in day_min_max:
530+
day_labels = []
531+
532+
hourly_delta = 2
533+
534+
first_hour = int(math.ceil((day_min_time.hour + day_min_time.minute / 60.0) / hourly_delta) * hourly_delta)
535+
t = day_min_time.replace(hour=first_hour, minute=0, second=0, microsecond=0)
536+
537+
last_hour = int(math.floor((day_max_time.hour + day_max_time.minute / 60.0) / hourly_delta) * hourly_delta)
538+
end = day_max_time.replace(hour=last_hour, minute=0, second=0, microsecond=0)
526539

527-
timeslot_matrix = [
528-
(start_time, end_time, (end_time - start_time).seconds / 60.0 / 60.0 * css_ems_per_hour, [(r, timeslots.get((r.pk, start_time))) for r in rooms])
529-
for start_time, end_time in sorted(times.items())
530-
]
540+
while t <= end:
541+
day_labels.append((t, 'top', timedelta_to_css_ems(t - day_min_time), 'left'))
542+
t += datetime.timedelta(seconds=hourly_delta * 60 * 60)
543+
544+
if not day_labels:
545+
day_labels.append((day_min_time, 'top', 0, 'left'))
546+
547+
time_labels.append({
548+
'day': day,
549+
'height': timedelta_to_css_ems(day_max_time - day_min_time),
550+
'labels': day_labels,
551+
})
552+
553+
# room columns
554+
timeslots_by_room_and_day = defaultdict(list)
555+
for t in timeslots_qs:
556+
timeslots_by_room_and_day[(t.location_id, t.time.date())].append(t)
557+
558+
room_columns = []
559+
for r in rooms:
560+
room_days = []
561+
562+
for day, day_min_time, day_max_time in day_min_max:
563+
day_timeslots = []
564+
for t in timeslots_by_room_and_day.get((r.pk, day), []):
565+
day_timeslots.append({
566+
'timeslot': t,
567+
'offset': timedelta_to_css_ems(t.time - day_min_time),
568+
'height': timedelta_to_css_ems(t.end_time() - t.time),
569+
})
570+
571+
room_days.append({
572+
'day': day,
573+
'timeslots': day_timeslots,
574+
'height': timedelta_to_css_ems(day_max_time - day_min_time),
575+
})
576+
577+
if any(d['timeslots'] for d in room_days):
578+
room_columns.append({
579+
'room': r,
580+
'days': room_days,
581+
})
531582

532583
# prepare sessions
584+
for ts in timeslots_qs:
585+
ts.session_assignments = []
586+
timeslots_by_pk = {ts.pk: ts for ts in timeslots_qs}
587+
588+
def cubehelix(i, total, hue=1.2, start_angle=0.5):
589+
# https://arxiv.org/pdf/1108.5083.pdf
590+
rotations = total // 4
591+
x = float(i + 1) / (total + 1)
592+
phi = 2 * math.pi * (start_angle / 3 + rotations * x)
593+
a = hue * x * (1 - x) / 2.0
594+
595+
return (
596+
max(0, min(x + a * (-0.14861 * math.cos(phi) + 1.78277 * math.sin(phi)), 1)),
597+
max(0, min(x + a * (-0.29227 * math.cos(phi) + -0.90649 * math.sin(phi)), 1)),
598+
max(0, min(x + a * (1.97294 * math.cos(phi)), 1)),
599+
)
600+
601+
session_parents = sorted(set(
602+
s.group.parent for s in sessions
603+
if s.group and s.group.parent and s.group.parent.type_id == 'area' or s.group.parent.acronym == 'irtf'
604+
), key=lambda p: p.acronym)
605+
for i, p in enumerate(session_parents):
606+
rgb_color = cubehelix(i, len(session_parents))
607+
p.scheduling_color = "#" + "".join(chr(int(round(x * 255))).encode('hex') for x in rgb_color)
608+
533609
unassigned_sessions = []
534610
session_data = []
535611
for s in sessions:
@@ -573,7 +649,8 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
573649
d['comments'] = s.comments
574650

575651
s.requested_duration_in_hours = s.requested_duration.seconds / 60.0 / 60.0
576-
s.scheduling_height = s.requested_duration_in_hours * css_ems_per_hour
652+
s.layout_height = timedelta_to_css_ems(s.requested_duration)
653+
s.parent_acronym = s.group.parent.acronym if s.group and s.group.parent else ""
577654

578655
scheduled = False
579656

@@ -602,11 +679,11 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
602679
'schedule': schedule,
603680
'can_edit': can_edit,
604681
'schedule_data': json.dumps(schedule_data, indent=2),
682+
'time_labels': time_labels,
605683
'rooms': rooms,
606-
'timeslot_matrix': timeslot_matrix,
684+
'room_columns': room_columns,
607685
'unassigned_sessions': unassigned_sessions,
608-
'timeslot_width': (100.0 - 10) / len(rooms),
609-
#'areas': areas,
686+
'session_parents': session_parents,
610687
'hide_menu': True,
611688
})
612689

ietf/static/ietf/css/ietf.css

Lines changed: 95 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -990,57 +990,83 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container {
990990
padding-bottom: 10em; /* ensure there's room for the scheduling panel */
991991
}
992992

993-
.edit-meeting-schedule .session {
994-
background-color: #fff;
995-
padding: 0 0.4em;
996-
margin: 0.2em 0.4em;
997-
border-radius: 0.3em;
998-
text-align: center;
993+
.edit-meeting-schedule .edit-grid {
994+
display: flex;
999995
}
1000996

1001-
.edit-meeting-schedule .session[draggable] {
1002-
cursor: grabbing;
997+
.edit-meeting-schedule .schedule-column h5 {
998+
text-align: center;
999+
margin: 0;
1000+
white-space: nowrap;
10031001
}
10041002

1005-
.edit-meeting-schedule .session.dragging {
1006-
opacity: 0.3;
1007-
transition: opacity 0.4s;
1003+
.edit-meeting-schedule .schedule-column .day {
1004+
position: relative;
1005+
margin-bottom: 3em;
10081006
}
10091007

1010-
.edit-meeting-schedule .session i.fa-comment-o {
1011-
width: 0; /* prevent icon from participating in text centering */
1008+
.edit-meeting-schedule .schedule-column .day > div {
1009+
position: absolute;
10121010
}
10131011

1014-
.edit-meeting-schedule .edit-meeting-grid th {
1015-
text-align: center;
1012+
.edit-meeting-schedule .time-labels-column > div {
1013+
min-width: 5em;
1014+
padding-right: 0.5em;
10161015
}
10171016

1018-
.edit-meeting-schedule .edit-meeting-grid td.day {
1019-
padding-top: 1em;
1017+
.edit-meeting-schedule .time-labels-column .time-label {
1018+
width: 100%;
10201019
}
10211020

1022-
.edit-meeting-schedule .edit-meeting-grid td.timeslot {
1023-
border: 2px solid #fff;
1024-
background-color: #f6f6f6;
1025-
padding: 1px;
1021+
.edit-meeting-schedule .time-labels-column .time-label.top-aligned {
1022+
border-top: 1px solid #ccc;
10261023
}
10271024

1028-
.edit-meeting-schedule .edit-meeting-grid td.timeslot.disabled {
1025+
.edit-meeting-schedule .time-labels-column .time-label.text-left span {
10291026
background-color: #fff;
1027+
padding-right: 0.2em;
1028+
}
1029+
1030+
.edit-meeting-schedule .time-labels-column .time-label.bottom-aligned {
1031+
border-bottom: 1px solid #ccc;
1032+
}
1033+
1034+
.edit-meeting-schedule .room-column {
1035+
flex-grow: 1;
1036+
}
1037+
1038+
.edit-meeting-schedule .room-column .day-label {
1039+
visibility: hidden; /* it's there to take up the space, but not shown */
10301040
}
10311041

1032-
.edit-meeting-schedule .edit-meeting-grid td.timeslot.dropping {
1042+
.edit-meeting-schedule .timeslot {
1043+
display: flex;
1044+
flex-direction: column;
1045+
background-color: #f6f6f6;
1046+
width: 100%;
1047+
border-right: 0.2em solid #fff;
1048+
border-left: 0.2em solid #fff;
1049+
overflow: hidden;
1050+
}
1051+
1052+
.edit-meeting-schedule .timeslot.dropping {
10331053
background-color: #f0f0f0;
10341054
transition: background-color 0.2s;
10351055
}
10361056

1057+
.edit-meeting-schedule .timeslot.overfull {
1058+
border-bottom: 2px dashed #ddd;
1059+
}
1060+
10371061
.edit-meeting-schedule .scheduling-panel {
1038-
position: fixed;
1062+
position: fixed; /* backwards compatibility */
1063+
position: sticky;
10391064
bottom: 0;
10401065
left: 0;
10411066
margin: 0;
10421067
padding: 0 1em;
10431068
width: 100%;
1069+
border-top: 0.2em solid #eee;
10441070
background-color: #fff;
10451071
opacity: 0.95;
10461072
z-index: 1;
@@ -1056,7 +1082,52 @@ a.fc-event, .fc-event, .fc-content, .fc-title, .fc-event-container {
10561082
transition: background-color 0.2s;
10571083
}
10581084

1085+
.edit-meeting-schedule .session-parent-toggles {
1086+
margin-top: 1em;
1087+
}
1088+
1089+
.edit-meeting-schedule .session-parent-toggles label {
1090+
font-weight: normal;
1091+
margin-right: 1em;
1092+
padding: 0 1em;
1093+
border: 0.1em solid #eee;
1094+
cursor: pointer;
1095+
}
1096+
1097+
/* sessions */
1098+
.edit-meeting-schedule .session {
1099+
background-color: #fff;
1100+
padding: 0 0.2em;
1101+
padding-left: 0.5em;
1102+
border: 0.2em solid #f6f6f6; /* this compensates for sessions being relatively smaller than they should */
1103+
border-radius: 0.4em;
1104+
text-align: center;
1105+
overflow: hidden;
1106+
}
1107+
1108+
.edit-meeting-schedule .session[draggable] {
1109+
cursor: grabbing;
1110+
}
1111+
1112+
.edit-meeting-schedule .session.dragging {
1113+
opacity: 0.3;
1114+
transition: opacity 0.4s;
1115+
}
1116+
1117+
.edit-meeting-schedule .session .color {
1118+
display: inline-block;
1119+
width: 1em;
1120+
height: 1em;
1121+
vertical-align: middle;
1122+
}
1123+
1124+
.edit-meeting-schedule .session i.fa-comment-o {
1125+
width: 0; /* prevent icon from participating in text centering */
1126+
}
1127+
10591128
.edit-meeting-schedule .unassigned-sessions .session {
1129+
vertical-align: top;
10601130
display: inline-block;
10611131
min-width: 6em;
1132+
margin-right: 0.4em;
10621133
}

0 commit comments

Comments
 (0)