diff --git a/CHANGELOG b/CHANGELOG index a20c85148..6c8b0a52b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ Other: - Created a poll description field for each assignment-poll. - Added possibility to use custom templates and static files in user data path directory. +- Added global chatbox for managers. Version 1.5.2 (unreleased) ========================== diff --git a/openslides/core/chatbox.py b/openslides/core/chatbox.py new file mode 100644 index 000000000..a359f5cd2 --- /dev/null +++ b/openslides/core/chatbox.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +from datetime import datetime + +from django.conf import settings +from django.contrib.sessions.models import Session +from sockjs.tornado import SockJSConnection + + +class ChatboxSocketHandler(SockJSConnection): + """ + Websocket handler for the chatbox. + """ + clients = set() + + def on_open(self, info): + """ + Checks connecting user and adds his client to the clients list. + """ + from openslides.participant.models import User + + session_key = info.get_cookie(settings.SESSION_COOKIE_NAME).value + session = Session.objects.get(session_key=session_key) + try: + self.user = User.objects.get(pk=session.get_decoded().get('_auth_user_id')) + except User.DoesNotExist: + return_value = False + else: + # TODO: Use correct permission here + if self.user.has_perm('projector.can_manage_projector'): + self.clients.add(self) + return_value = True + else: + return_value = False + return return_value + + def on_message(self, message): + """ + Sends the given message to all clients. + + Also appends the message to the cache and removes old messages if there + are more than 100. + """ + # TODO: Use correct permission here + if self.user.has_perm('projector.can_manage_projector') and message: + message_object = ChatMessage(person=self.user, message=message) + chat_messages.append(message_object) + if len(chat_messages) > 100: + chat_messages.pop(0) + self.broadcast( + self.clients, + '%s %s' % (message_object.html_time_and_person(), + message_object.message)) + + def on_close(self): + """ + Removes client from the clients list. + """ + self.clients.remove(self) + + +class ChatMessage(object): + """ + Class for all chat messages. They are stored in the chat_messages object. + + The argument person has to be a Person object, the argument message has to + be the message as string. The argument color can be a three-tuple of RGB + color values. Default is black (0, 0, 0). + """ + def __init__(self, person, message, color=None): + self.person = person + self.message = message + self.color = color or (0, 0, 0) + self.time = datetime.now() + + def html_time_and_person(self): + """ + Returns a styled prefix for each message using span and small html tags. + """ + return '%(person)s %(time)s:' % { + 'color': 'rgb(%d,%d,%d)' % self.color, + 'person': self.person.clean_name, + 'time': self.time.strftime('%H:%M')} + + +chat_messages = [] +""" +Cache with all messages during livetime of the server. +""" + + +def chat_messages_context_processor(request): + """ + Adds all chat messages to the request context as template context processor. + """ + if True: # TODO: Add permission check here + value = chat_messages + else: + value = None + return {'chat_messages': value} diff --git a/openslides/core/static/css/base.css b/openslides/core/static/css/base.css index 41b767470..c470c6e7d 100644 --- a/openslides/core/static/css/base.css +++ b/openslides/core/static/css/base.css @@ -195,8 +195,7 @@ legend + .control-group { font-weight: bold; } - -/** Left sitebar navigation **/ +/** Left sidebar navigation **/ .leftmenu ul { margin: 0; list-style: none; @@ -320,6 +319,7 @@ legend + .control-group { .icon-presentations { background-position: -264px -48px; } + /** More glyphicons free icons **/ .status_link .icon-on, .icon-checked-new { background-image: url("../img/glyphicons_152_check.png"); @@ -370,7 +370,6 @@ legend + .control-group { background-position: 0; } - /** Responsive **/ @media (max-width: 767px) { body { diff --git a/openslides/core/static/javascript/chatbox.js b/openslides/core/static/javascript/chatbox.js new file mode 100644 index 000000000..a4b61df74 --- /dev/null +++ b/openslides/core/static/javascript/chatbox.js @@ -0,0 +1,35 @@ +// Functions for OpenSlides manager chatbox + +$("button#open-chatbox").click(function(){ + $("div#chatbox").removeClass('hidden'); +}); + +$("button#close-chatbox").click(function(){ + $("div#chatbox").addClass('hidden'); +}); + +$(document).ready(function(){ + //~ var transports = $('#protocols input:checked').map(function(){ + //~ return $(this).attr('id'); + //~ }).get(); + + function print_message(message) { + var chatbox = $('#chatbox-text'); + chatbox.html(chatbox.html() + '

' + message + '

'); + chatbox.scrollTop(chatbox.scrollTop() + 10000); + } + + //~ var connection = new SockJS('http://' + window.location.host + '/chatbox', transports); + var connection = new SockJS('http://' + window.location.host + '/core/chatbox'); + + connection.onmessage = function(event) { + print_message(event.data); + }; + + $("#chatbox-form-send").click(function(){ + var message = $('#chatbox-form-input').val(); + connection.send(message); + $('#chatbox-form-input').val('').focus(); + return false; + }); +}); diff --git a/openslides/core/static/styles/chatbox.css b/openslides/core/static/styles/chatbox.css new file mode 100644 index 000000000..87c2c6fda --- /dev/null +++ b/openslides/core/static/styles/chatbox.css @@ -0,0 +1,19 @@ +/** Chatbox **/ +.icon-comments { + background-position: -240px -120px; +} +div#chatbox { + width: 35%; +} +div#chatbox h1 { + border-bottom: none; + font-size: 14px; + margin-bottom: 10px; + float: left; +} +div#chatbox div#chatbox-text { + clear: both; + min-height: 150px; + max-height:150px; + overflow-y:scroll; +} diff --git a/openslides/core/templates/base.html b/openslides/core/templates/base.html index acf0c24a5..f5ca763de 100644 --- a/openslides/core/templates/base.html +++ b/openslides/core/templates/base.html @@ -12,6 +12,7 @@ + {% for stylefile in extra_stylefiles %} @@ -36,6 +37,14 @@   + + {% if chat_messages != None %} + + {% endif %} +
{% if user.is_authenticated %} @@ -109,10 +118,15 @@
{% endblock %} + {% include 'core/chatbox.html' %} + + + + {% for javascript in extra_javascript %} diff --git a/openslides/core/templates/core/chatbox.html b/openslides/core/templates/core/chatbox.html new file mode 100644 index 000000000..a54615845 --- /dev/null +++ b/openslides/core/templates/core/chatbox.html @@ -0,0 +1,24 @@ +{% load i18n %} + +{% if chat_messages != None %} + +{% endif %} diff --git a/openslides/global_settings.py b/openslides/global_settings.py index b95e225a8..e72824234 100644 --- a/openslides/global_settings.py +++ b/openslides/global_settings.py @@ -104,6 +104,7 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.static', 'openslides.utils.auth.anonymous_context_additions', 'openslides.utils.main_menu.main_menu_entries', + 'openslides.core.chatbox.chat_messages_context_processor', ) CACHES = { diff --git a/openslides/utils/tornado_webserver.py b/openslides/utils/tornado_webserver.py index 2558f141a..a74b4d6fc 100644 --- a/openslides/utils/tornado_webserver.py +++ b/openslides/utils/tornado_webserver.py @@ -79,17 +79,20 @@ def run_tornado(addr, port, reload=False): url_string = 'http://%s:%s' % (addr, port) print _("Starting OpenSlides' tornado webserver listening to %(url_string)s") % {'url_string': url_string} - socket_js_router = SockJSRouter(ProjectorSocketHandler, '/projector/socket') - - # Start the application + # Setup WSGIContainer app = WSGIContainer(Django_WSGIHandler()) - tornado_app = Application(socket_js_router.urls + [ + + # Collect urls + projectpr_socket_js_router = SockJSRouter(ProjectorSocketHandler, '/projector/socket') + from openslides.core.chatbox import ChatboxSocketHandler + chatbox_socket_js_router = SockJSRouter(ChatboxSocketHandler, '/core/chatbox') + other_urls = [ (r"%s(.*)" % settings.STATIC_URL, DjangoStaticFileHandler), (r'%s(.*)' % settings.MEDIA_URL, StaticFileHandler, {'path': settings.MEDIA_ROOT}), - ('.*', FallbackHandler, dict(fallback=app)) - ], debug=reload) + ('.*', FallbackHandler, dict(fallback=app))] + # Start the application + tornado_app = Application(projectpr_socket_js_router.urls + chatbox_socket_js_router.urls + other_urls, debug=reload) server = HTTPServer(tornado_app) - server.listen(port=port, - address=addr) + server.listen(port=port, address=addr) IOLoop.instance().start()