#52: Added countdown on projector view with control unit (agenda overview) and time variable (general settings).

This commit is contained in:
Emanuel Schuetze 2011-11-09 21:16:02 +01:00
parent 52c24d6a48
commit 076699ff45
12 changed files with 197 additions and 12 deletions

View File

@ -12,7 +12,6 @@
<script type="text/javascript" src="/static/javascript/tabledrag.js"></script> <script type="text/javascript" src="/static/javascript/tabledrag.js"></script>
<script type="text/javascript" src="/static/javascript/knockout-1.2.1.js"></script> <script type="text/javascript" src="/static/javascript/knockout-1.2.1.js"></script>
<script type="text/javascript" src="/static/javascript/agenda.js"></script> <script type="text/javascript" src="/static/javascript/agenda.js"></script>
<script type="text/html" src="/static/javascript/agenda.slideTemplate.js"></script>
<script type="text/javascript"> <script type="text/javascript">
<!--//--><![CDATA[//><!-- <!--//--><![CDATA[//><!--
@ -48,13 +47,27 @@
<input type="button" onclick="window.location.href='{% url item_overview %}';" value="{%trans 'No' %}"> <input type="button" onclick="window.location.href='{% url item_overview %}';" value="{%trans 'No' %}">
</em> </em>
</div> </div>
<div style="text-align:right; padding-bottom:3px;">
<div style="text-align: right; padding: 0 5px 5px 0; margin-top:-20px;">
<!-- countdown -->
<input type="checkbox" name="countdown" onchange="document.location='{% if countdown_visible == "True" %}{% url countdown_close %}{% else %}{% url countdown_open %}{% endif %}'"
{% if countdown_visible == "True" %}checked{% endif %}>
{% trans "Countdown" %} ({{countdown_time}} {% trans "sec" %}){% if countdown_visible == "True" %}:
<a class="beamer_countdown" href="{% url countdown_reset countdown_time %}" title="{% trans 'Reset countdown' %}"><img src="/static/images/icons/media-skip-backward.png"></a>
<a class="beamer_countdown" href="{% url countdown_start %}" title="{% trans 'Start countdown' %}"><img src="/static/images/icons/media-playback-start.png"></a>
<a class="beamer_countdown" href="{% url countdown_stop %}" title="{% trans 'Stop countdown' %}"><img src="/static/images/icons/media-playback-pause.png"></a>
{% endif %}
<p></p>
<!-- beamer control -->
{% trans "Adjust projector view" %}: {% trans "Adjust projector view" %}:
<a class="beamer_edit" href="{% url beamer_bigger %}"><img src="/static/images/icons/zoom-in.png"></a> <a class="beamer_edit" href="{% url beamer_smaller %}"><img src="/static/images/icons/zoom-out.png"></a> <a class="beamer_edit" href="{% url beamer_bigger %}" title="{% trans 'Zoom in' %}"><img src="/static/images/icons/zoom-in.png"></a>
<a class="beamer_edit" href="{% url beamer_up %}"><img src="/static/images/icons/go-up.png"></a> <a class="beamer_edit" href="{% url beamer_down %}"><img src="/static/images/icons/go-down.png"></a> <a class="beamer_edit" href="{% url beamer_smaller %}" title="{% trans 'Zoom out' %}"><img src="/static/images/icons/zoom-out.png"></a>
<a class="beamer_edit" href="{% url beamer_clean %}"><img src="/static/images/icons/view-restore.png"></a> <a class="beamer_edit" href="{% url beamer_up %}" title="{% trans 'Scroll text up' %}"><img src="/static/images/icons/go-up.png"></a>
<a class="beamer_edit" href="{% url beamer_down %}" title="{% trans 'Scroll text down' %}"><img src="/static/images/icons/go-down.png"></a>
<a class="beamer_edit" href="{% url beamer_clean %}" title="{% trans 'Reset projector view' %}"><img src="/static/images/icons/view-restore.png"></a>
</div> </div>
{% endif %} {% endif %}
<table id="menu-overview" class="agendatable"> <table id="menu-overview" class="agendatable">
<tr> <tr>
{% if perms.agenda.can_manage_agenda %} {% if perms.agenda.can_manage_agenda %}

View File

@ -28,6 +28,8 @@
{% now "H:i" %} {% now "H:i" %}
</div> </div>
<div id="countdown">0:00</div>
<div id="content"> <div id="content">
{% block content %} {% block content %}
{% endblock %} {% endblock %}

View File

@ -66,4 +66,14 @@ urlpatterns = patterns('agenda.views',
url(r'^beamer/down$', 'beamer_edit', {'direction': 'down'}, name='beamer_down'), url(r'^beamer/down$', 'beamer_edit', {'direction': 'down'}, name='beamer_down'),
url(r'^beamer/clean$', 'beamer_edit', {'direction': 'clean'}, name='beamer_clean'), url(r'^beamer/clean$', 'beamer_edit', {'direction': 'clean'}, name='beamer_clean'),
url(r'^beamer/countdown/show$', 'beamer_countdown', {'command': 'show'}, name='countdown_open'),
url(r'^beamer/countdown/hide$', 'beamer_countdown', {'command': 'hide'}, name='countdown_close'),
url(r'^beamer/countdown/reset/(?P<time>\d+)$', 'beamer_countdown', {'command': 'reset'}, name='countdown_reset'),
url(r'^beamer/countdown/start$', 'beamer_countdown', {'command': 'start'}, name='countdown_start'),
url(r'^beamer/countdown/stop$', 'beamer_countdown', {'command': 'stop'}, name='countdown_stop'),
) )

View File

@ -83,7 +83,11 @@ def beamer(request):
'title': data['title'], 'title': data['title'],
'time': datetime.now().strftime('%H:%M'), 'time': datetime.now().strftime('%H:%M'),
'bigger': config_get('bigger'), 'bigger': config_get('bigger'),
'up': config_get('up')} 'up': config_get('up'),
'countdown_visible': config_get('countdown_visible'),
'countdown_time': config_get('agenda_countdown_time'),
'countdown_control': config_get('countdown_control'),
}
return ajax_request(jsondata) return ajax_request(jsondata)
else: else:
return render_to_response(template, return render_to_response(template,
@ -108,6 +112,27 @@ def beamer_edit(request, direction):
return ajax_request({}) return ajax_request({})
return redirect(reverse('item_overview')) return redirect(reverse('item_overview'))
@permission_required('agenda.can_manage_agenda')
def beamer_countdown(request, command, time=60):
if command == 'show':
config_set('countdown_visible', True)
elif command == 'hide':
config_set('countdown_visible', False)
elif command == 'reset':
config_set('countdown_control', 'reset')
elif command == 'start':
config_set('countdown_control', 'start')
elif command == 'stop':
config_set('countdown_control', 'stop')
if request.is_ajax():
if command == "show":
link = reverse('countdown_close')
else:
link = reverse('countdown_open')
return ajax_request({'countdown_visible': config_get('countdown_visible'),
'link': link})
return redirect(reverse('item_overview'))
def assignment_votes(item): def assignment_votes(item):
votes = [] votes = []
@ -170,7 +195,9 @@ def overview(request):
'items': items, 'items': items,
'items_hidden': items_hidden, 'items_hidden': items_hidden,
'overview': overview, 'overview': overview,
'summary': is_summary() 'summary': is_summary(),
'countdown_visible': config_get('countdown_visible'),
'countdown_time': config_get('agenda_countdown_time'),
} }

View File

@ -104,7 +104,7 @@ $(function() {
hideClosedSlides(true); hideClosedSlides(true);
} }
// controll beamer // control beamer
$('.beamer_edit').click(function(event) { $('.beamer_edit').click(function(event) {
event.preventDefault(); event.preventDefault();
link = $(this); link = $(this);
@ -117,5 +117,35 @@ $(function() {
}); });
}); });
// control countdown
$('.beamer_countdown').click(function(event) {
event.preventDefault();
link = $(this);
$.ajax({
type: 'GET',
url: link.attr('href'),
dataType: 'json',
success: function(data) {
}
});
});
$('.countdown_visible_link').click(function(event) {
event.preventDefault();
link = $(this);
$.ajax({
type: 'GET',
url: link.attr('href'),
dataType: 'json',
success: function(data) {
if (data.countdown_visible == "True") {
newclass = 'open';
} else {
newclass = 'closed';
}
link.removeClass('closed open').addClass(newclass);
link.attr('href', data.link);
}
});
});
ko.applyBindings(ViewModel); ko.applyBindings(ViewModel);
}); });

View File

@ -14,6 +14,18 @@ function presentation_reload() {
$('#content li').css({'font-size': data.bigger + '%'}, 200); $('#content li').css({'font-size': data.bigger + '%'}, 200);
$('#content #sidebar').css({'font-size': '16px'}, 0); $('#content #sidebar').css({'font-size': '16px'}, 0);
$('#content').animate({'margin-top': data.up + 'em'}, 200); $('#content').animate({'margin-top': data.up + 'em'}, 200);
if (data.countdown_visible == "True")
$('#countdown').slideDown();
if (data.countdown_visible == "False")
$('#countdown').slideUp();
if (data.countdown_control == "reset")
resetTimer(data.countdown_time);
if (data.countdown_control == 'start') {
if (!timer_is_running)
startTimer();
}
if (data.countdown_control == 'stop')
stopTimer();
setTimeout("presentation_reload()", 500); setTimeout("presentation_reload()", 500);
}, },
error: function () { error: function () {
@ -38,3 +50,47 @@ $(document).ready(function() {
switchajax(); switchajax();
presentation_reload(); presentation_reload();
}); });
// *** Countdown variables and functions ***
var timer_value;
var timer_is_running=false;
var timer_is_visible=false;
var timerIntervalId;
function resetTimer(value) {
stopTimer()
timer_value = value;
updateTimer();
}
function stopTimer() {
timer_is_running = false;
clearInterval(timerIntervalId);
}
function startTimer() {
timer_is_running = true;
if (timer_value > 0) {
timerIntervalId = setInterval("decrementTimer()", 1000);
}
}
function decrementTimer() {
timer_value--;
if (timer_value <= 0) {
timer_value = 0;
stopTimer();
}
updateTimer();
}
function convertSeconds(s) {
var m = Math.floor(s / 60);
s %= 60;
var h = Math.floor(m / 60);
m %= 60;
return (h>0?h+':':'') + (h>0&&m<10?'0':'') + m + ':' + (s<10?'0':'') + s;
}
function updateTimer() {
if (timer_value >= 0) {
$("#countdown").html(convertSeconds(timer_value));
}
}

View File

@ -47,3 +47,19 @@ a.hidelink.show div {
display: inline-block; display: inline-block;
} }
a.countdown_visible_link.closed div {
background-image: url(/static/images/icons/user-offline.png);
background-repeat: no-repeat;
background-position: center;
width: 16px;
height: 16px;
display: inline-block;
}
a.countdown_visible_link.open div {
background-image: url(/static/images/icons/user-online.png);
background-repeat: no-repeat;
background-position: center;
width: 16px;
height: 16px;
display: inline-block;
}

View File

@ -56,10 +56,22 @@ body{
padding-left:30px; padding-left:30px;
background: url(../images/icons/clock.png) no-repeat scroll 0px 4px; background: url(../images/icons/clock.png) no-repeat scroll 0px 4px;
} }
#currentTime.ajax_error { #currentTime.ajax_error {
border-bottom: 4px solid red; border-bottom: 4px solid red;
} }
#countdown {
display: none;
font-size: 4em;
font-weight: bold;
padding: 0 1em;
position: fixed;
right: 40px;
top: 0;
background: #DDD9D9;
border-bottom-left-radius: 0.75em;
border-bottom-right-radius: 0.75em;
}
/*** CONTENT ***/ /*** CONTENT ***/
#content { #content {

View File

@ -32,6 +32,12 @@ class EventConfigForm(Form):
event_location = CharField(widget=TextInput(), required=False, label=_("Event location")) event_location = CharField(widget=TextInput(), required=False, label=_("Event location"))
event_organizer = CharField(widget=TextInput(), required=False, label=_("Event organizer")) event_organizer = CharField(widget=TextInput(), required=False, label=_("Event organizer"))
class AgendaConfigForm(Form):
error_css_class = 'error'
required_css_class = 'required'
agenda_countdown_time = IntegerField(widget=TextInput(attrs={'class':'small-input'}),label=_("Countdown (in seconds)"),initial=60, min_value=0)
class ApplicationConfigForm(Form): class ApplicationConfigForm(Form):
error_css_class = 'error' error_css_class = 'error'
required_css_class = 'required' required_css_class = 'required'

View File

@ -16,6 +16,7 @@ from django.utils.translation import ugettext as _
DEFAULT_DATA = { DEFAULT_DATA = {
'event_name': 'OpenSlides', 'event_name': 'OpenSlides',
'event_description': 'Presentation and voting system', 'event_description': 'Presentation and voting system',
'agenda_countdown_time': 60,
'application_min_supporters': 4, 'application_min_supporters': 4,
'application_preamble': 'Die Versammlung möge beschließen,', 'application_preamble': 'Die Versammlung möge beschließen,',
'application_pdf_title': _('Applications'), 'application_pdf_title': _('Applications'),

View File

@ -9,6 +9,11 @@
{{ form_event.as_p }} {{ form_event.as_p }}
</fieldset> </fieldset>
<p></p> <p></p>
<fieldset>
<legend>{%trans "Agenda" %}</legend>
{{ form_agenda.as_p }}
</fieldset>
<p></p>
<fieldset> <fieldset>
<legend>{%trans "Application" %}</legend> <legend>{%trans "Application" %}</legend>
{{ form_application.as_p }} {{ form_application.as_p }}

View File

@ -16,7 +16,7 @@ from django.contrib import messages
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from utils.utils import template from utils.utils import template
from utils.utils import template, permission_required from utils.utils import template, permission_required
from system.forms import SystemConfigForm, EventConfigForm, ApplicationConfigForm, AssignmentConfigForm from system.forms import SystemConfigForm, EventConfigForm, AgendaConfigForm, ApplicationConfigForm, AssignmentConfigForm
from system.api import config_get, config_set from system.api import config_get, config_set
@permission_required('system.can_manage_system') @permission_required('system.can_manage_system')
@ -45,15 +45,18 @@ def get_system_config(request):
def get_general_config(request): def get_general_config(request):
if request.method == 'POST': if request.method == 'POST':
form_event = EventConfigForm(request.POST, prefix='event') form_event = EventConfigForm(request.POST, prefix='event')
form_agenda = AgendaConfigForm(request.POST, prefix='agenda')
form_application = ApplicationConfigForm(request.POST, prefix='application') form_application = ApplicationConfigForm(request.POST, prefix='application')
form_assignment = AssignmentConfigForm(request.POST, prefix='assignment') form_assignment = AssignmentConfigForm(request.POST, prefix='assignment')
if form_event.is_valid() and form_application.is_valid() and form_assignment.is_valid(): if form_event.is_valid() and form_agenda.is_valid() and form_application.is_valid() and form_assignment.is_valid():
# event form # event form
config_set('event_name', form_event.cleaned_data['event_name']) config_set('event_name', form_event.cleaned_data['event_name'])
config_set('event_description', form_event.cleaned_data['event_description']) config_set('event_description', form_event.cleaned_data['event_description'])
config_set('event_date', form_event.cleaned_data['event_date']) config_set('event_date', form_event.cleaned_data['event_date'])
config_set('event_location', form_event.cleaned_data['event_location']) config_set('event_location', form_event.cleaned_data['event_location'])
config_set('event_organizer', form_event.cleaned_data['event_organizer']) config_set('event_organizer', form_event.cleaned_data['event_organizer'])
# agenda form
config_set('agenda_countdown_time', form_agenda.cleaned_data['agenda_countdown_time'])
# application form # application form
config_set('application_min_supporters', form_application.cleaned_data['application_min_supporters']) config_set('application_min_supporters', form_application.cleaned_data['application_min_supporters'])
config_set('application_preamble', form_application.cleaned_data['application_preamble']) config_set('application_preamble', form_application.cleaned_data['application_preamble'])
@ -78,6 +81,9 @@ def get_general_config(request):
'event_location': config_get('event_location'), 'event_location': config_get('event_location'),
'event_organizer': config_get('event_organizer'), 'event_organizer': config_get('event_organizer'),
}, prefix='event') }, prefix='event')
form_agenda = AgendaConfigForm(initial={
'agenda_countdown_time': config_get('agenda_countdown_time'),
}, prefix='agenda')
form_application = ApplicationConfigForm(initial={ form_application = ApplicationConfigForm(initial={
'application_min_supporters': config_get('application_min_supporters'), 'application_min_supporters': config_get('application_min_supporters'),
'application_preamble': config_get('application_preamble'), 'application_preamble': config_get('application_preamble'),
@ -94,6 +100,7 @@ def get_general_config(request):
}, prefix='assignment') }, prefix='assignment')
return { return {
'form_event': form_event, 'form_event': form_event,
'form_agenda': form_agenda,
'form_application': form_application, 'form_application': form_application,
'form_assignment': form_assignment, 'form_assignment': form_assignment,
} }