* 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:
parent
4e649b9e2c
commit
aea533e4dd
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue