Split and cleaned up JS files. Added test.

This commit is contained in:
Norman Jäckel 2015-10-22 00:01:51 +02:00
parent 0e3688fc33
commit 32b23df763
18 changed files with 713 additions and 614 deletions

View File

@ -6,7 +6,7 @@ class AgendaAppConfig(AppConfig):
verbose_name = 'OpenSlides Agenda' verbose_name = 'OpenSlides Agenda'
angular_site_module = True angular_site_module = True
angular_projector_module = True angular_projector_module = True
js_files = ['js/agenda/agenda.js'] js_files = ['js/agenda/base.js', 'js/agenda/site.js', 'js/agenda/projector.js']
def ready(self): def ready(self):
# Load projector elements. # Load projector elements.

View File

@ -0,0 +1,122 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
.factory('Speaker', [
'DS',
function(DS) {
return DS.defineResource({
name: 'agenda/speaker',
relations: {
belongsTo: {
'users/user': {
localField: 'user',
localKey: 'user_id',
}
}
}
});
}
])
.factory('Agenda', [
'DS',
'Speaker',
'jsDataModel',
function(DS, Speaker, jsDataModel) {
var name = 'agenda/item';
return DS.defineResource({
name: name,
useClass: jsDataModel,
methods: {
getResourceName: function () {
return name;
}
},
relations: {
hasMany: {
'core/tag': {
localField: 'tags',
localKeys: 'tags_id',
},
'agenda/speaker': {
localField: 'speakers',
foreignKey: 'item_id',
}
}
}
});
}
])
.factory('AgendaTree', [
function () {
return {
getTree: function (items) {
// Sort items after there weight
items.sort(function(itemA, itemB) {
return itemA.weight - itemB.weight;
});
// Build a dict with all children (dict-value) to a specific
// item id (dict-key).
var itemChildren = {};
_.each(items, function (item) {
if (item.parent_id) {
// Add item to his parent. If it is the first child, then
// create a new list.
try {
itemChildren[item.parent_id].push(item);
} catch (error) {
itemChildren[item.parent_id] = [item];
}
}
});
// Recursive function that generates a nested list with all
// items with there children
function getChildren(items) {
var returnItems = [];
_.each(items, function (item) {
returnItems.push({
item: item,
children: getChildren(itemChildren[item.id]),
id: item.id,
});
});
return returnItems;
}
// Generates the list of root items (with no parents)
var parentItems = items.filter(function (item) {
return !item.parent_id;
});
return getChildren(parentItems);
},
// Returns a list of all items as a flat tree the attribute parentCount
getFlatTree: function(items) {
var tree = this.getTree(items);
var flatItems = [];
function generateFatTree(tree, parentCount) {
_.each(tree, function (item) {
item.item.parentCount = parentCount;
flatItems.push(item.item);
generateFatTree(item.children, parentCount + 1);
});
}
generateFatTree(tree, 0);
return flatItems;
},
};
}
])
// Make sure that the Agenda resource is loaded.
.run(['Agenda', function(Agenda) {}]);
}());

View File

@ -0,0 +1,55 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
.config([
'slidesProvider',
function(slidesProvider) {
slidesProvider.registerSlide('agenda/item', {
template: 'static/templates/agenda/slide-item-detail.html',
});
slidesProvider.registerSlide('agenda/item-list', {
template: 'static/templates/agenda/slide-item-list.html',
});
}
])
.controller('SlideItemDetailCtrl', [
'$scope',
'Agenda',
'User',
function($scope, Agenda, 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.id;
Agenda.find(id);
User.findAll();
Agenda.bindOne(id, $scope, 'item');
// get flag for list-of-speakers-slide (true/false)
$scope.is_list_of_speakers = $scope.element.list_of_speakers;
}
])
.controller('SlideItemListCtrl', [
'$scope',
'$http',
'Agenda',
'AgendaTree',
function($scope, $http, Agenda, AgendaTree) {
// 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.
Agenda.findAll();
// Bind agenda tree to the scope
$scope.$watch(function () {
return Agenda.lastModified();
}, function () {
$scope.items = AgendaTree.getFlatTree(Agenda.getAll());
});
}
]);
}());

View File

@ -1,122 +1,6 @@
"use strict"; (function () {
angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
.factory('Speaker', [
'DS',
function(DS) {
return DS.defineResource({
name: 'agenda/speaker',
relations: {
belongsTo: {
'users/user': {
localField: 'user',
localKey: 'user_id',
}
}
}
});
}
])
.factory('Agenda', [
'DS',
'Speaker',
'jsDataModel',
function(DS, Speaker, jsDataModel) {
var name = 'agenda/item'
return DS.defineResource({
name: name,
useClass: jsDataModel,
methods: {
getResourceName: function () {
return name;
}
},
relations: {
hasMany: {
'core/tag': {
localField: 'tags',
localKeys: 'tags_id',
},
'agenda/speaker': {
localField: 'speakers',
foreignKey: 'item_id',
}
}
}
});
}
])
.factory('AgendaTree', [
function () {
return {
getTree: function (items) {
// Sort items after there weight
items.sort(function(itemA, itemB) {
return itemA.weight - itemB.weight;
});
// Build a dict with all children (dict-value) to a specific
// item id (dict-key).
var itemChildren = {};
_.each(items, function (item) {
if (item.parent_id) {
// Add item to his parent. If it is the first child, then
// create a new list.
try {
itemChildren[item.parent_id].push(item);
} catch (error) {
itemChildren[item.parent_id] = [item];
}
}
});
// Recursive function that generates a nested list with all
// items with there children
function getChildren(items) {
var returnItems = [];
_.each(items, function (item) {
returnItems.push({
item: item,
children: getChildren(itemChildren[item.id]),
id: item.id,
});
});
return returnItems;
}
// Generates the list of root items (with no parents)
var parentItems = items.filter(function (item) {
return !item.parent_id;
});
return getChildren(parentItems);
},
// Returns a list of all items as a flat tree the attribute parentCount
getFlatTree: function(items) {
var tree = this.getTree(items);
var flatItems = [];
function generateFatTree(tree, parentCount) {
_.each(tree, function (item) {
item.item.parentCount = parentCount;
flatItems.push(item.item);
generateFatTree(item.children, parentCount + 1);
});
}
generateFatTree(tree, 0);
return flatItems;
},
}
}
])
// Make sure that the Agenda resource is loaded.
.run(['Agenda', function(Agenda) {}]);
'use strict';
angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
@ -421,7 +305,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
} }
); );
}); });
} };
// import from csv file // import from csv file
$scope.csv = { $scope.csv = {
@ -447,60 +331,11 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
); );
} }
$scope.csvimported = true; $scope.csvimported = true;
} };
$scope.clear = function () { $scope.clear = function () {
$scope.csv.result = null; $scope.csv.result = null;
}; };
} }
]); ]);
}());
angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
.config([
'slidesProvider',
function(slidesProvider) {
slidesProvider.registerSlide('agenda/item', {
template: 'static/templates/agenda/slide-item-detail.html',
});
slidesProvider.registerSlide('agenda/item-list', {
template: 'static/templates/agenda/slide-item-list.html',
});
}
])
.controller('SlideItemDetailCtrl', [
'$scope',
'Agenda',
'User',
function($scope, Agenda, 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.id;
Agenda.find(id);
User.findAll();
Agenda.bindOne(id, $scope, 'item');
// get flag for list-of-speakers-slide (true/false)
$scope.is_list_of_speakers = $scope.element.list_of_speakers;
}
])
.controller('SlideItemListCtrl', [
'$scope',
'$http',
'Agenda',
'AgendaTree',
function($scope, $http, Agenda, AgendaTree) {
// 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.
Agenda.findAll();
// Bind agenda tree to the scope
$scope.$watch(function () {
return Agenda.lastModified();
}, function () {
$scope.items = AgendaTree.getFlatTree(Agenda.getAll());
});
}
]);

View File

@ -6,7 +6,7 @@ class AssignmentsAppConfig(AppConfig):
verbose_name = 'OpenSlides Assignments' verbose_name = 'OpenSlides Assignments'
angular_site_module = True angular_site_module = True
angular_projector_module = True angular_projector_module = True
js_files = ['js/assignments/assignments.js'] js_files = ['js/assignments/base.js', 'js/assignments/site.js', 'js/assignments/projector.js']
def ready(self): def ready(self):
# Load projector elements. # Load projector elements.

View File

@ -0,0 +1,22 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.assignments', [])
.factory('Assignment', ['DS', 'jsDataModel', function(DS, jsDataModel) {
var name = 'assignments/assignment';
return DS.defineResource({
name: name,
useClass: jsDataModel,
methods: {
getResourceName: function () {
return name;
}
}
});
}])
.run(['Assignment', function(Assignment) {}]);
}());

View File

@ -0,0 +1,22 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.assignments.projector', ['OpenSlidesApp.assignments'])
.config(function(slidesProvider) {
slidesProvider.registerSlide('assignments/assignment', {
template: 'static/templates/assignments/slide_assignment.html',
});
})
.controller('SlideAssignmentCtrl', function($scope, Assignment) {
// 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.id;
Assignment.find(id);
Assignment.bindOne(id, $scope, 'assignment');
});
}());

View File

@ -1,22 +1,6 @@
"use strict"; (function () {
angular.module('OpenSlidesApp.assignments', [])
.factory('Assignment', ['DS', 'jsDataModel', function(DS, jsDataModel) {
var name = 'assignments/assignment'
return DS.defineResource({
name: name,
useClass: jsDataModel,
methods: {
getResourceName: function () {
return name;
}
}
});
}])
.run(['Assignment', function(Assignment) {}]);
'use strict';
angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
@ -94,7 +78,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
}) })
.controller('AssignmentDetailCtrl', function($scope, Assignment, assignment) { .controller('AssignmentDetailCtrl', function($scope, Assignment, assignment) {
Assignment.bindOne(assignment.id, $scope, 'assignment') Assignment.bindOne(assignment.id, $scope, 'assignment');
}) })
.controller('AssignmentCreateCtrl', function($scope, $state, Assignment) { .controller('AssignmentCreateCtrl', function($scope, $state, Assignment) {
@ -119,19 +103,4 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
}; };
}); });
angular.module('OpenSlidesApp.assignments.projector', ['OpenSlidesApp.assignments']) }());
.config(function(slidesProvider) {
slidesProvider.registerSlide('assignments/assignment', {
template: 'static/templates/assignments/slide_assignment.html',
});
})
.controller('SlideAssignmentCtrl', function($scope, Assignment) {
// 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.id;
Assignment.find(id);
Assignment.bindOne(id, $scope, 'assignment');
});

View File

@ -6,7 +6,7 @@ class CoreAppConfig(AppConfig):
verbose_name = 'OpenSlides Core' verbose_name = 'OpenSlides Core'
angular_site_module = True angular_site_module = True
angular_projector_module = True angular_projector_module = True
js_files = ['js/core/core.js'] js_files = ['js/core/base.js', 'js/core/site.js', 'js/core/projector.js']
def ready(self): def ready(self):
# Load projector elements. # Load projector elements.

View File

@ -0,0 +1,218 @@
(function () {
'use strict';
// The core module used for the OpenSlides site and the projector
angular.module('OpenSlidesApp.core', [
'angular-loading-bar',
'js-data',
'gettext',
'ngAnimate',
'ui.bootstrap',
'ui.tree',
'uiSwitch',
])
.config(['DSProvider', 'DSHttpAdapterProvider', function(DSProvider, DSHttpAdapterProvider) {
// 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.basePath = '/rest';
DSProvider.defaults.afterReap = function(model, items) {
if (items.length > 5) {
model.findAll({}, {bypassCache: true});
} else {
_.forEach(items, function (item) {
model.refresh(item[model.idAttribute]);
});
}
};
DSHttpAdapterProvider.defaults.forceTrailingSlash = true;
}])
.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;
})
.run(['DS', 'autoupdate', 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
});
}])
.factory('loadGlobalData', [
'$rootScope',
'$http',
'Config',
'Projector',
function ($rootScope, $http, Config, Projector) {
return function () {
// Puts the config object into each scope.
Config.findAll().then(function() {
$rootScope.config = function(key) {
try {
return Config.get(key).value;
}
catch(err) {
console.log("Unkown config key: " + key);
return '';
}
};
});
// Loads all projector data
Projector.findAll();
// Loads server time and calculates server offset
$http.get('/core/servertime/').then(function(data) {
$rootScope.serverOffset = Math.floor( Date.now() / 1000 - data.data );
});
};
}
])
// Load the global data on startup
.run([
'loadGlobalData',
function(loadGlobalData, operator) {
loadGlobalData();
}
])
.factory('jsDataModel', ['$http', 'Projector', function($http, Projector) {
var BaseModel = function() {};
BaseModel.prototype.project = function() {
return $http.post(
'/rest/core/projector/1/prune_elements/',
[{name: this.getResourceName(), id: this.id}]
);
};
BaseModel.prototype.isProjected = function() {
// Returns true if there is a projector element with the same
// name and the same id.
var projector = Projector.get(1);
if (typeof projector === 'undefined') return false;
var self = this;
var predicate = function (element) {
return element.name == self.getResourceName() &&
typeof element.id !== 'undefined' &&
element.id == self.id;
};
return typeof _.findKey(projector.elements, predicate) === 'string';
};
return BaseModel;
}])
.factory('Customslide', ['DS', 'jsDataModel', function(DS, jsDataModel) {
var name = 'core/customslide';
return DS.defineResource({
name: name,
useClass: jsDataModel,
methods: {
getResourceName: function () {
return name;
},
},
});
}])
.factory('Tag', ['DS', function(DS) {
return DS.defineResource({
name: 'core/tag',
});
}])
.factory('Config', ['DS', function(DS) {
return DS.defineResource({
name: 'core/config',
idAttribute: 'key',
});
}])
/* Model for a projector.
*
* At the moment we use only one projector, so there will be only one object
* in this model. It has the id 1. For later releases there will be multiple
* projector objects.
*
* This model uses onConfilict: 'replace' instead of 'merge'. This is necessary
* because the keys of the projector objects can change and old keys have to
* be removed. See http://www.js-data.io/docs/dsdefaults#onconflict for
* more information.
*/
.factory('Projector', ['DS', function(DS) {
return DS.defineResource({
name: 'core/projector',
onConflict: 'replace',
});
}])
/* Converts number of seconds into string "hh:mm:ss" or "mm:ss" */
.filter('osSecondsToTime', [
function () {
return function (totalseconds) {
var time;
var total = Math.abs(totalseconds);
if (parseInt(totalseconds)) {
var hh = Math.floor(total / 3600);
var mm = Math.floor(total % 3600 / 60);
var ss = Math.floor(total % 60);
var zero = "0";
// Add leading "0" for double digit values
hh = (zero+hh).slice(-2);
mm = (zero+mm).slice(-2);
ss = (zero+ss).slice(-2);
if (hh == "00")
time = mm + ':' + ss;
else
time = hh + ":" + mm + ":" + ss;
if (totalseconds < 0)
time = "-"+time;
} else {
time = "--:--";
}
return time;
};
}
])
// Make sure that the DS factories are loaded by making them a dependency
.run(['Projector', 'Config', 'Tag', 'Customslide', function(Projector, Config, Tag, Customslide){}]);
}());

View File

@ -0,0 +1,136 @@
(function () {
'use strict';
// 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',
});
slidesProvider.registerSlide('core/countdown', {
template: 'static/templates/core/slide_countdown.html',
});
slidesProvider.registerSlide('core/message', {
template: 'static/templates/core/slide_message.html',
});
})
.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) {
if (!element.error) {
$scope.elements.push(element);
} else {
console.error("Error for slide " + element.name + ": " + element.error);
}
});
$scope.scroll = -10 * Projector.get(1).scroll;
$scope.scale = 100 + 20 * Projector.get(1).scale;
});
});
})
.controller('SlideCustomSlideCtrl', [
'$scope',
'Customslide',
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.id;
Customslide.find(id);
Customslide.bindOne(id, $scope, 'customslide');
}
])
.controller('SlideClockCtrl', [
'$scope',
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.servertime = ( Date.now() / 1000 - $scope.serverOffset ) * 1000;
}
])
.controller('SlideCountdownCtrl', [
'$scope',
'$interval',
function($scope, $interval) {
// 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.seconds = Math.floor( $scope.element.countdown_time - Date.now() / 1000 + $scope.serverOffset );
$scope.status = $scope.element.status;
$scope.visible = $scope.element.visible;
$scope.index = $scope.element.index;
$scope.description = $scope.element.description;
// start interval timer if countdown status is running
var interval;
if ($scope.status == "running") {
interval = $interval( function() {
$scope.seconds = Math.floor( $scope.element.countdown_time - Date.now() / 1000 + $scope.serverOffset );
}, 1000);
} else {
$scope.seconds = $scope.element.countdown_time;
}
$scope.$on('$destroy', function() {
// Cancel the interval if the controller is destroyed
$interval.cancel(interval);
});
}
])
.controller('SlideMessageCtrl', [
'$scope',
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.message = $scope.element.message;
$scope.visible = $scope.element.visible;
}
]);
}());

View File

@ -1,218 +1,6 @@
"use strict"; (function () {
// The core module used for the OpenSlides site and the projector
angular.module('OpenSlidesApp.core', [
'angular-loading-bar',
'js-data',
'gettext',
'ngAnimate',
'ui.bootstrap',
'ui.tree',
'uiSwitch',
])
.config(['DSProvider', 'DSHttpAdapterProvider', function(DSProvider, DSHttpAdapterProvider) {
// 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.basePath = '/rest';
DSProvider.defaults.afterReap = function(model, items) {
if (items.length > 5) {
model.findAll({}, {bypassCache: true});
} else {
_.forEach(items, function (item) {
model.refresh(item[model.idAttribute]);
});
}
};
DSHttpAdapterProvider.defaults.forceTrailingSlash = true;
}])
.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;
})
.run(['DS', 'autoupdate', 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
});
}])
.factory('loadGlobalData', [
'$rootScope',
'$http',
'Config',
'Projector',
function ($rootScope, $http, Config, Projector) {
return function () {
// Puts the config object into each scope.
Config.findAll().then(function() {
$rootScope.config = function(key) {
try {
return Config.get(key).value;
}
catch(err) {
console.log("Unkown config key: " + key);
return ''
}
}
});
// Loads all projector data
Projector.findAll();
// Loads server time and calculates server offset
$http.get('/core/servertime/').then(function(data) {
$rootScope.serverOffset = Math.floor( Date.now() / 1000 - data.data );
});
}
}
])
// Load the global data on startup
.run([
'loadGlobalData',
function(loadGlobalData, operator) {
loadGlobalData();
}
])
.factory('jsDataModel', ['$http', 'Projector', function($http, Projector) {
var BaseModel = function() {};
BaseModel.prototype.project = function() {
return $http.post(
'/rest/core/projector/1/prune_elements/',
[{name: this.getResourceName(), id: this.id}]
);
};
BaseModel.prototype.isProjected = function() {
// Returns true if there is a projector element with the same
// name and the same id.
var projector = Projector.get(1);
if (typeof projector === 'undefined') return false;
var self = this;
var predicate = function (element) {
return element.name == self.getResourceName() &&
typeof element.id !== 'undefined' &&
element.id == self.id;
};
return typeof _.findKey(projector.elements, predicate) === 'string';
};
return BaseModel;
}])
.factory('Customslide', ['DS', 'jsDataModel', function(DS, jsDataModel) {
var name = 'core/customslide'
return DS.defineResource({
name: name,
useClass: jsDataModel,
methods: {
getResourceName: function () {
return name;
},
},
});
}])
.factory('Tag', ['DS', function(DS) {
return DS.defineResource({
name: 'core/tag',
});
}])
.factory('Config', ['DS', function(DS) {
return DS.defineResource({
name: 'core/config',
idAttribute: 'key',
});
}])
/* Model for a projector.
*
* At the moment we use only one projector, so there will be only one object
* in this model. It has the id 1. For later releases there will be multiple
* projector objects.
*
* This model uses onConfilict: 'replace' instead of 'merge'. This is necessary
* because the keys of the projector objects can change and old keys have to
* be removed. See http://www.js-data.io/docs/dsdefaults#onconflict for
* more information.
*/
.factory('Projector', ['DS', function(DS) {
return DS.defineResource({
name: 'core/projector',
onConflict: 'replace',
});
}])
/* Converts number of seconds into string "hh:mm:ss" or "mm:ss" */
.filter('osSecondsToTime', [
function () {
return function (totalseconds) {
var time;
var total = Math.abs(totalseconds);
if (parseInt(totalseconds)) {
var hh = Math.floor(total / 3600);
var mm = Math.floor(total % 3600 / 60);
var ss = Math.floor(total % 60);
var zero = "0";
// Add leading "0" for double digit values
hh = (zero+hh).slice(-2);
mm = (zero+mm).slice(-2);
ss = (zero+ss).slice(-2);
if (hh == "00")
time = mm + ':' + ss;
else
time = hh + ":" + mm + ":" + ss;
if (totalseconds < 0)
time = "-"+time;
} else {
time = "--:--";
}
return time;
};
}
])
// Make sure that the DS factories are loaded by making them a dependency
.run(['Projector', 'Config', 'Tag', 'Customslide', function(Projector, Config, Tag, Customslide){}]);
'use strict';
// The core module for the OpenSlides site // The core module for the OpenSlides site
angular.module('OpenSlidesApp.core.site', [ angular.module('OpenSlidesApp.core.site', [
@ -243,7 +31,7 @@ angular.module('OpenSlidesApp.core.site', [
var that = this; var that = this;
this.scope = scope; this.scope = scope;
this.updateMainMenu(); this.updateMainMenu();
operator.onOperatorChange(function () {that.updateMainMenu()}); operator.onOperatorChange(function () {that.updateMainMenu();});
}, },
updateMainMenu: function () { updateMainMenu: function () {
this.scope.elements = this.getElements(); this.scope.elements = this.getElements();
@ -258,7 +46,7 @@ angular.module('OpenSlidesApp.core.site', [
}); });
return elements; return elements;
} }
} };
}]; }];
} }
]) ])
@ -318,7 +106,7 @@ angular.module('OpenSlidesApp.core.site', [
.config(function($stateProvider, $urlMatcherFactoryProvider) { .config(function($stateProvider, $urlMatcherFactoryProvider) {
// Make the trailing slash optional // Make the trailing slash optional
$urlMatcherFactoryProvider.strictMode(false) $urlMatcherFactoryProvider.strictMode(false);
// Use stateProvider.decorator to give default values to our states // Use stateProvider.decorator to give default values to our states
$stateProvider.decorator('views', function(state, parent) { $stateProvider.decorator('views', function(state, parent) {
@ -346,7 +134,7 @@ angular.module('OpenSlidesApp.core.site', [
if (_.last(patterns).match(/(create|update)/)) { if (_.last(patterns).match(/(create|update)/)) {
// When state_patterns is in the form "app.module.create" or // When state_patterns is in the form "app.module.create" or
// "app.module.update", use the form template. // "app.module.update", use the form template.
templateUrl = 'static/templates/' + patterns[0] + '/' + patterns[1] + '-form.html' templateUrl = 'static/templates/' + patterns[0] + '/' + patterns[1] + '-form.html';
} else { } else {
// Replaces the first point through a slash (the app name) // Replaces the first point through a slash (the app name)
var appName = state.name.replace('.', '/'); var appName = state.name.replace('.', '/');
@ -385,7 +173,7 @@ angular.module('OpenSlidesApp.core.site', [
} }
state.url = state.url || defaultUrl; state.url = state.url || defaultUrl;
return parent(state) return parent(state);
}); });
}) })
@ -487,8 +275,8 @@ angular.module('OpenSlidesApp.core.site', [
addState: function(name, state) { addState: function(name, state) {
$stateProvider.state(name, state); $stateProvider.state(name, state);
} }
} };
} };
}) })
// Load the django url patterns // Load the django url patterns
@ -540,7 +328,7 @@ angular.module('OpenSlidesApp.core.site', [
$scope.value = config.value; $scope.value = config.value;
$scope.help_text = field.help_text; $scope.help_text = field.help_text;
} }
} };
}) })
.controller("MainMenuCtrl", [ .controller("MainMenuCtrl", [
@ -562,7 +350,7 @@ angular.module('OpenSlidesApp.core.site', [
if (lang != 'en') { if (lang != 'en') {
gettextCatalog.loadRemote("static/i18n/" + lang + ".json"); gettextCatalog.loadRemote("static/i18n/" + lang + ".json");
} }
} };
}) })
.controller("LoginFormCtrl", function ($scope, $modal) { .controller("LoginFormCtrl", function ($scope, $modal) {
@ -573,7 +361,7 @@ angular.module('OpenSlidesApp.core.site', [
controller: 'LoginFormModalCtrl', controller: 'LoginFormModalCtrl',
size: 'sm', size: 'sm',
}); });
} };
}) })
.controller('LoginFormModalCtrl', [ .controller('LoginFormModalCtrl', [
@ -626,7 +414,7 @@ angular.module('OpenSlidesApp.core.site', [
$scope.save = function(key, value) { $scope.save = function(key, value) {
Config.get(key).value = value; Config.get(key).value = value;
Config.save(key); Config.save(key);
} };
}) })
// Customslide Controller // Customslide Controller
@ -698,7 +486,7 @@ angular.module('OpenSlidesApp.core.site', [
// *** countdown functions *** // *** countdown functions ***
$scope.calculateCountdownTime = function (countdown) { $scope.calculateCountdownTime = function (countdown) {
countdown.seconds = Math.floor( countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset ); countdown.seconds = Math.floor( countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset );
} };
$scope.rebuildAllElements = function () { $scope.rebuildAllElements = function () {
$scope.countdowns = []; $scope.countdowns = [];
$scope.messages = []; $scope.messages = [];
@ -721,7 +509,7 @@ angular.module('OpenSlidesApp.core.site', [
}); });
$scope.scrollLevel = Projector.get(1).scroll; $scope.scrollLevel = Projector.get(1).scroll;
$scope.scaleLevel = Projector.get(1).scale; $scope.scaleLevel = Projector.get(1).scale;
} };
// get initial values for $scope.countdowns, $scope.messages, $scope.scrollLevel // get initial values for $scope.countdowns, $scope.messages, $scope.scrollLevel
// and $scope.scaleLevel (after page reload) // and $scope.scaleLevel (after page reload)
@ -943,134 +731,4 @@ angular.module('OpenSlidesApp.core.site', [
}; };
}); });
}());
// 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',
});
slidesProvider.registerSlide('core/countdown', {
template: 'static/templates/core/slide_countdown.html',
});
slidesProvider.registerSlide('core/message', {
template: 'static/templates/core/slide_message.html',
});
})
.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) {
if (!element.error) {
$scope.elements.push(element);
} else {
console.error("Error for slide " + element.name + ": " + element.error)
}
});
$scope.scroll = -10 * Projector.get(1).scroll;
$scope.scale = 100 + 20 * Projector.get(1).scale;
});
});
})
.controller('SlideCustomSlideCtrl', [
'$scope',
'Customslide',
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.id;
Customslide.find(id);
Customslide.bindOne(id, $scope, 'customslide');
}
])
.controller('SlideClockCtrl', [
'$scope',
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.servertime = ( Date.now() / 1000 - $scope.serverOffset ) * 1000;
}
])
.controller('SlideCountdownCtrl', [
'$scope',
'$interval',
function($scope, $interval) {
// 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.seconds = Math.floor( $scope.element.countdown_time - Date.now() / 1000 + $scope.serverOffset );
$scope.status = $scope.element.status;
$scope.visible = $scope.element.visible;
$scope.index = $scope.element.index;
$scope.description = $scope.element.description;
// start interval timer if countdown status is running
var interval;
if ($scope.status == "running") {
interval = $interval( function() {
$scope.seconds = Math.floor( $scope.element.countdown_time - Date.now() / 1000 + $scope.serverOffset );
}, 1000);
} else {
$scope.seconds = $scope.element.countdown_time;
}
$scope.$on('$destroy', function() {
// Cancel the interval if the controller is destroyed
$interval.cancel(interval);
});
}
])
.controller('SlideMessageCtrl', [
'$scope',
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.message = $scope.element.message;
$scope.visible = $scope.element.visible;
}
]);

View File

@ -6,7 +6,7 @@ class MediafilesAppConfig(AppConfig):
verbose_name = 'OpenSlides Mediafiles' verbose_name = 'OpenSlides Mediafiles'
angular_site_module = True angular_site_module = True
angular_projector_module = True angular_projector_module = True
js_files = ['js/mediafiles/mediafiles.js'] js_files = ['js/mediafiles/base.js', 'js/mediafiles/site.js', 'js/mediafiles/projector.js']
def ready(self): def ready(self):
# Load projector elements. # Load projector elements.

View File

@ -0,0 +1,28 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.mediafiles', [])
.factory('Mediafile', ['DS', function(DS) {
return DS.defineResource({
name: 'mediafiles/mediafile',
computed: {
is_presentable: ['filetype', function (filetype) {
var PRESENTABLE_FILE_TYPES = ['application/pdf'];
return _.contains(PRESENTABLE_FILE_TYPES, filetype);
}],
filename: [function () {
var filename = this.mediafile.name;
return /\/(.+?)$/.exec(filename)[1];
}],
title_or_filename: ['title', 'mediafile', function (title) {
return title || this.filename;
}]
}
});
}])
.run(['Mediafile', function(Mediafile) {}]);
}());

View File

@ -0,0 +1,7 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.mediafiles.projector', ['OpenSlidesApp.mediafiles']);
}());

View File

@ -1,49 +1,6 @@
"use strict"; (function () {
angular.module('OpenSlidesApp.mediafiles', []) 'use strict';
.factory('Mediafile', ['DS', function(DS) {
return DS.defineResource({
name: 'mediafiles/mediafile',
computed: {
is_presentable: ['filetype', function (filetype) {
var PRESENTABLE_FILE_TYPES = ['application/pdf']
return _.contains(PRESENTABLE_FILE_TYPES, filetype);
}],
filename: [function () {
var filename = this.mediafile.name;
return /\/(.+?)$/.exec(filename)[1];
}],
title_or_filename: ['title', 'mediafile', function (title) {
return title || this.filename;
}]
}
});
}])
.run(['Mediafile', function(Mediafile) {}]);
function uploadFile($timeout, $scope, $state, Upload, mediafile) {
return function(file) {
file.upload = Upload.upload({
url: '/rest/mediafiles/mediafile/' + (mediafile ? mediafile.id : ''),
method: mediafile ? 'PUT' : 'POST',
fields: {title: file.title},
file: file.mediafile,
fileFormDataName: 'mediafile'
});
file.upload.then(function (response) {
$timeout(function () {
file.result = response.data;
$state.go('mediafiles.mediafile.list');
});
}, function (response) {
if (response.status > 0)
$scope.errorMsg = response.status + ': ' + response.data;
});
}
}
angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.mediafiles']) angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.mediafiles'])
@ -134,5 +91,26 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
$scope.save = uploadFile($timeout, $scope, $state, Upload, mediafile); $scope.save = uploadFile($timeout, $scope, $state, Upload, mediafile);
}); });
function uploadFile($timeout, $scope, $state, Upload, mediafile) {
return function(file) {
file.upload = Upload.upload({
url: '/rest/mediafiles/mediafile/' + (mediafile ? mediafile.id : ''),
method: mediafile ? 'PUT' : 'POST',
fields: {title: file.title},
file: file.mediafile,
fileFormDataName: 'mediafile'
});
angular.module('OpenSlidesApp.mediafiles.projector', ['OpenSlidesApp.mediafiles']); file.upload.then(function (response) {
$timeout(function () {
file.result = response.data;
$state.go('mediafiles.mediafile.list');
});
}, function (response) {
if (response.status > 0)
$scope.errorMsg = response.status + ': ' + response.data;
});
};
}
}());

View File

@ -0,0 +1,38 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0002_auto_20150630_0143'),
]
operations = [
migrations.AlterModelOptions(
name='user',
options={
'ordering': ('last_name', 'first_name', 'username'),
'permissions': (
('can_see_name', 'Can see names of users'),
('can_see_extra_data', 'Can see extra data of users'),
('can_manage', 'Can manage users'))},
),
migrations.AlterField(
model_name='user',
name='about_me',
field=models.TextField(blank=True, help_text='Profile text.', default='', verbose_name='About me'),
),
migrations.AlterField(
model_name='user',
name='is_active',
field=models.BooleanField(
help_text='Designates whether this user should be treated as active. Unselect this instead of deleting the account.',
default=True,
verbose_name='Active'),
),
migrations.AlterField(
model_name='user',
name='is_present',
field=models.BooleanField(help_text='Designates whether this user is in the room or not.', default=False, verbose_name='Present'),
),
]

View File

@ -0,0 +1,11 @@
from unittest import TestCase
from openslides.utils import utils
class ToRomanTest(TestCase):
def test_to_roman_result(self):
self.assertEqual(utils.to_roman(3), 'III')
def test_to_roman_none(self):
self.assertTrue(utils.to_roman(-3) is None)