From 302f749f9447f21c0e78a99432fc57bc1a8ecd9e Mon Sep 17 00:00:00 2001 From: Nicolas Giard Date: Mon, 25 Jul 2022 20:27:04 +0000 Subject: [PATCH 1/4] fix: handle session dropdown link on mobile view --- client/agenda/AgendaScheduleList.vue | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/client/agenda/AgendaScheduleList.vue b/client/agenda/AgendaScheduleList.vue index fb1d7457590..4338b150d91 100644 --- a/client/agenda/AgendaScheduleList.vue +++ b/client/agenda/AgendaScheduleList.vue @@ -107,11 +107,12 @@ span.badge.is-rescheduled(v-else-if='!isMobile && item.status === `resched`') Rescheduled .agenda-table-cell-links-buttons(v-else-if='agendaStore.viewport < 1200 && item.links && item.links.length > 0') n-dropdown( + v-if='!agendaStore.colorPickerVisible' trigger='click' :options='item.links' key-field='id' :render-icon='renderLinkIcon' - v-if='!agendaStore.colorPickerVisible' + @select='goToSessionLink' ) n-button(size='tiny') i.bi.bi-three-dots @@ -189,13 +190,18 @@ import { NCheckbox, NCheckboxGroup, NDropdown, - NPopover + NPopover, + useMessage } from 'naive-ui' import AgendaDetailsModal from './AgendaDetailsModal.vue' import { useAgendaStore } from './store' +// MESSAGE PROVIDER + +const message = useMessage() + // STORES const agendaStore = useAgendaStore() @@ -491,6 +497,14 @@ function toggleColorPicker () { }) } +function goToSessionLink (lnkKey, lnk) { + if (lnk.href) { + window.location.assign(lnk.href) + } else { + message.error('Missing link for this dropdown item.') + } +} + function showMaterials (eventId) { state.eventDetails = find(agendaStore.scheduleAdjusted, ['id', eventId]) state.showEventDetails = true From 296b4b7f522637b1a738dcd545a4688e94237950 Mon Sep 17 00:00:00 2001 From: Nicolas Giard Date: Mon, 25 Jul 2022 23:49:48 +0000 Subject: [PATCH 2/4] feat: agenda session materials API endpoint --- ietf/api/urls.py | 2 ++ ietf/meeting/tests_views.py | 19 +++++++++++++++++++ ietf/meeting/views.py | 26 ++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/ietf/api/urls.py b/ietf/api/urls.py index 34c1127e548..bc3bfb0190f 100644 --- a/ietf/api/urls.py +++ b/ietf/api/urls.py @@ -29,6 +29,8 @@ url(r'^meeting/session/video/url$', meeting_views.api_set_session_video_url), # Meeting agenda + floorplan data url(r'^meeting/(?P[A-Za-z0-9._+-]+)/agenda-data$', meeting_views.api_get_agenda_data), + # Meeting session materials + url(r'^meeting/session/(?P[A-Za-z0-9._+-]+)/materials$', meeting_views.api_get_session_materials), # Let Meetecho trigger recording imports url(r'^notify/meeting/import_recordings/(?P[a-z0-9-]+)/?$', meeting_views.api_import_recordings), # Let MeetEcho upload bluesheets diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index b7e6e70a984..b5df0809b7a 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -170,6 +170,7 @@ def test_meeting_agenda(self): self.assertEqual(r.status_code, 200) # Agenda API tests + # -> Meeting data r = self.client.get(urlreverse("ietf.meeting.views.api_get_agenda_data", kwargs=dict(num=meeting.number))) self.assertEqual(r.status_code, 200) rjson = json.loads(r.content.decode("utf8")) @@ -193,6 +194,24 @@ def test_meeting_agenda(self): "floors": [] } ) + # -> Session Materials + r = self.client.get(urlreverse("ietf.meeting.views.api_get_session_materials", kwargs=dict(session_id=session.id))) + self.assertEqual(r.status_code, 200) + rjson = json.loads(r.content.decode("utf8")) + minutes = session.minutes() + self.assertJSONEqual( + r.content.decode("utf8"), + { + "url": session.agenda().get_href(), + "slides": rjson.get("slides"), # Just expect the value to exist + "minutes": { + "id": minutes.id, + "title": minutes.title, + "url": minutes.get_href(), + "ext": minutes.file_extension() + } if minutes is not None else None + } + ) r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number,utc='-utc'))) self.assertEqual(r.status_code, 200) diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 3bcee59aaba..937dbcbf90a 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -1639,9 +1639,26 @@ def api_get_agenda_data (request, num=None): "floors": list(map(agenda_extract_floorplan, floors)) }) +def api_get_session_materials (request, session_id=None): + session = get_object_or_404(Session,pk=session_id) + + minutes = session.minutes() + + return JsonResponse({ + "url": session.agenda().get_href(), + "slides": list(map(agenda_extract_slide, session.slides())), + "minutes": { + "id": minutes.id, + "title": minutes.title, + "url": minutes.get_href(), + "ext": minutes.file_extension() + } if minutes is not None else None + }) + def agenda_extract_schedule (item): return { "id": item.id, + "sessionId": item.session.id, "room": item.room_name, "location": { "short": item.timeslot.location.floorplan.short, @@ -1670,6 +1687,7 @@ def agenda_extract_schedule (item): }, "agenda": { "url": item.session.agenda().get_href() + # "slides": item.session.slides } if item.session.agenda() is not None else { "url": None }, @@ -1727,6 +1745,14 @@ def agenda_extract_recording (item): "url": item.external_url } +def agenda_extract_slide (item): + return { + "id": item.id, + "title": item.title, + "url": item.get_versionless_href(), + "ext": item.file_extension() + } + def agenda_csv(schedule, filtered_assignments): response = HttpResponse(content_type="text/csv; charset=%s"%settings.DEFAULT_CHARSET) writer = csv.writer(response, delimiter=str(','), quoting=csv.QUOTE_ALL) From a679bcc432d8cb9515fcb701e0a7a0c84a7644f2 Mon Sep 17 00:00:00 2001 From: Nicolas Giard Date: Tue, 26 Jul 2022 00:39:54 +0000 Subject: [PATCH 3/4] feat: agenda-neue - add slides + minutes tabs to session materials dialog --- client/agenda/AgendaDetailsModal.vue | 124 +++++++++++++++++++++++++-- 1 file changed, 119 insertions(+), 5 deletions(-) diff --git a/client/agenda/AgendaDetailsModal.vue b/client/agenda/AgendaDetailsModal.vue index 68749362b54..8b2857204f4 100644 --- a/client/agenda/AgendaDetailsModal.vue +++ b/client/agenda/AgendaDetailsModal.vue @@ -70,19 +70,64 @@ n-modal(v-model:show='modalShown') span.badge {{eventDetails.locationShort}} span {{eventDetails.locationName}} span {{eventDetails.room}} - .detail-text(v-if='eventDetails.materialsUrl') - iframe( - :src='eventDetails.materialsUrl' + nav.detail-nav.nav.nav-pills.nav-justified.mt-3 + a.nav-link( + :class='{ active: state.tab === `agenda` }' + @click='state.tab = `agenda`' + ) + i.bi.bi-list-columns-reverse.me-2 + span Agenda + a.nav-link( + :class='{ active: state.tab === `slides` }' + @click='state.tab = `slides`' + ) + i.bi.bi-easel.me-2 + span Slides + a.nav-link( + :class='{ active: state.tab === `minutes` }' + @click='state.tab = `minutes`' ) + i.bi.bi-journal-text.me-2 + span Minutes + .detail-text(v-if='eventDetails.materialsUrl') + template(v-if='state.tab === `agenda`') + iframe( + :src='eventDetails.materialsUrl' + ) + template(v-else-if='state.tab === `slides`') + .text-center(v-if='state.isLoading') + n-spin(description='Loading slides...') + .text-center.p-3(v-else-if='!state.materials || !state.materials.slides || state.materials.slides.length < 1') + span No slides submitted for this session. + .list-group(v-else) + a.list-group-item( + v-for='slide of state.materials.slides' + :key='slide.id' + :href='slide.url' + target='_blank' + ) + i.bi.me-2(:class='`bi-filetype-` + slide.ext') + span {{slide.title}} + template(v-else) + .text-center(v-if='state.isLoading') + n-spin(description='Loading minutes...') + .text-center.p-3(v-else-if='!state.materials || !state.materials.minutes') + span No minutes submitted for this session. + iframe( + v-else + :src='state.materials.minutes.url' + )