Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 119 additions & 5 deletions client/agenda/AgendaDetailsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -101,6 +146,10 @@ const props = defineProps({
}
})

// MESSAGE PROVIDER

const message = useMessage()

// STORES

const agendaStore = useAgendaStore()
Expand All @@ -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(() => {
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -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;
Expand All @@ -217,6 +327,10 @@ const modalShown = computed({
margin-top: 12px;
border-radius: 5px;

.bi {
color: $blue;
}

> iframe {
width: 100%;
height: 50vh;
Expand Down
18 changes: 16 additions & 2 deletions client/agenda/AgendaScheduleList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions ietf/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<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
Expand Down
19 changes: 19 additions & 0 deletions ietf/meeting/tests_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand All @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions ietf/meeting/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down