diff --git a/openslides/agenda/static/js/agenda/agenda.js b/openslides/agenda/static/js/agenda/agenda.js index 8037d3676..9955ee880 100644 --- a/openslides/agenda/static/js/agenda/agenda.js +++ b/openslides/agenda/static/js/agenda/agenda.js @@ -1,5 +1,18 @@ angular.module('OpenSlidesApp.agenda', []) +.factory('Agenda', function(DS) { + return DS.defineResource({ + name: 'agenda/item', + endpoint: '/rest/agenda/item/' + }); +}) + +// Make sure that the Agenda resource is loaded. +.run(function(Agenda) {}); + + +angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) + .config(function($stateProvider) { $stateProvider .state('agenda', { @@ -68,13 +81,6 @@ angular.module('OpenSlidesApp.agenda', []) }); }) -.factory('Agenda', function(DS) { - return DS.defineResource({ - name: 'agenda/item', - endpoint: '/rest/agenda/item/' - }); -}) - .controller('ItemListCtrl', function($scope, Agenda, tree) { Agenda.bindAll({}, $scope, 'items'); diff --git a/openslides/assignments/static/js/assignments/assignments.js b/openslides/assignments/static/js/assignments/assignments.js index b65ec19b1..d24389b1e 100644 --- a/openslides/assignments/static/js/assignments/assignments.js +++ b/openslides/assignments/static/js/assignments/assignments.js @@ -1,5 +1,17 @@ angular.module('OpenSlidesApp.assignments', []) +.factory('Assignment', function(DS) { + return DS.defineResource({ + name: 'assignments/assignment', + endpoint: '/rest/assignments/assignment/' + }); +}) + +.run(function(Assignment) {}); + + +angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) + .config(function($stateProvider) { $stateProvider .state('assignments', { @@ -37,13 +49,6 @@ angular.module('OpenSlidesApp.assignments', []) }); }) -.factory('Assignment', function(DS) { - return DS.defineResource({ - name: 'assignments/assignment', - endpoint: '/rest/assignments/assignment/' - }); -}) - .controller('AssignmentListCtrl', function($scope, Assignment, phases) { Assignment.bindAll({}, $scope, 'assignments'); // get all item types via OPTIONS request diff --git a/openslides/core/models.py b/openslides/core/models.py index e7a9c36a3..ac203d4fa 100644 --- a/openslides/core/models.py +++ b/openslides/core/models.py @@ -41,7 +41,7 @@ class Projector(RESTModelMixin, models.Model): ('can_use_chat', ugettext_noop('Can use the chat'))) @property - def projector_elements(self): + def elements(self): """ A generator to retrieve all projector elements given in the config field. For every element the method get_data() is called and its diff --git a/openslides/core/projector.py b/openslides/core/projector.py index 0ba63fa37..0083d19ce 100644 --- a/openslides/core/projector.py +++ b/openslides/core/projector.py @@ -13,24 +13,16 @@ class CustomSlideSlide(ProjectorElement): Slide definitions for custom slide model. """ name = 'core/customslide' - scripts = 'core/customslide_slide.js' def get_context(self): pk = self.config_entry.get('id') if not CustomSlide.objects.filter(pk=pk).exists(): raise ProjectorException(_('Custom slide does not exist.')) - return [{ - 'collection': 'core/customslide', - 'id': pk}] + return {'id': pk} def get_requirements(self, config_entry): - self.config_entry = config_entry - try: - pk = self.get_context()[0]['id'] - except ProjectorException: - # Custom slide does not exist so just do nothing. - pass - else: + pk = config_entry.get('id') + if pk is not None: yield ProjectorRequirement( view_class=CustomSlideViewSet, view_action='retrieve', @@ -42,7 +34,6 @@ class Clock(ProjectorElement): Clock on the projector. """ name = 'core/clock' - scripts = 'core/clock.js' def get_context(self): return {'server_time': now().timestamp()} @@ -73,7 +64,6 @@ class Countdown(ProjectorElement): To hide a running countdown add {"hidden": true}. """ name = 'core/countdown' - scripts = 'core/countdown.js' def get_context(self): if self.config_entry.get('countdown_time') is None: @@ -88,7 +78,6 @@ class Message(ProjectorElement): Short message on the projector. Rendered as overlay. """ name = 'core/message' - scripts = 'core/message.js' def get_context(self): if self.config_entry.get('message') is None: diff --git a/openslides/core/serializers.py b/openslides/core/serializers.py index bd3eef7fe..995f4898a 100644 --- a/openslides/core/serializers.py +++ b/openslides/core/serializers.py @@ -33,7 +33,7 @@ class ProjectorSerializer(ModelSerializer): class Meta: model = Projector - fields = ('config', 'projector_elements', ) + fields = ('id', 'config', 'elements', ) class CustomSlideSerializer(ModelSerializer): diff --git a/openslides/core/static/js/app.js b/openslides/core/static/js/app.js index f84121985..238773954 100644 --- a/openslides/core/static/js/app.js +++ b/openslides/core/static/js/app.js @@ -1,24 +1,38 @@ angular.module('OpenSlidesApp', [ - 'ui.router', 'angular-loading-bar', 'js-data', 'gettext', - 'ngBootbox', - 'ngFabForm', - 'ngMessages', 'ngAnimate', - 'ngCsvImport', - 'ngSanitize', 'ui.bootstrap', - 'ui.select', 'ui.tree', - 'xeditable', - 'OpenSlidesApp.core', +]); + +angular.module('OpenSlidesApp.projector', [ + 'OpenSlidesApp', + 'OpenSlidesApp.core.projector', 'OpenSlidesApp.agenda', 'OpenSlidesApp.motions', 'OpenSlidesApp.assignments', - 'OpenSlidesApp.users', + 'OpenSlidesApp.users.projector', 'OpenSlidesApp.mediafiles', +]); + +angular.module('OpenSlidesApp.site', [ + 'OpenSlidesApp', + 'ui.router', + 'ngBootbox', + 'ngFabForm', + 'ngMessages', + 'ngCsvImport', + 'ngSanitize', // TODO: remove this as global dependency + 'ui.select', + 'xeditable', + 'OpenSlidesApp.core.site', + 'OpenSlidesApp.agenda.site', + 'OpenSlidesApp.motions.site', + 'OpenSlidesApp.assignments.site', + 'OpenSlidesApp.users.site', + 'OpenSlidesApp.mediafiles.site', ]) .config(function($urlRouterProvider, $locationProvider) { diff --git a/openslides/core/static/js/core.js b/openslides/core/static/js/core.js index f32b52483..d2826de26 100644 --- a/openslides/core/static/js/core.js +++ b/openslides/core/static/js/core.js @@ -1,5 +1,119 @@ +// The core module used for the OpenSlides site and the projector angular.module('OpenSlidesApp.core', []) +.config(function(DSProvider) { + // Reloads everything after 5 minutes. + // TODO: * find a way only to reload things that are still needed + DSProvider.defaults.maxAge = 5 * 60 * 1000; // 5 minutes + DSProvider.defaults.reapAction = 'none'; + DSProvider.defaults.afterReap = function(model, items) { + if (items.length > 5) { + model.findAll({}, {bypassCache: true}); + } else { + _.forEach(items, function (item) { + model.refresh(item[model.idAttribute]); + }); + } + }; +}) + +.run(function(DS, autoupdate) { + autoupdate.on_message(function(data) { + // TODO: when MODEL.find() is called after this + // a new request is fired. This could be a bug in DS + + // TODO: Do not send the status code to the client, but make the decission + // on the server side. It is an implementation detail, that tornado + // sends request to wsgi, which should not concern the client. + console.log("Received object: " + data.collection + ", " + data.id); + if (data.status_code == 200) { + DS.inject(data.collection, data.data); + } else if (data.status_code == 404) { + DS.eject(data.collection, data.id); + } + // TODO: handle other statuscodes + }); +}) + +.run(function($rootScope, Config) { + // Puts the config object into each scope. + // TODO: maybe rootscope.config has to set before findAll() is finished + Config.findAll().then(function() { + $rootScope.config = function(key) { + try { + return Config.get(key).value; + } + catch(err) { + console.log("Unkown config key: " + key); + return '' + } + } + }); +}) + +.factory('autoupdate', function() { + var url = location.origin + "/sockjs"; + + var Autoupdate = { + socket: null, + message_receivers: [], + connect: function() { + var autoupdate = this; + this.socket = new SockJS(url); + + this.socket.onmessage = function(event) { + _.forEach(autoupdate.message_receivers, function(receiver) { + receiver(event.data); + }); + } + + this.socket.onclose = function() { + setTimeout(autoupdate.connect, 5000); + } + }, + on_message: function(receiver) { + this.message_receivers.push(receiver); + } + }; + Autoupdate.connect(); + return Autoupdate; +}) + +.factory('Customslide', function(DS) { + return DS.defineResource({ + name: 'core/customslide', + endpoint: '/rest/core/customslide/' + }); +}) + +.factory('Tag', function(DS) { + return DS.defineResource({ + name: 'core/tag', + endpoint: '/rest/core/tag/' + }); +}) + +.factory('Config', function(DS) { + return DS.defineResource({ + name: 'config/config', + idAttribute: 'key', + endpoint: '/rest/config/config/' + }); +}) + +.factory('Projector', function(DS) { + return DS.defineResource({ + name: 'core/projector', + endpoint: '/rest/core/projector/', + }); +}) + +.run(function(Projector, Config, Tag, Customslide){}); + + +// The core module for the OpenSlides site +angular.module('OpenSlidesApp.core.site', ['OpenSlidesApp.core']) + .config(function($stateProvider, $urlMatcherFactoryProvider) { // Make the trailing slash optional $urlMatcherFactoryProvider.strictMode(false) @@ -80,6 +194,13 @@ angular.module('OpenSlidesApp.core', []) url: '/', templateUrl: 'static/templates/dashboard.html' }) + .state('projector', { + url: '/projector', + data: {extern: true}, + onEnter: function($window) { + $window.location.href = this.url; + } + }) .state('core', { url: '/core', abstract: true, @@ -108,22 +229,6 @@ angular.module('OpenSlidesApp.core', []) $locationProvider.html5Mode(true); }) -.config(function(DSProvider) { - // Reloads everything after 5 minutes. - // TODO: * find a way only to reload things that are still needed - DSProvider.defaults.maxAge = 5 * 60 * 1000; // 5 minutes - DSProvider.defaults.reapAction = 'none'; - DSProvider.defaults.afterReap = function(model, items) { - if (items.length > 5) { - model.findAll({}, {bypassCache: true}); - } else { - _.forEach(items, function (item) { - model.refresh(item[model.idAttribute]); - }); - } - }; -}) - // config for ng-fab-form .config(function(ngFabFormProvider) { ngFabFormProvider.extendConfig({ @@ -131,6 +236,8 @@ angular.module('OpenSlidesApp.core', []) }); }) +// Helper to add ui.router states at runtime. +// Needed for the django url_patterns. .provider('runtimeStates', function($stateProvider) { this.$get = function($q, $timeout, $state) { return { @@ -141,6 +248,7 @@ angular.module('OpenSlidesApp.core', []) } }) +// Load the django url patterns .run(function(runtimeStates, $http) { $http.get('/core/url_patterns/').then(function(data) { for (var pattern in data.data) { @@ -155,87 +263,20 @@ angular.module('OpenSlidesApp.core', []) }); }) -.run(function(DS, autoupdate) { - autoupdate.on_message(function(data) { - // TODO: when MODEL.find() is called after this - // a new request is fired. This could be a bug in DS - - // TODO: Do not send the status code to the client, but make the decission - // on the server side. It is an implementation detail, that tornado - // sends request to wsgi, which should not concern the client. - if (data.status_code == 200) { - DS.inject(data.collection, data.data); - } else if (data.status_code == 404) { - DS.eject(data.collection, data.id); - } - // TODO: handle other statuscodes - }); -}) - -.run(function($rootScope, Config) { - // Puts the config object into each scope. - // TODO: maybe rootscope.config has to set before findAll() is finished - Config.findAll().then(function() {; - $rootScope.config = function(key) { - return Config.get(key).value; - } - }); -}) - -//options for angular-xeditable +// options for angular-xeditable .run(function(editableOptions) { editableOptions.theme = 'bs3'; }) -.factory('autoupdate', function() { - //TODO: use config here - var url = "http://" + location.host + "/sockjs"; - - var Autoupdate = { - socket: null, - message_receivers: [], - connect: function() { - var autoupdate = this; - this.socket = new SockJS(url); - - this.socket.onmessage = function(event) { - _.forEach(autoupdate.message_receivers, function(receiver) { - receiver(event.data); - }); - } - - this.socket.onclose = function() { - setTimeout(autoupdate.connect, 5000); - } - }, - on_message: function(receiver) { - this.message_receivers.push(receiver); - } +// Activate an Element from the Rest-API on the projector +// At the moment it only activates item on projector 1 +.factory('projectorActivate', function($http) { + return function(model, id) { + return $http.post( + '/rest/core/projector/1/prune_elements/', + [{name: model.name, id: id}] + ); }; - Autoupdate.connect(); - return Autoupdate; -}) - -.factory('Customslide', function(DS) { - return DS.defineResource({ - name: 'core/customslide', - endpoint: '/rest/core/customslide/' - }); -}) - -.factory('Tag', function(DS) { - return DS.defineResource({ - name: 'core/tag', - endpoint: '/rest/core/tag/' - }); -}) - -.factory('Config', function(DS) { - return DS.defineResource({ - name: 'config/config', - idAttribute: 'key', - endpoint: '/rest/config/config/' - }); }) .controller("LanguageCtrl", function ($scope, gettextCatalog) { @@ -346,3 +387,82 @@ angular.module('OpenSlidesApp.core', []) } }; }); + + +// The core module for the OpenSlides projector +angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core']) + +// Provider to register slides in a .config() statement. +.provider('slides', function() { + var slidesMap = {}; + + this.registerSlide = function(name, config) { + slidesMap[name] = config; + return this; + }; + + this.$get = function($templateRequest, $q) { + var self = this; + return { + getElements: function(projector) { + var elements = []; + var factory = this; + _.forEach(projector.elements, function(element) { + if (element.name in slidesMap) { + element.template = slidesMap[element.name].template; + elements.push(element); + } else { + console.log("Unknown slide: " + element.name); + } + }); + return elements; + } + } + }; +}) + +.config(function(slidesProvider) { + slidesProvider.registerSlide('core/customslide', { + template: 'static/templates/core/slide_customslide.html', + }); + + slidesProvider.registerSlide('core/clock', { + template: 'static/templates/core/slide_clock.html', + }); +}) + +.filter('osServertime',function() { + return function(serverOffset) { + var date = new Date(); + return date.setTime(date.getTime() - serverOffset); + }; +}) + +.controller('ProjectorCtrl', function($scope, Projector, slides) { + Projector.find(1).then(function() { + $scope.$watch(function () { + return Projector.lastModified(1); + }, function () { + $scope.elements = []; + _.forEach(slides.getElements(Projector.get(1)), function(element) { + $scope.elements.push(element); + }); + }); + }); +}) + +.controller('SlideCustomSlideCtr', function($scope, Customslide) { + // Attention! Each object that is used here has to be dealt on server side. + // Add it to the coresponding get_requirements method of the ProjectorElement + // class. + var id = $scope.element.context.id; + Customslide.find(id); + Customslide.bindOne(id, $scope, 'customslide'); +}) + +.controller('SlideClockCtr', function($scope) { + // Attention! Each object that is used here has to be dealt on server side. + // Add it to the coresponding get_requirements method of the ProjectorElement + // class. + $scope.serverOffset = Date.parse(new Date().toUTCString()) - $scope.element.context.server_time; +}); diff --git a/openslides/core/static/templates/core/slide_clock.html b/openslides/core/static/templates/core/slide_clock.html new file mode 100644 index 000000000..d74638bac --- /dev/null +++ b/openslides/core/static/templates/core/slide_clock.html @@ -0,0 +1,3 @@ +