Angular Client
* Split angular app into a site- and a projector app * Created client slide api and slides for customslide and user * JS-function to activate a slide
This commit is contained in:
parent
a5d9f0bb42
commit
a4c00d5ee3
@ -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');
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -33,7 +33,7 @@ class ProjectorSerializer(ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Projector
|
||||
fields = ('config', 'projector_elements', )
|
||||
fields = ('id', 'config', 'elements', )
|
||||
|
||||
|
||||
class CustomSlideSerializer(ModelSerializer):
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
.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;
|
||||
});
|
||||
|
3
openslides/core/static/templates/core/slide_clock.html
Normal file
3
openslides/core/static/templates/core/slide_clock.html
Normal file
@ -0,0 +1,3 @@
|
||||
<div ng-controller="SlideClockCtr">
|
||||
{{ serverOffset | osServertime | date:'HH:mm' }} Uhr
|
||||
</div>
|
@ -0,0 +1,4 @@
|
||||
<div ng-controller="SlideCustomSlideCtr">
|
||||
<h1>{{ customslide.title }}</h1>
|
||||
{{ customslide.text }}
|
||||
</div>
|
@ -2,7 +2,7 @@
|
||||
<!--[if lt IE 7]> <html lang="en" ng-app="myApp" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
||||
<!--[if IE 7]> <html lang="en" ng-app="myApp" class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||
<!--[if IE 8]> <html lang="en" ng-app="myApp" class="no-js lt-ie9"> <![endif]-->
|
||||
<!--[if gt IE 8]><!--> <html lang="en" ng-app="OpenSlidesApp" class="no-js"> <!--<![endif]-->
|
||||
<!--[if gt IE 8]><!--> <html lang="en" ng-app="OpenSlidesApp.site" class="no-js"> <!--<![endif]-->
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<base href="/">
|
||||
@ -133,7 +133,7 @@
|
||||
<span class="text" translate>Home</span>
|
||||
</a>
|
||||
<li>
|
||||
<a href="#TODO">
|
||||
<a ui-sref="projector">
|
||||
<span class="ico"><i class="fa fa-video-camera fa-lg"></i></span>
|
||||
<span class="text" translate>Projector</span>
|
||||
</a>
|
||||
|
25
openslides/core/static/templates/projector.html
Normal file
25
openslides/core/static/templates/projector.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" ng-app="OpenSlidesApp.projector" class="no-js">
|
||||
<meta charset="utf-8">
|
||||
<base href="/">
|
||||
<title>OpenSlides – Projector</title>
|
||||
<link rel="stylesheet" href="static/css/openslides-libs.css">
|
||||
<link rel="stylesheet" href="static/css/projector.css">
|
||||
<script src="static/js/openslides-libs.js"></script>
|
||||
|
||||
<div id="header">
|
||||
<img id="logo" src="/static/img/logo.png" alt="OpenSlides" />
|
||||
<span class="navbar-text optional">{{ config('general_event_name') }}</span>
|
||||
</div>
|
||||
|
||||
<div id="content" ng-controller="ProjectorCtrl">
|
||||
<div ng-repeat="element in elements"><div ng-include="element.template"></div></div>
|
||||
</div>
|
||||
|
||||
<script src="static/js/app.js"></script>
|
||||
<script src="static/js/core.js"></script>
|
||||
<script src="static/js/agenda/agenda.js"></script>
|
||||
<script src="static/js/motions/motions.js"></script>
|
||||
<script src="static/js/assignments/assignments.js"></script>
|
||||
<script src="static/js/users/users.js"></script>
|
||||
<script src="static/js/mediafiles/mediafiles.js"></script>
|
@ -10,4 +10,11 @@ urlpatterns = patterns(
|
||||
url(r'^core/version/$',
|
||||
views.VersionView.as_view(),
|
||||
name='core_version'),
|
||||
|
||||
# View for the projectors are handelt by angular.
|
||||
url(r'^projector.*$', views.ProjectorView.as_view()),
|
||||
|
||||
# Main entry point for all angular pages.
|
||||
# Has to be the last entry in the urls.py
|
||||
url(r'^.*$', views.IndexView.as_view()),
|
||||
)
|
||||
|
@ -43,6 +43,17 @@ class IndexView(utils_views.CSRFMixin, utils_views.View):
|
||||
return HttpResponse(content)
|
||||
|
||||
|
||||
class ProjectorView(utils_views.View):
|
||||
"""
|
||||
Access the projector.
|
||||
"""
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
with open(finders.find('templates/projector.html')) as f:
|
||||
content = f.read()
|
||||
return HttpResponse(content)
|
||||
|
||||
|
||||
class ProjectorViewSet(ReadOnlyModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve and update the projector slide info.
|
||||
|
@ -1,5 +1,17 @@
|
||||
angular.module('OpenSlidesApp.mediafiles', [])
|
||||
|
||||
.factory('Mediafile', function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'mediafiles/mediafile',
|
||||
endpoint: '/rest/mediafiles/mediafile/'
|
||||
});
|
||||
})
|
||||
|
||||
.run(function(Mediafile) {});
|
||||
|
||||
|
||||
angular.module('OpenSlidesApp.mediafiles.site', ['OpenSlidesApp.mediafiles'])
|
||||
|
||||
.config(function($stateProvider) {
|
||||
$stateProvider
|
||||
.state('mediafiles', {
|
||||
@ -26,13 +38,6 @@ angular.module('OpenSlidesApp.mediafiles', [])
|
||||
});
|
||||
})
|
||||
|
||||
.factory('Mediafile', function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'mediafiles/mediafile',
|
||||
endpoint: '/rest/mediafiles/mediafile/'
|
||||
});
|
||||
})
|
||||
|
||||
.controller('MediafileListCtrl', function($scope, $http, Mediafile) {
|
||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||
|
||||
@ -80,4 +85,3 @@ angular.module('OpenSlidesApp.mediafiles', [])
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,29 @@
|
||||
angular.module('OpenSlidesApp.motions', [])
|
||||
|
||||
.factory('Motion', function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'motions/motion',
|
||||
endpoint: '/rest/motions/motion/'
|
||||
});
|
||||
})
|
||||
.factory('Category', function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'motions/category',
|
||||
endpoint: '/rest/motions/category/'
|
||||
});
|
||||
})
|
||||
.factory('Workflow', function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'motions/workflow',
|
||||
endpoint: '/rest/motions/workflow/'
|
||||
});
|
||||
})
|
||||
|
||||
.run(function(Motion, Category, Workflow) {});
|
||||
|
||||
|
||||
angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
|
||||
.config(function($stateProvider) {
|
||||
$stateProvider
|
||||
.state('motions', {
|
||||
@ -104,25 +128,6 @@ angular.module('OpenSlidesApp.motions', [])
|
||||
})
|
||||
})
|
||||
|
||||
.factory('Motion', function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'motions/motion',
|
||||
endpoint: '/rest/motions/motion/'
|
||||
});
|
||||
})
|
||||
.factory('Category', function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'motions/category',
|
||||
endpoint: '/rest/motions/category/'
|
||||
});
|
||||
})
|
||||
.factory('Workflow', function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'motions/workflow',
|
||||
endpoint: '/rest/motions/workflow/'
|
||||
});
|
||||
})
|
||||
|
||||
.controller('MotionListCtrl', function($scope, Motion) {
|
||||
Motion.bindAll({}, $scope, 'motions');
|
||||
|
||||
|
@ -1,17 +1,17 @@
|
||||
from django.conf.urls import include, patterns, url
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
from openslides.core.views import IndexView
|
||||
from openslides.utils.rest_api import router
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^(?P<url>.*[^/])$', RedirectView.as_view(url='/%(url)s/')),
|
||||
url(r'^rest/', include(router.urls)),
|
||||
url(r'^', include('openslides.core.urls')),
|
||||
url(r'^agenda/', include('openslides.agenda.urls')),
|
||||
url(r'^assignments/', include('openslides.assignments.urls')),
|
||||
url(r'^motions/', include('openslides.motions.urls')),
|
||||
url(r'^users/', include('openslides.users.urls')),
|
||||
url(r'^.*$', IndexView.as_view()),
|
||||
|
||||
# The urls.py for the core app has to be the last entry in the urls.py
|
||||
url(r'^', include('openslides.core.urls')),
|
||||
)
|
||||
|
@ -12,33 +12,23 @@ class UserSlide(ProjectorElement):
|
||||
Slide definitions for user model.
|
||||
"""
|
||||
name = 'users/user'
|
||||
scripts = 'users/user_slide.js'
|
||||
|
||||
def get_context(self):
|
||||
pk = self.config_entry.get('id')
|
||||
try:
|
||||
user = User.objects.get(pk=pk)
|
||||
except User.DoesNotExist:
|
||||
if not User.objects.filter(pk=pk).exists():
|
||||
raise ProjectorException(_('User does not exist.'))
|
||||
result = [{
|
||||
'collection': 'users/user',
|
||||
'id': pk}]
|
||||
for group in user.groups.all():
|
||||
result.append({
|
||||
'collection': 'users/group',
|
||||
'id': group.pk})
|
||||
return result
|
||||
return {'id': pk}
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
self.config_entry = config_entry
|
||||
try:
|
||||
context = self.get_context()
|
||||
except ProjectorException:
|
||||
# User does not exist so just do nothing.
|
||||
pass
|
||||
else:
|
||||
for item in context:
|
||||
pk = config_entry.get('id')
|
||||
if pk is not None:
|
||||
yield ProjectorRequirement(
|
||||
view_class=UserViewSet if item['collection'] == 'users/user' else GroupViewSet,
|
||||
view_class=UserViewSet,
|
||||
view_action='retrive',
|
||||
pk=pk)
|
||||
|
||||
for group in User.objects.get(pk=pk).groups.all():
|
||||
yield ProjectorRequirement(
|
||||
view_class=GroupViewSet,
|
||||
view_action='retrieve',
|
||||
pk=str(item['id']))
|
||||
pk=str(group.pk))
|
||||
|
@ -1,5 +1,56 @@
|
||||
angular.module('OpenSlidesApp.users', [])
|
||||
|
||||
.factory('User', function(DS, Group) {
|
||||
return DS.defineResource({
|
||||
name: 'users/user',
|
||||
endpoint: '/rest/users/user/',
|
||||
methods: {
|
||||
get_short_name: function() {
|
||||
// should be the same as in the python user model.
|
||||
var firstName = _.trim(this.first_name),
|
||||
lastName = _.trim(this.last_name),
|
||||
name;
|
||||
|
||||
if (firstName && lastName) {
|
||||
// TODO: check config
|
||||
name = [firstName, lastName].join(' ');
|
||||
} else {
|
||||
name = firstName || lastName || this.username;
|
||||
}
|
||||
return name;
|
||||
},
|
||||
getPerms: function() {
|
||||
var allPerms = [];
|
||||
_.forEach(this.groups, function(groupId) {
|
||||
// Get group from server
|
||||
Group.find(groupId);
|
||||
// But do not work with the returned promise, because in
|
||||
// this case this method can not be called in $watch
|
||||
group = Group.get(groupId);
|
||||
if (group) {
|
||||
_.forEach(group.permissions, function(perm) {
|
||||
allPerms.push(perm);
|
||||
});
|
||||
}
|
||||
});
|
||||
return _.uniq(allPerms);
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
.factory('Group', function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'users/group',
|
||||
endpoint: '/rest/users/group/'
|
||||
});
|
||||
})
|
||||
|
||||
.run(function(User, Group) {});
|
||||
|
||||
|
||||
angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
||||
|
||||
.config(function($stateProvider) {
|
||||
$stateProvider
|
||||
.state('users', {
|
||||
@ -126,52 +177,6 @@ angular.module('OpenSlidesApp.users', [])
|
||||
$rootScope.operator = operator;
|
||||
})
|
||||
|
||||
.factory('User', function(DS, Group) {
|
||||
return DS.defineResource({
|
||||
name: 'users/user',
|
||||
endpoint: '/rest/users/user/',
|
||||
methods: {
|
||||
get_short_name: function() {
|
||||
// should be the same as in the python user model.
|
||||
var firstName = _.trim(this.first_name),
|
||||
lastName = _.trim(this.last_name),
|
||||
name;
|
||||
|
||||
if (firstName && lastName) {
|
||||
// TODO: check config
|
||||
name = [firstName, lastName].join(' ');
|
||||
} else {
|
||||
name = firstName || lastName || this.username;
|
||||
}
|
||||
return name;
|
||||
},
|
||||
getPerms: function() {
|
||||
var allPerms = [];
|
||||
_.forEach(this.groups, function(groupId) {
|
||||
// Get group from server
|
||||
Group.find(groupId);
|
||||
// But do not work with the returned promise, because in
|
||||
// this case this method can not be called in $watch
|
||||
group = Group.get(groupId);
|
||||
if (group) {
|
||||
_.forEach(group.permissions, function(perm) {
|
||||
allPerms.push(perm);
|
||||
});
|
||||
}
|
||||
});
|
||||
return _.uniq(allPerms);
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
.factory('Group', function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'users/group',
|
||||
endpoint: '/rest/users/group/'
|
||||
});
|
||||
})
|
||||
|
||||
/*
|
||||
* Directive to check for permissions
|
||||
*
|
||||
@ -239,7 +244,7 @@ angular.module('OpenSlidesApp.users', [])
|
||||
};
|
||||
}])
|
||||
|
||||
.controller('UserListCtrl', function($scope, User) {
|
||||
.controller('UserListCtrl', function($scope, User, projectorActivate) {
|
||||
User.bindAll({}, $scope, 'users');
|
||||
|
||||
// setup table sorting
|
||||
@ -264,6 +269,10 @@ angular.module('OpenSlidesApp.users', [])
|
||||
$scope.delete = function (user) {
|
||||
User.destroy(user.id);
|
||||
};
|
||||
|
||||
$scope.project = function(user) {
|
||||
projectorActivate(User, user.id).error(function() {console.log('success')});
|
||||
};
|
||||
})
|
||||
|
||||
.controller('UserDetailCtrl', function($scope, User, user) {
|
||||
@ -369,9 +378,25 @@ angular.module('OpenSlidesApp.users', [])
|
||||
// DS.flush();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
angular.module('OpenSlidesApp.users.projector', ['OpenSlidesApp.users'])
|
||||
|
||||
.config(function(slidesProvider) {
|
||||
slidesProvider.registerSlide('users/user', {
|
||||
template: 'static/templates/users/slide_user.html',
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
.controller('SlideUserCtr', function($scope, User) {
|
||||
// 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;
|
||||
User.find(id);
|
||||
User.bindOne(id, $scope, 'user');
|
||||
});
|
||||
|
||||
// this is code from angular.js. Find a way to call this function from this file
|
||||
function getBlockNodes(nodes) {
|
||||
|
3
openslides/users/static/templates/users/slide_user.html
Normal file
3
openslides/users/static/templates/users/slide_user.html
Normal file
@ -0,0 +1,3 @@
|
||||
<div ng-controller="SlideUserCtr">
|
||||
<h1>{{ user.username }}</h1>
|
||||
</div>
|
@ -106,7 +106,8 @@
|
||||
<td os-perms="users.can_manage" class="optional">{{ user.last_login | date:'yyyy-MM-dd HH:mm:ss'}}
|
||||
<td os-perms="users.can_manage core.can_manage_projector" class="nobr">
|
||||
<!-- projector, TODO: add link to activate slidea-->
|
||||
<a href="#TODO" os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||
ng-click="project(user)"
|
||||
title="{{ 'Show' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
|
@ -8,13 +8,12 @@ class ProjectorElement(object, metaclass=SignalConnectMetaClass):
|
||||
Base class for an element on the projector.
|
||||
|
||||
Every app which wants to add projector elements has to create classes
|
||||
subclassing from this base class with different names. The name and
|
||||
scripts attributes have to be set. The metaclass
|
||||
(SignalConnectMetaClass) does the rest of the magic.
|
||||
subclassing from this base class with different names. The name attribute
|
||||
has to be set. The metaclass (SignalConnectMetaClass) does the rest of the
|
||||
magic.
|
||||
"""
|
||||
signal = Signal()
|
||||
name = None
|
||||
scripts = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
@ -45,20 +44,8 @@ class ProjectorElement(object, metaclass=SignalConnectMetaClass):
|
||||
assert self.config_entry.get('name') == self.name, (
|
||||
'To get data of a projector element, the correct config entry has to be given.')
|
||||
return {
|
||||
'scripts': self.get_scripts(),
|
||||
'context': self.get_context()}
|
||||
|
||||
def get_scripts(self):
|
||||
"""
|
||||
Returns ...?
|
||||
"""
|
||||
# TODO: Write docstring
|
||||
if self.scripts is None:
|
||||
raise NotImplementedError(
|
||||
'A projector element class must define either a '
|
||||
'get_scripts method or have a scripts argument.')
|
||||
return self.scripts
|
||||
|
||||
def get_context(self):
|
||||
"""
|
||||
Returns the context of the projector element.
|
||||
|
@ -23,13 +23,11 @@ class ProjectorAPI(TestCase):
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(json.loads(response.content.decode()), {
|
||||
'id': 1,
|
||||
'config': [{'name': 'core/customslide', 'id': customslide.id}],
|
||||
'projector_elements': [
|
||||
'elements': [
|
||||
{'name': 'core/customslide',
|
||||
'scripts': 'core/customslide_slide.js',
|
||||
'context': [
|
||||
{'collection': 'core/customslide',
|
||||
'id': customslide.id}]}]})
|
||||
'context': {'id': customslide.id}}]})
|
||||
|
||||
def test_invalid_slide_on_default_projector(self):
|
||||
self.client.login(username='admin', password='admin')
|
||||
@ -41,8 +39,9 @@ class ProjectorAPI(TestCase):
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(json.loads(response.content.decode()), {
|
||||
'id': 1,
|
||||
'config': [{'name': 'invalid_slide'}],
|
||||
'projector_elements': [
|
||||
'elements': [
|
||||
{'name': 'invalid_slide',
|
||||
'error': 'Projector element does not exist.'}]})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user