added possibility to show pdfs on the projector
This commit is contained in:
parent
95ea800958
commit
bd332bd3d5
@ -250,6 +250,8 @@ OpenSlides uses the following projects or parts of them:
|
|||||||
|
|
||||||
* `Django haystack <http://haystacksearch.org>`_, License: BSD
|
* `Django haystack <http://haystacksearch.org>`_, License: BSD
|
||||||
|
|
||||||
|
* `pdf.js <http://mozilla.github.io/pdf.js/>`_, License: Apache License v2.0
|
||||||
|
|
||||||
* `Pillow <https://github.com/python-imaging/Pillow/>`_, License: Standard
|
* `Pillow <https://github.com/python-imaging/Pillow/>`_, License: Standard
|
||||||
PIL License
|
PIL License
|
||||||
|
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import slides # noqa
|
@ -12,16 +12,20 @@
|
|||||||
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
|
|
||||||
|
from openslides.projector.models import SlideMixin
|
||||||
from openslides.utils.person.models import PersonField
|
from openslides.utils.person.models import PersonField
|
||||||
|
|
||||||
|
|
||||||
class Mediafile(models.Model):
|
class Mediafile(SlideMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
Class for uploaded files which can be delivered under a certain url.
|
Class for uploaded files which can be delivered under a certain url.
|
||||||
"""
|
"""
|
||||||
|
slide_callback_name = 'mediafile'
|
||||||
|
PRESENTABLE_FILE_TYPES = ['application/pdf']
|
||||||
|
|
||||||
mediafile = models.FileField(upload_to='file', verbose_name=ugettext_lazy("File"))
|
mediafile = models.FileField(upload_to='file', verbose_name=ugettext_lazy("File"))
|
||||||
"""
|
"""
|
||||||
@ -41,6 +45,12 @@ class Mediafile(models.Model):
|
|||||||
filetype = models.CharField(max_length=255, editable=False)
|
filetype = models.CharField(max_length=255, editable=False)
|
||||||
"""A string used to show the type of the file."""
|
"""A string used to show the type of the file."""
|
||||||
|
|
||||||
|
is_presentable = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=ugettext_lazy("Is Presentable"),
|
||||||
|
help_text=ugettext_lazy("If checked, this file can be presented on the projector. "
|
||||||
|
"Currently, this is only possible for PDFs."))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""
|
"""
|
||||||
Meta class for the mediafile model.
|
Meta class for the mediafile model.
|
||||||
@ -67,15 +77,16 @@ class Mediafile(models.Model):
|
|||||||
self.filetype = ugettext_noop('unknown')
|
self.filetype = ugettext_noop('unknown')
|
||||||
return super(Mediafile, self).save(*args, **kwargs)
|
return super(Mediafile, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@models.permalink
|
|
||||||
def get_absolute_url(self, link='update'):
|
def get_absolute_url(self, link='update'):
|
||||||
"""
|
"""
|
||||||
Returns the URL to a mediafile. The link can be 'update' or 'delete'.
|
Returns the URL to a mediafile. The link can be 'projector',
|
||||||
|
'update' or 'delete'.
|
||||||
"""
|
"""
|
||||||
if link == 'update' or link == 'edit': # 'edit' ist only used until utils/views.py is fixed
|
if link == 'update' or link == 'edit': # 'edit' ist only used until utils/views.py is fixed
|
||||||
return ('mediafile_update', [str(self.id)])
|
return reverse('mediafile_update', kwargs={'pk': str(self.id)})
|
||||||
if link == 'delete':
|
if link == 'delete':
|
||||||
return ('mediafile_delete', [str(self.id)])
|
return reverse('mediafile_delete', kwargs={'pk': str(self.id)})
|
||||||
|
return super(Mediafile, self).get_absolute_url(link)
|
||||||
|
|
||||||
def get_filesize(self):
|
def get_filesize(self):
|
||||||
"""
|
"""
|
||||||
|
34
openslides/mediafile/slides.py
Normal file
34
openslides/mediafile/slides.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
from openslides.config.api import config
|
||||||
|
from openslides.projector.api import register_slide
|
||||||
|
|
||||||
|
from .models import Mediafile
|
||||||
|
|
||||||
|
|
||||||
|
def mediafile_presentation_as_slide(**kwargs):
|
||||||
|
"""
|
||||||
|
Return the html code for a presentation of a Mediafile.
|
||||||
|
|
||||||
|
At the moment, only the presentation of pdfs is supported.
|
||||||
|
"""
|
||||||
|
file_pk = kwargs.get('pk', None)
|
||||||
|
page_num = kwargs.get('page_num', 1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
pdf = Mediafile.objects.get(
|
||||||
|
pk=file_pk,
|
||||||
|
filetype__in=Mediafile.PRESENTABLE_FILE_TYPES,
|
||||||
|
is_presentable=True)
|
||||||
|
except Mediafile.DoesNotExist:
|
||||||
|
# TODO what doing, if a wrong pk is given?
|
||||||
|
pdf = None
|
||||||
|
context = {'pdf': pdf, 'page_num': page_num,
|
||||||
|
'fullscreen': config['pdf_fullscreen']}
|
||||||
|
return render_to_string('mediafile/presentation_slide.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
register_slide('mediafile', mediafile_presentation_as_slide)
|
7070
openslides/mediafile/static/javascript/pdf.js
Normal file
7070
openslides/mediafile/static/javascript/pdf.js
Normal file
File diff suppressed because it is too large
Load Diff
39219
openslides/mediafile/static/javascript/pdf.worker.js
vendored
Normal file
39219
openslides/mediafile/static/javascript/pdf.worker.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
95
openslides/mediafile/static/javascript/pdf_presenter.js
Normal file
95
openslides/mediafile/static/javascript/pdf_presenter.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
var pdf = PDFJS.getDocument(projector['pdf_url']);
|
||||||
|
|
||||||
|
projector['load_pdf_page'] = function(page) {
|
||||||
|
projector['pdf_page_num'] = page;
|
||||||
|
pdf.then(function(pdf) {
|
||||||
|
pdf.getPage(page).then(set_convas_size);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
projector['load_pdf'] = function(data) {
|
||||||
|
projector['pdf_url'] = data['url'];
|
||||||
|
projector['pdf_page_num'] = data['page_num'];
|
||||||
|
pdf = PDFJS.getDocument(projector['pdf_url']);
|
||||||
|
projector['load_pdf_page'](projector['pdf_page_num']);
|
||||||
|
};
|
||||||
|
|
||||||
|
projector['toggle_fullscreen'] = function(fullscreen) {
|
||||||
|
projector['pdf_fullscreen'] = fullscreen;
|
||||||
|
content = $('#content');
|
||||||
|
presentation = $('#presentation');
|
||||||
|
footer = $('#footer');
|
||||||
|
body = $('body');
|
||||||
|
if (fullscreen) {
|
||||||
|
content.addClass('fullscreen');
|
||||||
|
presentation.addClass('fullscreen');
|
||||||
|
footer.addClass('black');
|
||||||
|
body.addClass('black');
|
||||||
|
} else {
|
||||||
|
content.removeClass('fullscreen');
|
||||||
|
presentation.removeClass('fullscreen');
|
||||||
|
footer.removeClass('black');
|
||||||
|
body.removeClass('black');
|
||||||
|
}
|
||||||
|
$(window).resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
function scale_to_height(page) {
|
||||||
|
return page.getViewport(window.innerHeight / page.getViewport(1.0).height);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scale_to_width(page) {
|
||||||
|
return page.getViewport(window.innerWidth / page.getViewport(1.0).width);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_correct_viewport(page, canvas) {
|
||||||
|
if(window.innerWidth > window.innerHeight) {
|
||||||
|
viewport = scale_to_height(page);
|
||||||
|
if (viewport.width > window.innerWidth) {
|
||||||
|
viewport = scale_to_width(page);
|
||||||
|
canvas.height = viewport.height;
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
} else {
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
canvas.width = viewport.width;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewport = scale_to_width(page);
|
||||||
|
if (viewport.height > window.innerHeight) {
|
||||||
|
viewport = scale_to_height(page);
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
canvas.width = viewport.width;
|
||||||
|
} else {
|
||||||
|
canvas.height = viewport.height;
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return viewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_convas_size(page) {
|
||||||
|
var canvas = document.getElementById('presentation');
|
||||||
|
var context = canvas.getContext('2d');
|
||||||
|
if (projector['pdf_fullscreen']) {
|
||||||
|
viewport = get_correct_viewport(page, canvas);
|
||||||
|
} else {
|
||||||
|
viewport = page.getViewport(window.innerWidth / page.getViewport(1.0).width);
|
||||||
|
canvas.height = viewport.height;
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
}
|
||||||
|
page.render({canvasContext: context, viewport: viewport});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
$(window).resize(function() {
|
||||||
|
projector['load_pdf_page'](projector['pdf_page_num']);
|
||||||
|
});
|
||||||
|
if (projector['pdf_fullscreen']) {
|
||||||
|
if (!$('#content').hasClass('fullscreen')) {
|
||||||
|
$('#content').addClass('fullscreen');
|
||||||
|
$('#footer').addClass('black');
|
||||||
|
$('body').addClass('black');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(window).resize();
|
||||||
|
});
|
@ -36,6 +36,12 @@
|
|||||||
<span style="width: 1px; white-space: nowrap;">
|
<span style="width: 1px; white-space: nowrap;">
|
||||||
<a href="{{ mediafile|absolute_url:'update' }}" rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini"><i class="icon-pencil"></i></a>
|
<a href="{{ mediafile|absolute_url:'update' }}" rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini"><i class="icon-pencil"></i></a>
|
||||||
<a href="{{ mediafile|absolute_url:'delete' }}" rel="tooltip" data-original-title="{% trans 'Delete' %}" class="btn btn-mini"><i class="icon-remove"></i></a>
|
<a href="{{ mediafile|absolute_url:'delete' }}" rel="tooltip" data-original-title="{% trans 'Delete' %}" class="btn btn-mini"><i class="icon-remove"></i></a>
|
||||||
|
{% if mediafile.is_presentable %}{% if mediafile.filetype in mediafile.PRESENTABLE_FILE_TYPES %}
|
||||||
|
<a href="{{ mediafile|absolute_url:'projector' }}" class="activate_link choose-pdf btn {% if mediafile.is_active_slide %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
||||||
|
<i class="icon-facetime-video {% if mediafile.is_active_slide %}icon-white{% endif %}">
|
||||||
|
</i>
|
||||||
|
</a>
|
||||||
|
{% endif %}{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
45
openslides/mediafile/templates/mediafile/pdfs_widget.html
Normal file
45
openslides/mediafile/templates/mediafile/pdfs_widget.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
<form action="{% url 'target_pdf_page' %}" method="GET" class="set-page-form">
|
||||||
|
<div class="input-prepend" style="margin-bottom:0;">
|
||||||
|
<a class="btn go-first-page">
|
||||||
|
<i class="icon-fast-backward"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn pdf-page-ctl" href="{% url 'prev_pdf_page' %}">
|
||||||
|
<i class="icon-backward"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn pdf-page-ctl" href="{% url 'next_pdf_page' %}">
|
||||||
|
<i class="icon-forward"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-append" style="margin-bottom:0;">
|
||||||
|
<a class="btn pdf-toggle-fullscreen {%if pdf_fullscreen %}btn-primary{% endif %}" href="{% url 'toggle_fullscreen' %}">
|
||||||
|
<i class="icon-fullscreen {%if pdf_fullscreen %}icon-white{% endif %}"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-append input-prepend" style="margin-bottom:0;">
|
||||||
|
<span class="add-on">Page:</span>
|
||||||
|
<input id="page_num" name="page_num" type="number" style="width: 22px;" value="{{ current_page }}">
|
||||||
|
|
||||||
|
<button type="submit" id="go_to_page" class="btn" style="width: 40px;">
|
||||||
|
<i class="icon-refresh"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<ul style="line-height: 180%">
|
||||||
|
{% for pdf in pdfs %}
|
||||||
|
<li class="{% if pdf.is_active_slide %}activeline{% endif %}">
|
||||||
|
<a href="{{ pdf|absolute_url:'projector' }}" class="activate_link choose-pdf btn {% if pdf.is_active_slide %}btn-primary{% endif %} btn-mini" title="{% trans 'Show' %}">
|
||||||
|
<i class="icon-facetime-video {% if pdf.is_active_slide %}icon-white{% endif %}"></i>
|
||||||
|
</a> {{ pdf }}
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li>{% trans 'No PDFs available.' %}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
@ -0,0 +1,15 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<script src="{% static 'javascript/pdf.js' %}" type="text/javascript"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
projector['pdf_url'] = '{{ pdf.mediafile.url }}';
|
||||||
|
projector['pdf_page_num'] = {{ page_num }};
|
||||||
|
projector['pdf_fullscreen'] = {% if fullscreen %}true{% else %}false{% endif %};
|
||||||
|
PDFJS.workerSrc = "{% static 'javascript/pdf.worker.js' %}";
|
||||||
|
</script>
|
||||||
|
<script src="{% static 'javascript/pdf_presenter.js' %}" type="text/javascript"></script>
|
||||||
|
|
||||||
|
<div class="canvas-container">
|
||||||
|
<canvas id="presentation" class="{% if fullscreen %}fullscreen{% endif %}"></canvas>
|
||||||
|
</div>
|
@ -31,4 +31,12 @@ urlpatterns = patterns(
|
|||||||
url(r'^(?P<pk>\d+)/del/$',
|
url(r'^(?P<pk>\d+)/del/$',
|
||||||
views.MediafileDeleteView.as_view(),
|
views.MediafileDeleteView.as_view(),
|
||||||
name='mediafile_delete'),
|
name='mediafile_delete'),
|
||||||
|
url(r'^pdf/next/$', views.PdfNextView.as_view(), name='next_pdf_page'),
|
||||||
|
url(r'^pdf/prev/$', views.PdfPreviousView.as_view(), name='prev_pdf_page'),
|
||||||
|
url(r'^pdf/target_page/$',
|
||||||
|
views.PdfGoToPageView.as_view(),
|
||||||
|
name='target_pdf_page'),
|
||||||
|
url(r'^pdf/toggle_fullscreen/$',
|
||||||
|
views.PdfToggleFullscreenView.as_view(),
|
||||||
|
name='toggle_fullscreen')
|
||||||
)
|
)
|
||||||
|
@ -11,10 +11,16 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.http import HttpResponse
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
from openslides.config.api import config
|
||||||
|
from openslides.projector.api import get_active_slide
|
||||||
|
from openslides.projector.projector import Widget
|
||||||
from openslides.utils.template import Tab
|
from openslides.utils.template import Tab
|
||||||
from openslides.utils.views import CreateView, DeleteView, ListView, UpdateView
|
from openslides.utils.tornado_webserver import ProjectorSocketHandler
|
||||||
|
from openslides.utils.views import (AjaxView, CreateView, DeleteView, RedirectView, ListView,
|
||||||
|
UpdateView)
|
||||||
|
|
||||||
from .forms import MediafileNormalUserCreateForm, MediafileUpdateForm
|
from .forms import MediafileNormalUserCreateForm, MediafileUpdateForm
|
||||||
from .models import Mediafile
|
from .models import Mediafile
|
||||||
@ -94,6 +100,132 @@ class MediafileDeleteView(DeleteView):
|
|||||||
return super(MediafileDeleteView, self).on_clicked_yes(*args, **kwargs)
|
return super(MediafileDeleteView, self).on_clicked_yes(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class PdfNavBaseView(AjaxView):
|
||||||
|
"""
|
||||||
|
BaseView for the Pdf Ajax Navigation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_ajax_context(self, *args, **kwargs):
|
||||||
|
return {'current_page': self.active_slide['page_num']}
|
||||||
|
|
||||||
|
def load_other_page(self, active_slide):
|
||||||
|
"""
|
||||||
|
Tell connected clients to load an other pdf page.
|
||||||
|
"""
|
||||||
|
config['projector_active_slide'] = active_slide
|
||||||
|
ProjectorSocketHandler.send_updates(
|
||||||
|
{'calls': {'load_pdf_page': active_slide['page_num']}})
|
||||||
|
|
||||||
|
|
||||||
|
class PdfNextView(PdfNavBaseView):
|
||||||
|
"""
|
||||||
|
Activate the next Page of a pdf and return the number of the current page.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Increment the page number by 1.
|
||||||
|
|
||||||
|
If the page number is set in the active slide, we are the value is
|
||||||
|
incremented by 1. Otherwise, it is the first page and it is set to 2.
|
||||||
|
"""
|
||||||
|
self.active_slide = get_active_slide()
|
||||||
|
if self.active_slide['callback'] == 'mediafile':
|
||||||
|
if 'page_num' not in self.active_slide:
|
||||||
|
self.active_slide['page_num'] = 2
|
||||||
|
else:
|
||||||
|
self.active_slide['page_num'] += 1
|
||||||
|
self.load_other_page(self.active_slide)
|
||||||
|
response = super(PdfNextView, self).get(self, request, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
# no Mediafile is active and the JavaScript should not do anything.
|
||||||
|
response = HttpResponse()
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class PdfPreviousView(PdfNavBaseView):
|
||||||
|
"""
|
||||||
|
Activate the previous Page of a pdf and return the number of the current page.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Decrement the page number by 1.
|
||||||
|
|
||||||
|
If the page number is set and it is greater than 1, it is decremented
|
||||||
|
by 1. Otherwise, it is the first page and nothing happens.
|
||||||
|
"""
|
||||||
|
self.active_slide = get_active_slide()
|
||||||
|
response = None
|
||||||
|
if self.active_slide['callback'] == 'mediafile':
|
||||||
|
if 'page_num' in self.active_slide and self.active_slide['page_num'] > 1:
|
||||||
|
self.active_slide['page_num'] -= 1
|
||||||
|
self.load_other_page(self.active_slide)
|
||||||
|
response = super(PdfPreviousView, self).get(self, request, *args, **kwargs)
|
||||||
|
if not response:
|
||||||
|
response = HttpResponse()
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class PdfGoToPageView(PdfNavBaseView):
|
||||||
|
"""
|
||||||
|
Activate the page set in the textfield.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
target_page = int(request.GET.get('page_num'))
|
||||||
|
self.active_slide = get_active_slide()
|
||||||
|
if target_page:
|
||||||
|
self.active_slide['page_num'] = target_page
|
||||||
|
self.load_other_page(self.active_slide)
|
||||||
|
response = super(PdfGoToPageView, self).get(self, request, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
response = HttpResponse()
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class PdfToggleFullscreenView(RedirectView):
|
||||||
|
"""
|
||||||
|
Toggle fullscreen mode for pdf presentations.
|
||||||
|
"""
|
||||||
|
allow_ajax = True
|
||||||
|
url_name = 'dashboard'
|
||||||
|
|
||||||
|
def get_ajax_context(self, *args, **kwargs):
|
||||||
|
config['pdf_fullscreen'] = not config['pdf_fullscreen']
|
||||||
|
active_slide = get_active_slide()
|
||||||
|
if active_slide['callback'] == 'mediafile':
|
||||||
|
ProjectorSocketHandler.send_updates(
|
||||||
|
{'calls': {'toggle_fullscreen': config['pdf_fullscreen']}})
|
||||||
|
return {'fullscreen': config['pdf_fullscreen']}
|
||||||
|
|
||||||
|
|
||||||
|
def get_widgets(request):
|
||||||
|
"""
|
||||||
|
Return the widgets of the projector app
|
||||||
|
"""
|
||||||
|
widgets = []
|
||||||
|
|
||||||
|
# PDF-Presentation widget
|
||||||
|
pdfs = Mediafile.objects.filter(
|
||||||
|
filetype__in=Mediafile.PRESENTABLE_FILE_TYPES,
|
||||||
|
is_presentable=True
|
||||||
|
)
|
||||||
|
current_page = get_active_slide().get('page_num', 1)
|
||||||
|
widgets.append(Widget(
|
||||||
|
request,
|
||||||
|
name='presentations',
|
||||||
|
display_name=_('Presentations'),
|
||||||
|
template='mediafile/pdfs_widget.html',
|
||||||
|
context={'pdfs': pdfs, 'current_page': current_page,
|
||||||
|
'pdf_fullscreen': config['pdf_fullscreen']},
|
||||||
|
permission_required='projector.can_manage_projector',
|
||||||
|
default_column=1,
|
||||||
|
default_weight=75))
|
||||||
|
|
||||||
|
return widgets
|
||||||
|
|
||||||
|
|
||||||
def register_tab(request):
|
def register_tab(request):
|
||||||
"""
|
"""
|
||||||
Inserts a new Tab to the views for files.
|
Inserts a new Tab to the views for files.
|
||||||
|
@ -73,12 +73,17 @@ def config_variables(sender, **kwargs):
|
|||||||
name='projector_active_overlays',
|
name='projector_active_overlays',
|
||||||
default_value=[])
|
default_value=[])
|
||||||
|
|
||||||
|
projector_pdf_fullscreen = ConfigVariable(
|
||||||
|
name='pdf_fullscreen',
|
||||||
|
default_value=False)
|
||||||
|
|
||||||
return ConfigPage(
|
return ConfigPage(
|
||||||
title='No title here', url='bar', required_permission=None, variables=(
|
title='No title here', url='bar', required_permission=None, variables=(
|
||||||
projector, projector_message,
|
projector, projector_message,
|
||||||
countdown_time, countdown_start_stamp, countdown_pause_stamp,
|
countdown_time, countdown_start_stamp, countdown_pause_stamp,
|
||||||
countdown_state, projector_scale, projector_scroll,
|
countdown_state, projector_scale, projector_scroll,
|
||||||
projector_active_overlays, projector_js_cache))
|
projector_active_overlays, projector_js_cache,
|
||||||
|
projector_pdf_fullscreen))
|
||||||
|
|
||||||
|
|
||||||
@receiver(projector_overlays, dispatch_uid="projector_countdown")
|
@receiver(projector_overlays, dispatch_uid="projector_countdown")
|
||||||
|
@ -22,7 +22,7 @@ function restoreOrder() {
|
|||||||
var colid = value.id;
|
var colid = value.id;
|
||||||
var cookieName = "cookie-" + colid;
|
var cookieName = "cookie-" + colid;
|
||||||
var cookie = $.cookie(cookieName);
|
var cookie = $.cookie(cookieName);
|
||||||
if ( cookie == null ) { return; }
|
if ( cookie === null ) { return; }
|
||||||
var IDs = cookie.split(",");
|
var IDs = cookie.split(",");
|
||||||
for (var i = 0, n = IDs.length; i < n; i++ ) {
|
for (var i = 0, n = IDs.length; i < n; i++ ) {
|
||||||
var widgetID = IDs[i];
|
var widgetID = IDs[i];
|
||||||
@ -81,7 +81,7 @@ $(function() {
|
|||||||
$('#countdown_play').show();
|
$('#countdown_play').show();
|
||||||
$('#countdown_stop').hide();
|
$('#countdown_stop').hide();
|
||||||
}
|
}
|
||||||
$('#countdown_time').val(data['countdown_time'])
|
$('#countdown_time').val(data['countdown_time']);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -123,6 +123,55 @@ $(function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// control pdf pages
|
||||||
|
$('.pdf-page-ctl').click(function(event){
|
||||||
|
event.preventDefault();
|
||||||
|
var link = $(this);
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET',
|
||||||
|
url: link.attr('href'),
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(data) {
|
||||||
|
if (typeof data.current_page !== 'undefined') {
|
||||||
|
$('#page_num').val(data.current_page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.set-page-form').submit(function() {
|
||||||
|
$(this).ajaxSubmit();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.go-first-page').click(function() {
|
||||||
|
$('#page_num').val('1');
|
||||||
|
$('.set-page-form').ajaxSubmit();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.pdf-toggle-fullscreen').click(function(event){
|
||||||
|
event.preventDefault();
|
||||||
|
var link = $(this);
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET',
|
||||||
|
url: link.attr('href'),
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(data) {
|
||||||
|
if(data.fullscreen) {
|
||||||
|
if (!link.hasClass('btn-primary')) {
|
||||||
|
link.addClass('btn-primary');
|
||||||
|
link.find('i').addClass('icon-white');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (link.hasClass('btn-primary')) {
|
||||||
|
link.removeClass('btn-primary');
|
||||||
|
link.find('i').removeClass('icon-white');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/* comment out this function because '$.browser' has been removed from jquery 1.9, see:
|
/* comment out this function because '$.browser' has been removed from jquery 1.9, see:
|
||||||
http://blog.jquery.com/2013/01/15/jquery-1-9-final-jquery-2-0-beta-migrate-final-released/
|
http://blog.jquery.com/2013/01/15/jquery-1-9-final-jquery-2-0-beta-migrate-final-released/
|
||||||
TODO: use jquery migrate to have $.browser support for IE8;
|
TODO: use jquery migrate to have $.browser support for IE8;
|
||||||
|
@ -66,6 +66,9 @@ var updater = {
|
|||||||
|
|
||||||
updateProjector: function(data) {
|
updateProjector: function(data) {
|
||||||
if (data.content) {
|
if (data.content) {
|
||||||
|
$('#content').removeClass('fullscreen');
|
||||||
|
$('#footer').removeClass('black');
|
||||||
|
$('body').removeClass('black');
|
||||||
$('#content').html(data.content);
|
$('#content').html(data.content);
|
||||||
}
|
}
|
||||||
if (data.overlays) {
|
if (data.overlays) {
|
||||||
|
@ -86,12 +86,11 @@ body{
|
|||||||
top: 150px;
|
top: 150px;
|
||||||
right: 40px;
|
right: 40px;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
transition: all 1s;
|
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
|
transition-property: margin, font-size;
|
||||||
|
transition-duration: 1s;
|
||||||
}
|
}
|
||||||
#content .scroll {
|
|
||||||
transition: margin 1s;
|
|
||||||
}
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2.25em;
|
font-size: 2.25em;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
@ -193,3 +192,37 @@ tr.total td {
|
|||||||
td.elected {
|
td.elected {
|
||||||
background-color: #BED4DE !important;
|
background-color: #BED4DE !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* PDF Presentation */
|
||||||
|
|
||||||
|
.canvas-container {
|
||||||
|
width: 100%;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#presentation {
|
||||||
|
position: relative;
|
||||||
|
left:-75px;
|
||||||
|
top: -77px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#presentation.fullscreen {
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen {
|
||||||
|
z-index: 1000!important;
|
||||||
|
left: 0!important;
|
||||||
|
top: 0!important;
|
||||||
|
right: 0!important;
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer.black {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.black {
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
@ -9,6 +9,19 @@
|
|||||||
<link href="{% static 'styles/projector.css' %}" type="text/css" rel="stylesheet">
|
<link href="{% static 'styles/projector.css' %}" type="text/css" rel="stylesheet">
|
||||||
<link href="{% static 'img/favicon.png' %}" type="image/png" rel="shortcut icon">
|
<link href="{% static 'img/favicon.png' %}" type="image/png" rel="shortcut icon">
|
||||||
<title>{% trans 'Projector' %} – {{ 'event_name'|get_config }}</title>
|
<title>{% trans 'Projector' %} – {{ 'event_name'|get_config }}</title>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{% static 'javascript/jquery.min.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'javascript/sockjs-0.3.min.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'javascript/projector.js' %}"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
{% for js in overlay_js %}
|
||||||
|
projector.update_data({{ js|safe }});
|
||||||
|
{% endfor %}
|
||||||
|
{% for key, value in calls.items %}
|
||||||
|
projector.{{ key }}({{ value }});
|
||||||
|
{% endfor %}
|
||||||
|
</script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="header">
|
<div id="header">
|
||||||
@ -34,16 +47,5 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="{% static 'javascript/jquery.min.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'javascript/sockjs-0.3.min.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'javascript/projector.js' %}"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
{% for js in overlay_js %}
|
|
||||||
projector.update_data({{ js|safe }});
|
|
||||||
{% endfor %}
|
|
||||||
{% for key, value in calls.items %}
|
|
||||||
projector.{{ key }}({{ value }});
|
|
||||||
{% endfor %}
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -18,9 +18,11 @@ from django.shortcuts import redirect
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
|
from openslides.utils.tornado_webserver import ProjectorSocketHandler
|
||||||
from openslides.utils.template import Tab
|
from openslides.utils.template import Tab
|
||||||
from openslides.utils.views import (AjaxMixin, CreateView, DeleteView,
|
from openslides.utils.views import (AjaxMixin, CreateView, DeleteView,
|
||||||
RedirectView, TemplateView, UpdateView)
|
RedirectView, TemplateView, UpdateView)
|
||||||
|
from openslides.mediafile.models import Mediafile
|
||||||
|
|
||||||
from .api import (call_on_projector, get_active_slide, get_all_widgets,
|
from .api import (call_on_projector, get_active_slide, get_all_widgets,
|
||||||
get_overlays, get_projector_content, get_projector_overlays,
|
get_overlays, get_projector_content, get_projector_overlays,
|
||||||
@ -84,6 +86,16 @@ class ActivateView(RedirectView):
|
|||||||
allow_ajax = True
|
allow_ajax = True
|
||||||
|
|
||||||
def pre_redirect(self, request, *args, **kwargs):
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
|
if kwargs['callback'] == 'mediafile' and \
|
||||||
|
get_active_slide()['callback'] == 'mediafile':
|
||||||
|
# If the current slide is a pdf and the new page is also a slide, we dont have to use
|
||||||
|
# set_active_slide, because is causes a content reload.
|
||||||
|
kwargs.update({'page_num': 1, 'pk': request.GET.get('pk')})
|
||||||
|
url = Mediafile.objects.get(pk=kwargs['pk'], is_presentable=True).mediafile.url
|
||||||
|
config['projector_active_slide'] = kwargs
|
||||||
|
ProjectorSocketHandler.send_updates(
|
||||||
|
{'calls': {'load_pdf': {'url': url, 'page_num': kwargs['page_num']}}})
|
||||||
|
else:
|
||||||
set_active_slide(kwargs['callback'], kwargs=dict(request.GET.items()))
|
set_active_slide(kwargs['callback'], kwargs=dict(request.GET.items()))
|
||||||
config['projector_scroll'] = config.get_default('projector_scroll')
|
config['projector_scroll'] = config.get_default('projector_scroll')
|
||||||
config['projector_scale'] = config.get_default('projector_scale')
|
config['projector_scale'] = config.get_default('projector_scale')
|
||||||
|
@ -46,6 +46,10 @@ $(function () {
|
|||||||
}
|
}
|
||||||
link.addClass('btn-primary');
|
link.addClass('btn-primary');
|
||||||
link.children('i').addClass('icon-white');
|
link.children('i').addClass('icon-white');
|
||||||
|
// set page_num to 1 if a pdf is activated
|
||||||
|
if ( link.hasClass('choose-pdf') ) {
|
||||||
|
$('#page_num').val(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -322,7 +322,9 @@ legend + .control-group {
|
|||||||
.icon-append_to_list_of_speakers {
|
.icon-append_to_list_of_speakers {
|
||||||
background-position: -48px -144px;
|
background-position: -48px -144px;
|
||||||
}
|
}
|
||||||
|
.icon-presentations {
|
||||||
|
background-position: -264px -48px;
|
||||||
|
}
|
||||||
/** More glyphicons free icons **/
|
/** More glyphicons free icons **/
|
||||||
.status_link .icon-on, .icon-checked-new {
|
.status_link .icon-on, .icon-checked-new {
|
||||||
background-image: url("../img/glyphicons_152_check.png");
|
background-image: url("../img/glyphicons_152_check.png");
|
||||||
|
Loading…
Reference in New Issue
Block a user