Added global chatbox for managers.

This commit is contained in:
Norman Jäckel 2013-12-09 18:03:47 +01:00 committed by Emanuel Schütze
parent f870a75119
commit 42796f6118
9 changed files with 207 additions and 11 deletions

View File

@ -34,6 +34,7 @@ Other:
- Created a poll description field for each assignment-poll. - Created a poll description field for each assignment-poll.
- Added possibility to use custom templates and static files in user data path - Added possibility to use custom templates and static files in user data path
directory. directory.
- Added global chatbox for managers.
Version 1.5.2 (unreleased) Version 1.5.2 (unreleased)
========================== ==========================

100
openslides/core/chatbox.py Normal file
View File

@ -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 '<span style="color:%(color)s;">%(person)s <small class="grey">%(time)s</small>:</span>' % {
'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}

View File

@ -195,8 +195,7 @@ legend + .control-group {
font-weight: bold; font-weight: bold;
} }
/** Left sidebar navigation **/
/** Left sitebar navigation **/
.leftmenu ul { .leftmenu ul {
margin: 0; margin: 0;
list-style: none; list-style: none;
@ -320,6 +319,7 @@ legend + .control-group {
.icon-presentations { .icon-presentations {
background-position: -264px -48px; 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");
@ -370,7 +370,6 @@ legend + .control-group {
background-position: 0; background-position: 0;
} }
/** Responsive **/ /** Responsive **/
@media (max-width: 767px) { @media (max-width: 767px) {
body { body {

View File

@ -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() + '<p>' + message + '</p>');
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;
});
});

View File

@ -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;
}

View File

@ -12,6 +12,7 @@
<link href="{% static 'css/bootstrap.min.css' %}" type="text/css" rel="stylesheet" /> <link href="{% static 'css/bootstrap.min.css' %}" type="text/css" rel="stylesheet" />
<link href="{% static 'css/bootstrap-responsive.min.css' %}" type="text/css" rel="stylesheet" /> <link href="{% static 'css/bootstrap-responsive.min.css' %}" type="text/css" rel="stylesheet" />
<link href="{% static 'css/base.css' %}" type="text/css" rel="stylesheet" /> <link href="{% static 'css/base.css' %}" type="text/css" rel="stylesheet" />
<link href="{% static 'css/chatbox.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" />
{% for stylefile in extra_stylefiles %} {% for stylefile in extra_stylefiles %}
<link href="{% static stylefile %}" type="text/css" rel="stylesheet" /> <link href="{% static stylefile %}" type="text/css" rel="stylesheet" />
@ -36,6 +37,14 @@
</div> </div>
</form> &nbsp; </form> &nbsp;
<!-- Chatbox button -->
{% if chat_messages != None %}
<button class="btn" id="open-chatbox">
<i class="icon-comments"></i>
{% trans 'Chat' %}
</button>
{% endif %}
<!-- login/logout button --> <!-- login/logout button -->
<div class="btn-group"> <div class="btn-group">
{% if user.is_authenticated %} {% if user.is_authenticated %}
@ -109,10 +118,15 @@
</div><!--/#container--> </div><!--/#container-->
{% endblock %}<!--/body--> {% endblock %}<!--/body-->
{% include 'core/chatbox.html' %}
<!-- JavaScript (Placed at the end of the document so the pages load faster) --> <!-- JavaScript (Placed at the end of the document so the pages load faster) -->
<script src="{% static 'js/jquery/jquery.min.js' %}" type="text/javascript"></script> <script src="{% static 'js/jquery/jquery.min.js' %}" type="text/javascript"></script>
<script src="{% static 'js/bootstrap.min.js' %}" type="text/javascript"></script> <script src="{% static 'js/bootstrap.min.js' %}" type="text/javascript"></script>
<script src="{% static 'js/utils.js' %}" type="text/javascript"></script> <script src="{% static 'js/utils.js' %}" type="text/javascript"></script>
<script src="{% static 'js/sockjs-0.3.min.js' %}" type="text/javascript"></script>
<script src="{% static 'js/utils.js' %}" type="text/javascript"></script>
<script src="{% static 'js/chatbox.js' %}" type="text/javascript"></script>
<script src="{% url 'django.views.i18n.javascript_catalog' %}" type="text/javascript"></script> <script src="{% url 'django.views.i18n.javascript_catalog' %}" type="text/javascript"></script>
{% for javascript in extra_javascript %} {% for javascript in extra_javascript %}
<script src="{% static javascript %}" type="text/javascript"></script> <script src="{% static javascript %}" type="text/javascript"></script>

View File

@ -0,0 +1,24 @@
{% load i18n %}
{% if chat_messages != None %}
<div class="hidden well" id="chatbox">
<div>
<h1>Chatbox</h1>
<button class="btn btn-mini pull-right" id="close-chatbox"><i class="icon-remove"></i></button>
</div>
<div id="chatbox-text">
{% for message in chat_messages %}
<p>{{ message.html_time_and_person|safe }} {{ message.message }}</p>
{% endfor %}
</div>
<hr />
<form class="form-inline" id="chatbox-form">
<div class="input-append">
<input id="chatbox-form-input" type="text" />
<button id="chatbox-form-send" type="submit" class="btn btn-primary" title="{% trans 'Send message' %}">
<i class="icon-ok icon-white"></i>
</button>
</div>
</form>
</div>
{% endif %}

View File

@ -104,6 +104,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.static', 'django.core.context_processors.static',
'openslides.utils.auth.anonymous_context_additions', 'openslides.utils.auth.anonymous_context_additions',
'openslides.utils.main_menu.main_menu_entries', 'openslides.utils.main_menu.main_menu_entries',
'openslides.core.chatbox.chat_messages_context_processor',
) )
CACHES = { CACHES = {

View File

@ -79,17 +79,20 @@ def run_tornado(addr, port, reload=False):
url_string = 'http://%s:%s' % (addr, port) url_string = 'http://%s:%s' % (addr, port)
print _("Starting OpenSlides' tornado webserver listening to %(url_string)s") % {'url_string': url_string} print _("Starting OpenSlides' tornado webserver listening to %(url_string)s") % {'url_string': url_string}
socket_js_router = SockJSRouter(ProjectorSocketHandler, '/projector/socket') # Setup WSGIContainer
# Start the application
app = WSGIContainer(Django_WSGIHandler()) 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.STATIC_URL, DjangoStaticFileHandler),
(r'%s(.*)' % settings.MEDIA_URL, StaticFileHandler, {'path': settings.MEDIA_ROOT}), (r'%s(.*)' % settings.MEDIA_URL, StaticFileHandler, {'path': settings.MEDIA_ROOT}),
('.*', FallbackHandler, dict(fallback=app)) ('.*', FallbackHandler, dict(fallback=app))]
], debug=reload)
# 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 = HTTPServer(tornado_app)
server.listen(port=port, server.listen(port=port, address=addr)
address=addr)
IOLoop.instance().start() IOLoop.instance().start()