Split and cleaned up JS files. Added test.
This commit is contained in:
parent
0e3688fc33
commit
32b23df763
@ -6,7 +6,7 @@ class AgendaAppConfig(AppConfig):
|
||||
verbose_name = 'OpenSlides Agenda'
|
||||
angular_site_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):
|
||||
# Load projector elements.
|
||||
|
122
openslides/agenda/static/js/agenda/base.js
Normal file
122
openslides/agenda/static/js/agenda/base.js
Normal 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) {}]);
|
||||
|
||||
}());
|
55
openslides/agenda/static/js/agenda/projector.js
Normal file
55
openslides/agenda/static/js/agenda/projector.js
Normal 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());
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
||||
}());
|
@ -1,122 +1,6 @@
|
||||
"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) {}]);
|
||||
(function () {
|
||||
|
||||
'use strict';
|
||||
|
||||
angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
|
||||
@ -421,7 +305,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// import from csv file
|
||||
$scope.csv = {
|
||||
@ -447,60 +331,11 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
);
|
||||
}
|
||||
$scope.csvimported = true;
|
||||
}
|
||||
};
|
||||
$scope.clear = function () {
|
||||
$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());
|
||||
});
|
||||
}
|
||||
]);
|
||||
}());
|
@ -6,7 +6,7 @@ class AssignmentsAppConfig(AppConfig):
|
||||
verbose_name = 'OpenSlides Assignments'
|
||||
angular_site_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):
|
||||
# Load projector elements.
|
||||
|
22
openslides/assignments/static/js/assignments/base.js
Normal file
22
openslides/assignments/static/js/assignments/base.js
Normal 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) {}]);
|
||||
|
||||
}());
|
22
openslides/assignments/static/js/assignments/projector.js
Normal file
22
openslides/assignments/static/js/assignments/projector.js
Normal 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');
|
||||
});
|
||||
|
||||
}());
|
@ -1,22 +1,6 @@
|
||||
"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) {}]);
|
||||
(function () {
|
||||
|
||||
'use strict';
|
||||
|
||||
angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
|
||||
@ -94,7 +78,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
})
|
||||
|
||||
.controller('AssignmentDetailCtrl', function($scope, Assignment, assignment) {
|
||||
Assignment.bindOne(assignment.id, $scope, 'assignment')
|
||||
Assignment.bindOne(assignment.id, $scope, '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');
|
||||
});
|
||||
}());
|
@ -6,7 +6,7 @@ class CoreAppConfig(AppConfig):
|
||||
verbose_name = 'OpenSlides Core'
|
||||
angular_site_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):
|
||||
# Load projector elements.
|
||||
|
218
openslides/core/static/js/core/base.js
Normal file
218
openslides/core/static/js/core/base.js
Normal 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){}]);
|
||||
|
||||
}());
|
136
openslides/core/static/js/core/projector.js
Normal file
136
openslides/core/static/js/core/projector.js
Normal 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;
|
||||
}
|
||||
]);
|
||||
|
||||
}());
|
@ -1,218 +1,6 @@
|
||||
"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){}]);
|
||||
(function () {
|
||||
|
||||
'use strict';
|
||||
|
||||
// The core module for the OpenSlides site
|
||||
angular.module('OpenSlidesApp.core.site', [
|
||||
@ -243,7 +31,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
var that = this;
|
||||
this.scope = scope;
|
||||
this.updateMainMenu();
|
||||
operator.onOperatorChange(function () {that.updateMainMenu()});
|
||||
operator.onOperatorChange(function () {that.updateMainMenu();});
|
||||
},
|
||||
updateMainMenu: function () {
|
||||
this.scope.elements = this.getElements();
|
||||
@ -258,7 +46,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
});
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
}
|
||||
])
|
||||
@ -318,7 +106,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
|
||||
.config(function($stateProvider, $urlMatcherFactoryProvider) {
|
||||
// Make the trailing slash optional
|
||||
$urlMatcherFactoryProvider.strictMode(false)
|
||||
$urlMatcherFactoryProvider.strictMode(false);
|
||||
|
||||
// Use stateProvider.decorator to give default values to our states
|
||||
$stateProvider.decorator('views', function(state, parent) {
|
||||
@ -346,7 +134,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
if (_.last(patterns).match(/(create|update)/)) {
|
||||
// When state_patterns is in the form "app.module.create" or
|
||||
// "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 {
|
||||
// Replaces the first point through a slash (the app name)
|
||||
var appName = state.name.replace('.', '/');
|
||||
@ -385,7 +173,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
}
|
||||
|
||||
state.url = state.url || defaultUrl;
|
||||
return parent(state)
|
||||
return parent(state);
|
||||
});
|
||||
})
|
||||
|
||||
@ -487,8 +275,8 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
addState: function(name, state) {
|
||||
$stateProvider.state(name, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
|
||||
// Load the django url patterns
|
||||
@ -540,7 +328,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
$scope.value = config.value;
|
||||
$scope.help_text = field.help_text;
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
.controller("MainMenuCtrl", [
|
||||
@ -562,7 +350,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
if (lang != 'en') {
|
||||
gettextCatalog.loadRemote("static/i18n/" + lang + ".json");
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
.controller("LoginFormCtrl", function ($scope, $modal) {
|
||||
@ -573,7 +361,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
controller: 'LoginFormModalCtrl',
|
||||
size: 'sm',
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
.controller('LoginFormModalCtrl', [
|
||||
@ -626,7 +414,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
$scope.save = function(key, value) {
|
||||
Config.get(key).value = value;
|
||||
Config.save(key);
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
// Customslide Controller
|
||||
@ -698,7 +486,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
// *** countdown functions ***
|
||||
$scope.calculateCountdownTime = function (countdown) {
|
||||
countdown.seconds = Math.floor( countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
||||
}
|
||||
};
|
||||
$scope.rebuildAllElements = function () {
|
||||
$scope.countdowns = [];
|
||||
$scope.messages = [];
|
||||
@ -721,7 +509,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
});
|
||||
$scope.scrollLevel = Projector.get(1).scroll;
|
||||
$scope.scaleLevel = Projector.get(1).scale;
|
||||
}
|
||||
};
|
||||
|
||||
// get initial values for $scope.countdowns, $scope.messages, $scope.scrollLevel
|
||||
// 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;
|
||||
}
|
||||
]);
|
||||
}());
|
@ -6,7 +6,7 @@ class MediafilesAppConfig(AppConfig):
|
||||
verbose_name = 'OpenSlides Mediafiles'
|
||||
angular_site_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):
|
||||
# Load projector elements.
|
||||
|
28
openslides/mediafiles/static/js/mediafiles/base.js
Normal file
28
openslides/mediafiles/static/js/mediafiles/base.js
Normal 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) {}]);
|
||||
|
||||
}());
|
7
openslides/mediafiles/static/js/mediafiles/projector.js
Normal file
7
openslides/mediafiles/static/js/mediafiles/projector.js
Normal file
@ -0,0 +1,7 @@
|
||||
(function () {
|
||||
|
||||
'use strict';
|
||||
|
||||
angular.module('OpenSlidesApp.mediafiles.projector', ['OpenSlidesApp.mediafiles']);
|
||||
|
||||
}());
|
@ -1,49 +1,6 @@
|
||||
"use strict";
|
||||
(function () {
|
||||
|
||||
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) {}]);
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
'use strict';
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
}());
|
38
openslides/users/migrations/0003_auto_20151021_2320.py
Normal file
38
openslides/users/migrations/0003_auto_20151021_2320.py
Normal 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'),
|
||||
),
|
||||
]
|
11
tests/unit/utils/test_utils.py
Normal file
11
tests/unit/utils/test_utils.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user