From e329d2c976bfb376671f59ca7c5e0d445a121918 Mon Sep 17 00:00:00 2001 From: odkhang Date: Thu, 8 Aug 2024 18:46:52 +0700 Subject: [PATCH] Add sessions to sidebar and show filter and export options (#215) * Add sessions to sidebar and show filter and export options * update translate for sessions --- server/venueless/api/urls.py | 1 + server/venueless/api/views.py | 28 +++ webapp/src/components/RoomsSidebar.vue | 1 + webapp/src/locales/ar.json | 1 + webapp/src/locales/de.json | 1 + webapp/src/locales/en.json | 1 + webapp/src/locales/es.json | 1 + webapp/src/locales/fr.json | 1 + webapp/src/locales/pt_BR.json | 1 + webapp/src/locales/ru.json | 1 + webapp/src/locales/uk.json | 1 + webapp/src/router/index.js | 6 + webapp/src/views/schedule/export-select.vue | 161 ++++++++++++ webapp/src/views/schedule/index.vue | 98 +++++++- webapp/src/views/schedule/sessions/index.vue | 244 +++++++++++++++++++ 15 files changed, 545 insertions(+), 2 deletions(-) create mode 100644 webapp/src/views/schedule/export-select.vue create mode 100644 webapp/src/views/schedule/sessions/index.vue diff --git a/server/venueless/api/urls.py b/server/venueless/api/urls.py index f0468c4d..01a5a3eb 100644 --- a/server/venueless/api/urls.py +++ b/server/venueless/api/urls.py @@ -13,4 +13,5 @@ path("worlds//", include(world_router.urls)), path("worlds//theme", views.WorldThemeView.as_view()), path("worlds//favourite-talk/", views.UserFavouriteView.as_view()), + path("worlds//export-talk", views.ExportView.as_view()), ] diff --git a/server/venueless/api/views.py b/server/venueless/api/views.py index b3d0fe0a..28ecc785 100644 --- a/server/venueless/api/views.py +++ b/server/venueless/api/views.py @@ -3,6 +3,7 @@ from contextlib import suppress from urllib.parse import urlparse import jwt +import requests from asgiref.sync import async_to_sync from django.core import exceptions @@ -165,6 +166,33 @@ def get_uid_from_token(request, world_id): return token_decode.get("uid") +class ExportView(APIView): + + permission_classes = [] + + @staticmethod + def get(request, *args, **kwargs): + export_type = request.GET.get('export_type', 'json') + world = get_object_or_404(World, id=kwargs["world_id"]) + talk_config = world.config.get("pretalx") + user = User.objects.filter(token_id=request.user) + talk_base_url = talk_config.get('domain') + "/" + talk_config.get('event') + "/schedule/export/" + export_endpoint = 'schedule.' + export_type + talk_url = talk_base_url + export_endpoint + if 'my' in export_type and user: + user_state = user.first().client_state + if user_state and user_state.get('schedule') and user_state.get('schedule').get('favs'): + talk_list = user_state.get('schedule').get('favs') + talk_list_str = ','.join(talk_list) + export_endpoint = 'schedule-my.' + export_type.replace('my','') + talk_url = talk_base_url + export_endpoint + "?talks=" + talk_list_str + header = { + "Content-Type": "application/json" + } + response = requests.get(talk_url, headers=header) + return Response(response.content.decode("utf-8")) + + def get_domain(path): if not path: return "" diff --git a/webapp/src/components/RoomsSidebar.vue b/webapp/src/components/RoomsSidebar.vue index 720dc437..08c42337 100644 --- a/webapp/src/components/RoomsSidebar.vue +++ b/webapp/src/components/RoomsSidebar.vue @@ -8,6 +8,7 @@ transition(name="sidebar") .global-links(role="group", aria-label="pages") router-link.room(v-if="roomsByType.page.includes(rooms[0])", :to="{name: 'home'}", v-html="$emojify(rooms[0].name)") router-link.room(:to="{name: 'schedule'}", v-if="!!world.pretalx && (world.pretalx.url || world.pretalx.domain)") {{ $t('RoomsSidebar:schedule:label') }} + router-link.room(:to="{name: 'schedule:sessions'}", v-if="!!world.pretalx && (world.pretalx.url || world.pretalx.domain)") {{ $t('RoomsSidebar:session:label') }} router-link.room(:to="{name: 'schedule:speakers'}", v-if="!!world.pretalx && (world.pretalx.url || world.pretalx.domain)") {{ $t('RoomsSidebar:speaker:label') }} router-link.room(v-for="page of roomsByType.page", v-if="page !== rooms[0]", :to="{name: 'room', params: {roomId: page.id}}", v-html="$emojify(page.name)") .group-title#stages-title(v-if="roomsByType.stage.length || hasPermission('world:rooms.create.stage')") diff --git a/webapp/src/locales/ar.json b/webapp/src/locales/ar.json index 015dd077..82fc1d6d 100644 --- a/webapp/src/locales/ar.json +++ b/webapp/src/locales/ar.json @@ -348,6 +348,7 @@ "RoomsSidebar:schedule:label": "الجدول", "RoomsSidebar:speaker:label": "المتحدثين", "RoomsSidebar:stages-headline:text": "المراحل", + "RoomsSidebar:session:label": "الجلسات", "RoomsSidebar:users-tooltip:few": "بعض الأشخاص حاليًا داخل هذه الغرفة", "RoomsSidebar:users-tooltip:many": "العديد من المستخدمين حاليًا داخل هذه الغرفة", "Roulette:connecting:text": "لقد وجدنا شخصًا! جاري الاتصال …", diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 952f464c..48c53178 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -345,6 +345,7 @@ "RoomsSidebar:posters-headline:text": "Plakate", "RoomsSidebar:posters-manage:label": "Verwalten", "RoomsSidebar:schedule:label": "Zeitplan", + "RoomsSidebar:session:label": "Sitzungen", "RoomsSidebar:speaker:label": "Sprecher", "RoomsSidebar:stages-headline:text": "Bühnen", "RoomsSidebar:users-tooltip:few": "Einige Leute sind derzeit in diesem Raum", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 8f6dc6b6..7b3c07e3 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -347,6 +347,7 @@ "RoomsSidebar:posters-headline:text": "Posters", "RoomsSidebar:posters-manage:label": "Manage", "RoomsSidebar:schedule:label": "Schedule", + "RoomsSidebar:session:label": "Sessions", "RoomsSidebar:speaker:label": "Speakers", "RoomsSidebar:stages-headline:text": "Stages", "RoomsSidebar:users-tooltip:few": "Some people are currently inside this room", diff --git a/webapp/src/locales/es.json b/webapp/src/locales/es.json index 829e9f8b..9f9173d2 100644 --- a/webapp/src/locales/es.json +++ b/webapp/src/locales/es.json @@ -346,6 +346,7 @@ "RoomsSidebar:posters-headline:text": "Pósters", "RoomsSidebar:posters-manage:label": "Gestionar", "RoomsSidebar:schedule:label": "Programa", + "RoomsSidebar:session:label": "Sesiones", "RoomsSidebar:speaker:label": "Ponentes", "RoomsSidebar:stages-headline:text": "Escenarios", "RoomsSidebar:users-tooltip:few": "Pocas personas están en esta sala en este momento", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index 782fb43e..2243a05f 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -346,6 +346,7 @@ "RoomsSidebar:posters-headline:text": "Affiches", "RoomsSidebar:posters-manage:label": "Gérer", "RoomsSidebar:schedule:label": "Programme", + "RoomsSidebar:session:label": "Séances", "RoomsSidebar:speaker:label": "Intervenants", "RoomsSidebar:stages-headline:text": "Scènes", "RoomsSidebar:users-tooltip:few": "Quelques personnes sont actuellement dans cette salle", diff --git a/webapp/src/locales/pt_BR.json b/webapp/src/locales/pt_BR.json index a799f448..02e7d5ed 100644 --- a/webapp/src/locales/pt_BR.json +++ b/webapp/src/locales/pt_BR.json @@ -345,6 +345,7 @@ "RoomsSidebar:posters-headline:text": "Pôsteres", "RoomsSidebar:posters-manage:label": "Gerenciar", "RoomsSidebar:schedule:label": "Programa", + "RoomsSidebar:session:label": "Sessões", "RoomsSidebar:speaker:label": "Palestrantes", "RoomsSidebar:stages-headline:text": "Palcos", "RoomsSidebar:users-tooltip:few": "Algumas pessoas estão nesta sala no momento", diff --git a/webapp/src/locales/ru.json b/webapp/src/locales/ru.json index d9b66f34..cc07aedc 100644 --- a/webapp/src/locales/ru.json +++ b/webapp/src/locales/ru.json @@ -346,6 +346,7 @@ "RoomsSidebar:posters-headline:text": "Постеры", "RoomsSidebar:posters-manage:label": "Управление", "RoomsSidebar:schedule:label": "Расписание", + "RoomsSidebar:session:label": "Сессии", "RoomsSidebar:speaker:label": "Спикеры", "RoomsSidebar:stages-headline:text": "Сцены", "RoomsSidebar:users-tooltip:few": "Несколько человек в данный момент находятся в этой комнате", diff --git a/webapp/src/locales/uk.json b/webapp/src/locales/uk.json index 9d603bf7..6d3aa8dc 100644 --- a/webapp/src/locales/uk.json +++ b/webapp/src/locales/uk.json @@ -346,6 +346,7 @@ "RoomsSidebar:posters-headline:text": "Постери", "RoomsSidebar:posters-manage:label": "Управління", "RoomsSidebar:schedule:label": "Розклад", + "RoomsSidebar:session:label": "Сеанси", "RoomsSidebar:speaker:label": "Доповідачі", "RoomsSidebar:stages-headline:text": "Сцени", "RoomsSidebar:users-tooltip:few": "Кілька людей наразі знаходяться в цій кімнаті", diff --git a/webapp/src/router/index.js b/webapp/src/router/index.js index 95906781..1b4d7930 100644 --- a/webapp/src/router/index.js +++ b/webapp/src/router/index.js @@ -7,6 +7,7 @@ import RoomManager from 'views/rooms/manage' import Channel from 'views/channels/item' import Schedule from 'views/schedule' import Talk from 'views/schedule/talks/item' +import Session from 'views/schedule/sessions' import Speakers from 'views/schedule/speakers' import Speaker from 'views/schedule/speakers/item' import Exhibitor from 'views/exhibitors/item' @@ -87,6 +88,11 @@ const routes = [{ name: 'schedule:talk', component: Talk, props: true + }, { + path: '/sessions', + name: 'schedule:sessions', + component: Session, + props: true }, { path: '/schedule/speakers', name: 'schedule:speakers', diff --git a/webapp/src/views/schedule/export-select.vue b/webapp/src/views/schedule/export-select.vue new file mode 100644 index 00000000..030e51cf --- /dev/null +++ b/webapp/src/views/schedule/export-select.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/webapp/src/views/schedule/index.vue b/webapp/src/views/schedule/index.vue index 9312d339..edaf6e96 100644 --- a/webapp/src/views/schedule/index.vue +++ b/webapp/src/views/schedule/index.vue @@ -12,6 +12,14 @@ icon="star" @click="toggleFavFilter" :class="{active: filter.type === 'fav'}") {{favs.length}} + .export.dropdown + bunt-progress-circular.export-spinner(v-if="isExporting", size="small") + custom-dropdown(name="calendar-add1" + v-model="selectedExporter" + :options="exportType" + label="Add to Calendar" + @input="makeExport") + bunt-tabs.days(v-if="days && days.length > 1", :active-tab="currentDay.toISOString()", ref="tabs", v-scrollbar.x="") bunt-tab(v-for="day in days", :id="day.toISOString()", :header="moment(day).format('dddd DD. MMMM')", @selected="changeDay(day)") .scroll-parent(ref="scrollParent", v-scrollbar.x.y="") @@ -55,22 +63,67 @@ import moment from 'lib/timetravelMoment' import TimezoneChanger from 'components/TimezoneChanger' import scheduleProvidesMixin from 'components/mixins/schedule-provides' import Prompt from 'components/Prompt' +import api from 'lib/api' +import config from 'config' +import CustomDropdown from 'views/schedule/export-select' + +const exportTypeSet = [ + { + "id": "ics", + "label": "Session ICal" + }, + { + "id": "json", + "label": "Session JSON" + }, + { + "id": "xcal", + "label": "Session XCal" + }, + { + "id": "xml", + "label": "Session XML" + }, + { + "id": "myics", + "label": "My ⭐ Sessions ICal" + }, + { + "id": "myjson", + "label": "My ⭐ Sessions JSON" + }, + { + "id": "myxcal", + "label": "My ⭐ Sessions XCal" + }, + { + "id": "myxml", + "label": "My ⭐ Sessions XML" + }, +] export default { - components: { LinearSchedule, GridSchedule, TimezoneChanger, Prompt }, + components: { LinearSchedule, GridSchedule, TimezoneChanger, Prompt, CustomDropdown }, mixins: [scheduleProvidesMixin], data () { return { tracksFilter: {}, open: false, moment, - currentDay: moment().startOf('day') + currentDay: moment().startOf('day'), + selectedExporter: null, + exportOptions: [], + isExporting: false, + error: null } }, computed: { ...mapState(['now']), ...mapState('schedule', ['schedule', 'errorLoading', 'filter']), ...mapGetters('schedule', ['days', 'rooms', 'sessions', 'favs']), + exportType () { + return exportTypeSet + } }, watch: { tracksFilter: { @@ -112,6 +165,34 @@ export default { } else { this.$store.dispatch('schedule/filter', {type: 'fav'}) } + }, + async makeExport() { + try { + this.isExporting = true; + const url = config.api.base + 'export-talk?export_type=' + this.selectedExporter.id + const authHeader = api._config.token ? `Bearer ${api._config.token}` : (api._config.clientId ? `Client ${api._config.clientId}` : null) + const result = await fetch(url, { + method: 'GET', + headers: { + Accept: 'application/json', + Authorization: authHeader, + } + }).then(response => response.json()) + var a = document.createElement("a"); + document.body.appendChild(a); + const blob = new Blob([result], {type: "octet/stream"}), + download_url = window.URL.createObjectURL(blob); + a.href = download_url; + a.download = "schedule-" + this.selectedExporter.id + '.' + this.selectedExporter.id.replace('my',''); + a.click(); + window.URL.revokeObjectURL(download_url); + a.remove() + this.isExporting = false; + } catch (error) { + this.isExporting = false; + this.error = error + console.log(error) + } } } } @@ -140,6 +221,8 @@ export default { .bunt-scrollbar-rail-wrapper-x +below('m') display: none + .filter-actions + display: flex .error flex: auto display: flex @@ -174,4 +257,15 @@ export default { margin: 16px 8px &.active border: 2px solid #f9a557 + .export.dropdown + display: flex + margin-left: auto + padding-right: 40px !important + .bunt-progress-circular + width: 20px + height: 20px + .export-spinner + padding-top: 22px !important + margin-right: 10px + diff --git a/webapp/src/views/schedule/sessions/index.vue b/webapp/src/views/schedule/sessions/index.vue new file mode 100644 index 00000000..0e8cffb7 --- /dev/null +++ b/webapp/src/views/schedule/sessions/index.vue @@ -0,0 +1,244 @@ + + +