diff --git a/CHANGELOG b/CHANGELOG index ce2243963..1f2d1d363 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -96,6 +96,7 @@ Other: - Added new caching system with support for Redis. - Support https as websocket protocol (wss). - Added migration path from 2.0. +- Accelerated startup process (send all data to the client after login). Version 2.0 (2016-04-18) diff --git a/openslides/agenda/apps.py b/openslides/agenda/apps.py index 81e71f295..8c94160b0 100644 --- a/openslides/agenda/apps.py +++ b/openslides/agenda/apps.py @@ -35,3 +35,7 @@ class AgendaAppConfig(AppConfig): # Register viewsets. router.register(self.get_model('Item').get_collection_string(), ItemViewSet) + + def get_startup_elements(self): + from ..utils.collection import Collection + return [Collection(self.get_model('Item').get_collection_string())] diff --git a/openslides/assignments/apps.py b/openslides/assignments/apps.py index 6ce05de3a..6a796d687 100644 --- a/openslides/assignments/apps.py +++ b/openslides/assignments/apps.py @@ -24,3 +24,7 @@ class AssignmentsAppConfig(AppConfig): # Register viewsets. router.register(self.get_model('Assignment').get_collection_string(), AssignmentViewSet) router.register('assignments/poll', AssignmentPollViewSet) + + def get_startup_elements(self): + from ..utils.collection import Collection + return [Collection(self.get_model('Assignment').get_collection_string())] diff --git a/openslides/core/apps.py b/openslides/core/apps.py index c7dc9ceb7..3abd4e47a 100644 --- a/openslides/core/apps.py +++ b/openslides/core/apps.py @@ -14,10 +14,10 @@ class CoreAppConfig(AppConfig): # Import all required stuff. from django.db.models import signals - from openslides.core.config import config - from openslides.core.signals import post_permission_creation - from openslides.utils.rest_api import router - from openslides.utils.search import index_add_instance, index_del_instance + from .config import config + from .signals import post_permission_creation + from ..utils.rest_api import router + from ..utils.search import index_add_instance, index_del_instance from .config_variables import get_config_variables from .signals import delete_django_app_permissions from .views import ( @@ -55,3 +55,10 @@ class CoreAppConfig(AppConfig): signals.m2m_changed.connect( index_add_instance, dispatch_uid='m2m_index_add_instance') + + def get_startup_elements(self): + from .config import config + from ..utils.collection import Collection + for model in ('Projector', 'ChatMessage', 'Tag', 'ProjectorMessage', 'Countdown'): + yield Collection(self.get_model(model).get_collection_string()) + yield Collection(config.get_collection_string()) diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css index 342d250c4..995e630b0 100644 --- a/openslides/core/static/css/app.css +++ b/openslides/core/static/css/app.css @@ -129,6 +129,25 @@ img { margin: 0 auto 0 auto; } +#startup-overlay { + display: table; + background-color: #fff; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; +} +#startup-overlay h1 { + font-size: 56px; + text-align: center; +} +#startup-overlay > div { + display: table-cell; + vertical-align: middle; +} + /** Header **/ #header { float: left; @@ -138,12 +157,6 @@ img { color: #999; } -#header div.unconnected { - border: 1px solid red; - position: fixed; - width: 100%; - z-index: 1000; -} #header a.headerlink { text-decoration: none; } diff --git a/openslides/core/static/js/core/base.js b/openslides/core/static/js/core/base.js index bed46f257..1dea1621a 100644 --- a/openslides/core/static/js/core/base.js +++ b/openslides/core/static/js/core/base.js @@ -53,13 +53,12 @@ angular.module('OpenSlidesApp.core', [ .factory('autoupdate', [ 'DS', - '$rootScope', 'REALM', 'ProjectorID', - function (DS, $rootScope, REALM, ProjectorID) { + '$q', + function (DS, REALM, ProjectorID, $q) { var socket = null; var recInterval = null; - $rootScope.connected = false; var websocketProtocol; if (location.protocol == 'https:') { @@ -69,50 +68,83 @@ angular.module('OpenSlidesApp.core', [ } var websocketPath; - if (REALM == 'site') { + if (REALM === 'site') { websocketPath = '/ws/site/'; - } else if (REALM == 'projector') { + } else if (REALM === 'projector') { websocketPath = '/ws/projector/' + ProjectorID() + '/'; } else { console.error('The constant REALM is not set properly.'); } - var Autoupdate = { - messageReceivers: [], - onMessage: function (receiver) { - this.messageReceivers.push(receiver); - }, - reconnect: function () { - if (socket) { - socket.close(); - } + var Autoupdate = {}; + Autoupdate.messageReceivers = []; + // We use later a promise to defer the first message of the established ws connection. + Autoupdate.firstMessageDeferred = $q.defer(); + Autoupdate.onMessage = function (receiver) { + Autoupdate.messageReceivers.push(receiver); + }; + Autoupdate.reconnect = function () { + if (socket) { + socket.close(); } }; - var newConnect = function () { + Autoupdate.newConnect = function () { socket = new WebSocket(websocketProtocol + '//' + location.host + websocketPath); clearInterval(recInterval); - socket.onopen = function () { - $rootScope.connected = true; - }; socket.onclose = function () { - $rootScope.connected = false; socket = null; recInterval = setInterval(function () { - newConnect(); + Autoupdate.newConnect(); }, 1000); }; socket.onmessage = function (event) { _.forEach(Autoupdate.messageReceivers, function (receiver) { receiver(event.data); }); + // The first message is done: resolve the promise. + // TODO: check whether the promise is already resolved. + Autoupdate.firstMessageDeferred.resolve(); }; }; - - newConnect(); return Autoupdate; } ]) +.factory('operator', [ + 'User', + 'Group', + function (User, Group) { + var operator = { + user: null, + perms: [], + isAuthenticated: function () { + return !!this.user; + }, + setUser: function(user_id, user_data) { + if (user_id && user_data) { + operator.user = User.inject(user_data); + operator.perms = operator.user.getPerms(); + } else { + operator.user = null; + operator.perms = Group.get(1).permissions; + } + }, + // Returns true if the operator has at least one perm of the perms-list. + hasPerms: function(perms) { + if (typeof perms === 'string') { + perms = perms.split(' '); + } + return _.intersection(perms, operator.perms).length > 0; + }, + // Returns true if the operator is a member of group. + isInGroup: function(group) { + return _.indexOf(operator.user.groups_id, group.id) > -1; + }, + }; + return operator; + } +]) + // gets all in OpenSlides available languages .factory('Languages', [ 'gettext', @@ -230,7 +262,7 @@ angular.module('OpenSlidesApp.core', [ var deletedElements = []; var collectionString = key; _.forEach(list, function(data) { - // uncomment this line for debugging to log all autoupdates: + // Uncomment this line for debugging to log all autoupdates: // console.log("Received object: " + data.collection + ", " + data.id); // remove (=eject) object from local DS store @@ -301,39 +333,7 @@ angular.module('OpenSlidesApp.core', [ } ]) -.factory('loadGlobalData', [ - 'ChatMessage', - 'Config', - 'Projector', - 'ProjectorMessage', - 'Countdown', - function (ChatMessage, Config, Projector, ProjectorMessage, Countdown) { - return function () { - Config.findAll(); - - // Loads all projector data and the projectiondefaults - Projector.findAll(); - ProjectorMessage.findAll(); - Countdown.findAll(); - - // Loads all chat messages data and their user_ids - // TODO: add permission check if user has required chat permission - // error if include 'operator' here: - // "Circular dependency found: loadGlobalData <- operator <- loadGlobalData" - //if (operator.hasPerms("core.can_use_chat")) { - ChatMessage.findAll().then( function(chatmessages) { - angular.forEach(chatmessages, function (chatmessage) { - ChatMessage.loadRelations(chatmessage, 'user'); - }); - }); - //} - }; - } -]) - - // Template hooks - .factory('templateHooks', [ function () { var hooks = {}; diff --git a/openslides/core/static/js/core/projector.js b/openslides/core/static/js/core/projector.js index 4e7bbb61d..264bd19a6 100644 --- a/openslides/core/static/js/core/projector.js +++ b/openslides/core/static/js/core/projector.js @@ -8,6 +8,13 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core']) // Can be used to find out if the projector or the side is used .constant('REALM', 'projector') +.run([ + 'autoupdate', + function (autoupdate) { + autoupdate.newConnect(); + } +]) + // Provider to register slides in a .config() statement. .provider('slides', [ function() { @@ -84,10 +91,8 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core']) '$scope', '$location', 'gettext', - 'loadGlobalData', 'Projector', - function($scope, $location, gettext, loadGlobalData, Projector) { - loadGlobalData(); + function($scope, $location, gettext, Projector) { $scope.error = ''; // watch for changes in Projector diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js index f28fdba4f..780c43e6a 100644 --- a/openslides/core/static/js/core/site.js +++ b/openslides/core/static/js/core/site.js @@ -5,6 +5,7 @@ // The core module for the OpenSlides site angular.module('OpenSlidesApp.core.site', [ 'OpenSlidesApp.core', + 'OpenSlidesApp.core.start', 'OpenSlidesApp.poll.majority', 'ui.router', 'angular-loading-bar', @@ -76,10 +77,7 @@ angular.module('OpenSlidesApp.core.site', [ this.$get = ['operator', function(operator) { return { registerScope: function (scope) { - var that = this; this.scope = scope; - this.updateMainMenu(); - operator.onOperatorChange(function () {that.updateMainMenu();}); }, updateMainMenu: function () { this.scope.elements = this.getElements(); @@ -99,15 +97,6 @@ angular.module('OpenSlidesApp.core.site', [ } ]) -// Load the global data when the operator changes -.run([ - 'loadGlobalData', - 'operator', - function (loadGlobalData, operator) { - operator.onOperatorChange(loadGlobalData); - } -]) - .run([ 'editableOptions', 'gettext', @@ -615,14 +604,6 @@ angular.module('OpenSlidesApp.core.site', [ } ]) -// Load the global data on startup -.run([ - 'loadGlobalData', - function(loadGlobalData) { - loadGlobalData(); - } -]) - // html-tag os-form-field to generate generic from fields // TODO: make it possible to use other fields then config fields .directive('osFormField', [ @@ -676,28 +657,6 @@ angular.module('OpenSlidesApp.core.site', [ } ]) -.directive('routeLoadingIndicator', [ - '$rootScope', - '$state', - 'gettext', - function($rootScope, $state, gettext) { - gettext('Loading ...'); - return { - restrict: 'E', - template: "