mirror of
https://github.com/Kunsi/pretalx-plugin-broadcast-tools
synced 2025-04-28 22:50:59 +00:00
rename plugin to 'pretalx_broadcast_tools'
This commit is contained in:
parent
7c5e58023c
commit
213db3f640
17 changed files with 81 additions and 82 deletions
21
pretalx_broadcast_tools/apps.py
Normal file
21
pretalx_broadcast_tools/apps.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy
|
||||
|
||||
|
||||
class PluginApp(AppConfig):
|
||||
name = "pretalx_broadcast_tools"
|
||||
verbose_name = "Broadcasting Tools"
|
||||
|
||||
class PretalxPluginMeta:
|
||||
name = gettext_lazy("Broadcasting Tools")
|
||||
author = "kunsi"
|
||||
description = gettext_lazy(
|
||||
"Some tools which can be used for supporting a broadcasting "
|
||||
"software, for example a 'lower third' page which can be "
|
||||
"embedded into your broadcasting software"
|
||||
)
|
||||
visible = True
|
||||
version = "0.1.2"
|
||||
|
||||
def ready(self):
|
||||
from . import signals # NOQA
|
20
pretalx_broadcast_tools/forms.py
Normal file
20
pretalx_broadcast_tools/forms.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from hierarkey.forms import HierarkeyForm
|
||||
from i18nfield.forms import I18nFormField, I18nFormMixin, I18nTextInput
|
||||
|
||||
|
||||
class BroadcastToolsSettingsForm(I18nFormMixin, HierarkeyForm):
|
||||
lower_thirds_no_talk_info = I18nFormField(
|
||||
help_text=_(
|
||||
"Will be shown as talk title if there's currently no talk running."
|
||||
),
|
||||
label=_('"no talk running" information'),
|
||||
widget=I18nTextInput,
|
||||
required=True,
|
||||
)
|
||||
lower_thirds_info_string = I18nFormField(
|
||||
help_text=_("Will only be shown if there's a talk running."),
|
||||
label=_("info line"),
|
||||
required=False,
|
||||
widget=I18nTextInput,
|
||||
)
|
36
pretalx_broadcast_tools/signals.py
Normal file
36
pretalx_broadcast_tools/signals.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from django.dispatch import receiver
|
||||
from django.urls import resolve, reverse
|
||||
from django.utils.translation import gettext_noop
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from pretalx.common.models.settings import hierarkey
|
||||
from pretalx.orga.signals import nav_event_settings
|
||||
|
||||
hierarkey.add_default(
|
||||
"lower_thirds_no_talk_info",
|
||||
LazyI18nString.from_gettext(
|
||||
gettext_noop("Sorry, there's currently no talk running")
|
||||
),
|
||||
LazyI18nString,
|
||||
)
|
||||
hierarkey.add_default("lower_thirds_info_string", "", LazyI18nString)
|
||||
|
||||
|
||||
@receiver(nav_event_settings)
|
||||
def navbar_info(sender, request, **kwargs):
|
||||
url = resolve(request.path_info)
|
||||
if not request.user.has_perm("orga.change_settings", request.event):
|
||||
return []
|
||||
return [
|
||||
{
|
||||
"label": _("lower thirds"),
|
||||
"url": reverse(
|
||||
"plugins:pretalx_broadcast_tools:orga",
|
||||
kwargs={
|
||||
"event": request.event.slug,
|
||||
},
|
||||
),
|
||||
"active": url.namespace == "plugins:pretalx_broadcast_tools"
|
||||
and url.url_name == "orga",
|
||||
}
|
||||
]
|
|
@ -0,0 +1,36 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
#box {
|
||||
width: 1020px;
|
||||
|
||||
position: absolute;
|
||||
bottom: 80px;
|
||||
left: 50%;
|
||||
margin-left: -510px;
|
||||
|
||||
color: white;
|
||||
font-family: "Muli","Open Sans","OpenSans","Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
padding: 15px;
|
||||
|
||||
box-shadow: 5px 5px 10px 0px rgba(50, 50, 50, 0.75);
|
||||
background-color: #3aa57c;
|
||||
}
|
||||
|
||||
#title {
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#speaker {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#info_line {
|
||||
font-size: 16px;
|
||||
text-align: right;
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
schedule = null;
|
||||
room_name = null;
|
||||
event_info = null;
|
||||
|
||||
$(function() {
|
||||
$('#speaker').text('Content will appear soon.');
|
||||
});
|
||||
|
||||
function update_lower_third() {
|
||||
current_time = new Date(Date.now()).getTime()
|
||||
|
||||
try {
|
||||
hash = decodeURIComponent(window.location.hash.substring(1));
|
||||
room_name = hash;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return
|
||||
}
|
||||
|
||||
if (!schedule || !event_info) {
|
||||
console.warn("There's no schedule or no event info yet, exiting ...");
|
||||
return
|
||||
}
|
||||
|
||||
if (schedule['rooms'].length > 1 && !schedule['rooms'].includes(room_name)) {
|
||||
$('#title').text('Error')
|
||||
$('#speaker').text('Invalid room_name. Valid names: ' + schedule['rooms'].join(', '));
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
current_talk = null;
|
||||
|
||||
for (talk_i in schedule['talks']) {
|
||||
talk = schedule['talks'][talk_i]
|
||||
|
||||
if (schedule['rooms'].length > 1 && talk['room'] != room_name) {
|
||||
// not in this room
|
||||
continue;
|
||||
}
|
||||
|
||||
talk_start = new Date(talk['start']).getTime();
|
||||
talk_end = new Date(talk['end']).getTime();
|
||||
|
||||
if (talk_start < current_time && talk_end > current_time) {
|
||||
current_talk = talk;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_talk) {
|
||||
$('#title').text(current_talk['title']);
|
||||
$('#speaker').text(current_talk['persons'].join(', '));
|
||||
$('#info_line').text(current_talk['infoline']);
|
||||
} else {
|
||||
$('#title').text(event_info['no_talk']);
|
||||
$('#speaker').text('');
|
||||
$('#info_line').text('');
|
||||
}
|
||||
|
||||
if (current_talk && current_talk['track']) {
|
||||
$('#box').css('border-bottom', '10px solid ' + current_talk['track']['color']);
|
||||
} else {
|
||||
$('#box').css('border-bottom', 'none');
|
||||
}
|
||||
}
|
||||
window.setInterval(update_lower_third, 1000);
|
||||
|
||||
function update_schedule() {
|
||||
$.getJSON('../event.json', function(data) {
|
||||
event_info = data;
|
||||
|
||||
$('#box').css('background-color', data['color']);
|
||||
});
|
||||
$.getJSON('../schedule.json', function(data) {
|
||||
console.info('schedule updated with ' + data['talks'].length + ' talks in ' + data['rooms'].length + ' rooms');
|
||||
|
||||
schedule = data;
|
||||
|
||||
window.setTimeout(update_schedule, 30000);
|
||||
});
|
||||
}
|
||||
update_schedule();
|
|
@ -0,0 +1,21 @@
|
|||
{% load static %}
|
||||
{% load compress %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html" charset="UTF-8">
|
||||
<title>{{ request.event.name }} lower thirds</title>
|
||||
{% compress js %}
|
||||
<script src="{% static "vendored/jquery-3.1.1.js" %}"></script>
|
||||
{% endcompress %}
|
||||
<script src="{% static "pretalx_broadcast_tools/update.js" %}"></script>
|
||||
<link rel="stylesheet" href="{% static "pretalx_broadcast_tools/frontend.css" %}" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="box">
|
||||
<p id="title">Loading ...</p>
|
||||
<p id="speaker">Content should appear soon. If not, please verify you have Javascript enabled.</p>
|
||||
<p id="info_line"></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,52 @@
|
|||
{% extends "orga/base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<fieldset>
|
||||
<legend>
|
||||
{% translate "Set up lower thirds" %}
|
||||
</legend>
|
||||
{% bootstrap_field form.lower_thirds_no_talk_info layout='event' %}
|
||||
{% bootstrap_field form.lower_thirds_info_string layout='event' %}
|
||||
<p>
|
||||
The info line will be shown on the bottom right side of your
|
||||
lower third. If you set it to an empty string, it will automatically
|
||||
hide itself.
|
||||
</p>
|
||||
<p>
|
||||
pretalx will automatically replace some placeholders in your info
|
||||
string.
|
||||
Use <code>{CODE}</code> to embed the talk code (<code>MUX9U3</code>
|
||||
for example). You could use this to directly link to the talk
|
||||
feedback page.
|
||||
Use <code>{EVENT_SLUG}</code> to get the event slug.
|
||||
Use <code>{TALK_SLUG}</code> to get the talk slug.
|
||||
</p>
|
||||
|
||||
{% if request.event.rooms %}
|
||||
<h3>{% trans "room list" %}</h3>
|
||||
<ul>
|
||||
{% for room in request.event.rooms.all %}
|
||||
<li><a href="{% url 'plugins:pretalx_broadcast_tools:lowerthirds' request.event.slug %}#{{ room.name }}">{{ room.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<div class="submit-group panel">
|
||||
<span></span>
|
||||
<span class="d-flex flex-row-reverse">
|
||||
<button
|
||||
type="submit" class="btn btn-success btn-lg"
|
||||
name="action" value="save"
|
||||
>
|
||||
<i class="fa fa-check"></i>
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
27
pretalx_broadcast_tools/urls.py
Normal file
27
pretalx_broadcast_tools/urls.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from django.urls import re_path
|
||||
from pretalx.event.models.event import SLUG_CHARS
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
re_path(
|
||||
f"^(?P<event>[{SLUG_CHARS}]+)/p/broadcast-tools/lower-thirds/$",
|
||||
views.BroadcastToolsLowerThirdsView.as_view(),
|
||||
name="lowerthirds",
|
||||
),
|
||||
re_path(
|
||||
f"^(?P<event>[{SLUG_CHARS}]+)/p/broadcast-tools/event.json$",
|
||||
views.BroadcastToolsEventInfoView.as_view(),
|
||||
name="event_info",
|
||||
),
|
||||
re_path(
|
||||
f"^(?P<event>[{SLUG_CHARS}]+)/p/broadcast-tools/schedule.json$",
|
||||
views.BroadcastToolsScheduleView.as_view(),
|
||||
name="schedule",
|
||||
),
|
||||
re_path(
|
||||
f"^orga/event/(?P<event>[{SLUG_CHARS}]+)/settings/p/broadcast-tools/$",
|
||||
views.BroadcastToolsOrgaView.as_view(),
|
||||
name="orga",
|
||||
),
|
||||
]
|
108
pretalx_broadcast_tools/views.py
Normal file
108
pretalx_broadcast_tools/views.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytz
|
||||
from django.http import JsonResponse
|
||||
from django.views.generic import FormView
|
||||
from django.views.generic.base import TemplateView
|
||||
from pretalx.agenda.views.schedule import ScheduleMixin
|
||||
from pretalx.common.mixins.views import EventPermissionRequired, PermissionRequired
|
||||
from pretalx.schedule.exporters import ScheduleData
|
||||
|
||||
from .forms import BroadcastToolsSettingsForm
|
||||
|
||||
|
||||
class BroadcastToolsLowerThirdsView(TemplateView):
|
||||
template_name = "pretalx_broadcast_tools/lower_thirds.html"
|
||||
|
||||
|
||||
class BroadcastToolsOrgaView(PermissionRequired, FormView):
|
||||
form_class = BroadcastToolsSettingsForm
|
||||
permission_required = "orga.change_settings"
|
||||
template_name = "pretalx_broadcast_tools/orga.html"
|
||||
|
||||
def get_success_url(self):
|
||||
return self.request.path
|
||||
|
||||
def form_valid(self, form):
|
||||
form.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_object(self):
|
||||
return self.request.event
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
return {
|
||||
"obj": self.request.event,
|
||||
"attribute_name": "settings",
|
||||
"locales": self.request.event.locales,
|
||||
**kwargs,
|
||||
}
|
||||
|
||||
|
||||
class BroadcastToolsEventInfoView(TemplateView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
color = self.request.event.primary_color or "#3aa57c"
|
||||
return JsonResponse(
|
||||
{
|
||||
"slug": self.request.event.slug,
|
||||
"name": str(self.request.event.name),
|
||||
"no_talk": str(self.request.event.settings.lower_thirds_no_talk_info),
|
||||
"color": color,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class BroadcastToolsScheduleView(EventPermissionRequired, ScheduleMixin, TemplateView):
|
||||
permission_required = "agenda.view_schedule"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
schedule = ScheduleData(
|
||||
event=self.request.event,
|
||||
schedule=self.schedule,
|
||||
)
|
||||
tz = pytz.timezone(schedule.event.timezone)
|
||||
infoline = str(schedule.event.settings.infoline or "")
|
||||
return JsonResponse(
|
||||
{
|
||||
"rooms": sorted(
|
||||
{
|
||||
str(room["name"])
|
||||
for day in schedule.data
|
||||
for room in day["rooms"]
|
||||
}
|
||||
),
|
||||
"talks": [
|
||||
{
|
||||
"id": talk.submission.id,
|
||||
"start": talk.start.astimezone(tz).isoformat(),
|
||||
"end": (talk.start + dt.timedelta(minutes=talk.duration))
|
||||
.astimezone(tz)
|
||||
.isoformat(),
|
||||
"slug": talk.frab_slug,
|
||||
"title": talk.submission.title,
|
||||
"persons": sorted(
|
||||
{
|
||||
person.get_display_name()
|
||||
for person in talk.submission.speakers.all()
|
||||
}
|
||||
),
|
||||
"track": {
|
||||
"color": talk.submission.track.color,
|
||||
"name": str(talk.submission.track.name),
|
||||
}
|
||||
if talk.submission.track
|
||||
else None,
|
||||
"room": str(room["name"]),
|
||||
"infoline": infoline.format(
|
||||
EVENT_SLUG=str(schedule.event.slug),
|
||||
TALK_SLUG=talk.frab_slug,
|
||||
CODE=talk.submission.code,
|
||||
),
|
||||
}
|
||||
for day in schedule.data
|
||||
for room in day["rooms"]
|
||||
for talk in room["talks"]
|
||||
],
|
||||
},
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue