fix: add missing slides + minutes tabs to session materials dialog #4274 (#4275)

* fix: handle session dropdown link on mobile view

* feat: agenda session materials API endpoint

* feat: agenda-neue - add slides + minutes tabs to session materials dialog

* fix: remove commented line leftover
This commit is contained in:
Nicolas Giard 2022-07-26 10:30:20 -04:00 committed by GitHub
parent 4e649b9e2c
commit aea533e4dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 181 additions and 7 deletions

View file

@ -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'
)
</template>
<script setup>
import { computed } from 'vue'
import { computed, reactive, watch } from 'vue'
import {
NButton,
NCard,
NModal,
NPopover
NPopover,
NSpin,
useMessage
} from 'naive-ui'
import { useAgendaStore } from './store'
@ -101,6 +146,10 @@ const props = defineProps({
}
})
// MESSAGE PROVIDER
const message = useMessage()
// STORES
const agendaStore = useAgendaStore()
@ -109,6 +158,14 @@ const agendaStore = useAgendaStore()
const emit = defineEmits(['update:shown'])
// STATE
const state = reactive({
tab: 'agenda',
isLoading: false,
materials: {}
})
// COMPUTED
const eventDetails = computed(() => {
@ -142,6 +199,38 @@ const modalShown = computed({
}
})
// WATCHERS
watch(() => props.shown, (newValue) => {
if (newValue) {
state.materials = {}
state.tab = 'agenda'
if (props.event.flags.showAgenda) {
fetchSessionMaterials()
}
}
})
// METHODS
async function fetchSessionMaterials () {
if (!props.event) { return null }
state.isLoading = true
try {
const resp = await fetch(`/api/meeting/session/${props.event.sessionId}/materials`, { credentials: 'omit' })
if (!resp.ok) {
throw new Error(resp.statusText)
}
state.materials = await resp.json()
} catch (err) {
console.warn(err)
message.error('Failed to fetch session materials.')
}
state.isLoading = false
}
</script>
<style lang="scss">
@ -209,6 +298,27 @@ const modalShown = computed({
}
}
nav.detail-nav {
padding: 5px;
background-color: #FFF;
border: 1px solid $gray-300;
border-radius: 5px;
font-weight: 500;
a {
cursor: pointer;
.bi {
font-size: inherit;
color: inherit;
}
&:not(.active):hover {
background-color: rgba($blue-100, .25);
}
}
}
.detail-text {
padding: 12px;
background-color: #FAFAFA;
@ -217,6 +327,10 @@ const modalShown = computed({
margin-top: 12px;
border-radius: 5px;
.bi {
color: $blue;
}
> iframe {
width: 100%;
height: 50vh;

View file

@ -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

View file

@ -29,6 +29,8 @@ urlpatterns = [
url(r'^meeting/session/video/url$', meeting_views.api_set_session_video_url),
# Meeting agenda + floorplan data
url(r'^meeting/(?P<num>[A-Za-z0-9._+-]+)/agenda-data$', meeting_views.api_get_agenda_data),
# Meeting session materials
url(r'^meeting/session/(?P<session_id>[A-Za-z0-9._+-]+)/materials$', meeting_views.api_get_session_materials),
# Let Meetecho trigger recording imports
url(r'^notify/meeting/import_recordings/(?P<number>[a-z0-9-]+)/?$', meeting_views.api_import_recordings),
# Let MeetEcho upload bluesheets

View file

@ -170,6 +170,7 @@ class MeetingTests(BaseMeetingTestCase):
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 @@ class MeetingTests(BaseMeetingTestCase):
"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)

View file

@ -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,
@ -1727,6 +1744,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)