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'
|
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.
|
||||||
|
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";
|
(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());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
]);
|
|
@ -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.
|
||||||
|
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";
|
(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');
|
|
||||||
});
|
|
@ -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.
|
||||||
|
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";
|
(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)
|
||||||
@ -762,7 +550,7 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
};
|
};
|
||||||
$scope.editCountdown = function (countdown) {
|
$scope.editCountdown = function (countdown) {
|
||||||
var data = {};
|
var data = {};
|
||||||
data[countdown.uuid] = {
|
data[countdown.uuid] = {
|
||||||
"description": countdown.description,
|
"description": countdown.description,
|
||||||
"default": parseInt(countdown.default)
|
"default": parseInt(countdown.default)
|
||||||
};
|
};
|
||||||
@ -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'
|
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.
|
||||||
|
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', [])
|
'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;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}());
|
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