Merge pull request #3924 from ostcar/remove-old-client

remove old client
This commit is contained in:
Oskar Hahn 2018-10-26 06:24:49 +02:00 committed by GitHub
commit 1c99c0c40a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
222 changed files with 97 additions and 42579 deletions

View File

@ -18,7 +18,7 @@ matrix:
- flake8 openslides tests
- isort --check-only --diff --recursive openslides tests
- python -m mypy openslides/
- python -W ignore -m pytest --cov --cov-fail-under=75
- python -W ignore -m pytest --cov --cov-fail-under=70
- language: python
cache:
@ -35,7 +35,7 @@ matrix:
- flake8 openslides tests
- isort --check-only --diff --recursive openslides tests
- python -m mypy openslides/
- python -W ignore -m pytest --cov --cov-fail-under=75
- python -W ignore -m pytest --cov --cov-fail-under=70
- language: node_js
node_js:

View File

@ -43,31 +43,18 @@ See step 1. b. in the installation section in the `README.rst
<https://github.com/OpenSlides/OpenSlides/blob/master/README.rst>`_.
d. Install dependencies
'''''''''''''''''''''''
d. Finish the server
''''''''''''''''''''
Install all required Python packages::
$ pip install --requirement requirements.txt
Install all Node.js and Bower packages and run several JavaScript build tasks::
Create a settings file, run migrations and start the server::
$ yarn
Optional: To enhance performance run Gulp in production mode::
$ node_modules/.bin/gulp --production
e. Start OpenSlides
'''''''''''''''''''
Use the command-line interface::
$ python manage.py start
See step 1. d. in the installation section in the `README.rst
<https://github.com/OpenSlides/OpenSlides/blob/master/README.rst>`_.
$ python manage.py createsettings
$ python manage.py migrate
$ python manage.py runserver
To get help on the command line options run::
@ -84,13 +71,25 @@ When debugging something email related change the email backend to console::
$ python manage.py start --debug-email
To start OpenSlides with Daphne run::
e. Setup and start the client
'''''''''''''''''''''''''''''
$ python manage.py runserver
Go in the client's directory in a second command-line interface::
Use gulp watch in a second command-line interface::
$ cd client/
$ node_modules/.bin/gulp watch
Install all dependencies and start the development server::
$ npm install
$ yarn start
Now the client is available under ``localhost:4200``.
If you do not need to work with the client, you can build the client and let it be delivered by the server directly::
$ yarn build
The client's address is now ``localhost:8000``.
2. Installation on Windows
@ -127,16 +126,20 @@ To run some server tests see `.travis.yml
<https://github.com/OpenSlides/OpenSlides/blob/master/.travis.yml>`_.
b. Running AngularJS test cases
'''''''''''''''''''''''''''''''
b. Client tests and commands
''''''''''''''''''''''''''''
Run client tests by starting karma::
Change to the client's directory to run every client related command. Run client tests::
$ yarn run karma
$ yarn test
Watch for file changes and run the tests automatically after each change::
Fix the code format and lint it with::
$ yarn run karma:watch
$ yarn pretty-quick && yarn lint
To extract translations run::
$ yarn extract
OpenSlides in big mode
======================
@ -207,23 +210,7 @@ This is an example configuration for a single Daphne listen on port 8000::
server_name _;
location ~* ^/projector.*$ {
rewrite ^.*$ /static/templates/projector-container.html;
}
location ~* ^/real-projector.*$ {
rewrite ^.*$ /static/templates/projector.html;
}
location ~* ^/webclient.*$ {
rewrite ^/webclient/(site|projector).*$ /static/js/webclient-$1.js;
}
location /static {
alias <your path to>/collected-static;
}
location ~* ^/(?!ws|wss|media|rest|views).*$ {
rewrite ^.*$ /static/templates/index.html;
}
location / {
location ~* ^/(ws|wss|media|rest|apps).*$ {
proxy_pass http://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
@ -232,4 +219,7 @@ This is an example configuration for a single Daphne listen on port 8000::
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
}
location / {
alias <your path to>/collected-static;
}
}

View File

@ -165,53 +165,7 @@ OpenSlides uses the following projects or parts of them:
* Several Python packages (see ``requirements/production.txt`` and ``requirements/big_mode.txt``).
* Several JavaScript packages (see ``bower.json``)
* `angular <http://angularjs.org>`_, License: MIT
* `angular-animate <http://angularjs.org>`_, License: MIT
* `angular-bootstrap-colorpicker <https://github.com/buberdds/angular-bootstrap-colorpicker>`_, License: MIT
* `angular-chosen-localytics <http://github.com/leocaseiro/angular-chosen>`_, License: MIT
* `angular-ckeditor <https://github.com/lemonde/angular-ckeditor>`_, License: MIT
* `angular-file-saver <https://github.com/alferov/angular-file-saver>`_, License: MIT
* `angular-formly <http://formly-js.github.io/angular-formly/>`_, License: MIT
* `angular-formly-templates-bootstrap <https://github.com/formly-js/angular-formly-templates-bootstrap>`_, License: MIT
* `angular-gettext <http://angular-gettext.rocketeer.be/>`_, License: MIT
* `angular-messages <http://angularjs.org>`_, License: MIT
* `angular-pdf <http://github.com/sayanee/angularjs-pdf>`_, License: MIT
* `angular-sanitize <http://angularjs.org>`_, License: MIT
* `angular-scroll-glue <https://github.com/Luegg/angularjs-scroll-glue>`_, License: MIT
* `angular-ui-bootstrap <http://angular-ui.github.io/bootstrap/>`_, License: MIT
* `angular-ui-router <http://angular-ui.github.io/ui-router/>`_, License: MIT
* `angular-ui-router-title <https://github.com/nonplus/angular-ui-router-title>`_, License: MIT
* `angular-ui-tree <https://github.com/angular-ui-tree/angular-ui-tree>`_, License: MIT
* `angular-xeditable <https://github.com/vitalets/angular-xeditable>`_, License: MIT
* `angularjs-scroll-glue <https://github.com/Luegg/angularjs-scroll-glue>`_, License: MIT
* `angularjs-slider <https://github.com/angular-slider/angularjs-slider>`_, License: MIT
* `api-check <https://github.com/kentcdodds/api-check>`_, License: MIT
* `blob-polyfill <https://github.com/bjornstar/blob-polyfill>`_, License: MIT
* `bootstrap <http://getbootstrap.com>`_, License: MIT
* `bootstrap <http://getbootstrap.com>`_, License: MIT
* `bootstrap-css-only <https://getbootstrap.com/>`_, License: MIT
* `bootstrap-ui-datetime-picker <https://github.com/Gillardo/bootstrap-ui-datetime-picker>`_, License: MIT
* `chosen <https://harvesthq.github.io/chosen/>`_, License: MIT
* `ckeditor <https://ckeditor.com/ckeditor-4/>`_, License: (GPL-2.0 OR LGPL-2.1 OR MPL-1.1)
* `docxtemplater <https://github.com/open-xml-templating/docxtemplater>`_, License: MIT
* `file-saver.js <https://github.com/Teleborder/FileSaver.js>`_, License: LICENSE.md
* `font-awesome-bower <https://github.com/tdg5/font-awesome-bower>`_, License: MIT
* `jquery <https://jquery.com>`_, License: MIT
* `jquery.cookie <https://plugins.jquery.com/cookie>`_, License: MIT
* `js-data <http://www.js-data.io>`_, License: MIT
* `js-data-angular <https://github.com/js-data/js-data-angular>`_, License: MIT
* `jszip <http://stuartk.com/jszip>`_, License: MIT or GPLv3
* `lodash <https://lodash.com/>`_, License: MIT
* `ng-dialog <https://github.com/likeastore/ngDialog>`_, License: MIT
* `ng-file-upload <https://github.com/danialfarid/ng-file-upload>`_, License: MIT
* `ngbootbox <https://github.com/eriktufvesson/ngBootbox>`_, License: MIT
* `ngStorage <https://github.com/gsklee/ngStorage>`_, License: MIT
* `papaparse <http://papaparse.com>`_, License: MIT
* `pdfjs-dist <http://mozilla.github.io/pdf.js/>`_, License: Apache-2.0
* `pdfmake <https://bpampuch.github.io/pdfmake>`_, License: MIT
* `roboto-fontface <https://github.com/choffmeister/roboto-fontface-bower>`_, License: Apache-2.0
* Several JavaScript packages (see ``client/package.json``)
License and authors

View File

@ -1,64 +0,0 @@
{
"name": "openslides",
"private": true,
"dependencies": {
"bootstrap": "~3.3.7",
"jquery": "~3.1.0",
"angular": "~1.5.8",
"angular-animate": "~1.5.8",
"angular-bootstrap": "~2.1.3",
"angular-bootstrap-colorpicker": "~3.0.25",
"angular-chosen-localytics": "~1.5.0",
"angular-ckeditor": "~1.0.3",
"angular-file-saver": "~1.1.2",
"angular-formly": "~8.4.0",
"angular-formly-templates-bootstrap": "~6.2.0",
"angular-gettext": "~2.3.7",
"angular-messages": "~1.5.8",
"angular-pdf": "1.3.0",
"angular-sanitize": "~1.5.8",
"angular-scroll-glue": "~2.0.7",
"angular-ui-router": "~0.3.1",
"angular-ui-tree": "https://github.com/FinnStutzenstein/angular-ui-tree.git#94dbaaff6b36f9d7a514843b73c28d2a3684c0c0",
"angular-xeditable": "~0.5.0",
"angularjs-slider": "~6.2.2",
"bootstrap-css-only": "~3.3.6",
"bootstrap-ui-datetime-picker": "~2.4.0",
"ckeditor": "~4.7.2",
"docxtemplater": "~2.1.5",
"font-awesome-bower": "~4.7.0",
"jquery.cookie": "~1.4.1",
"js-data": "~2.9.0",
"js-data-angular": "~3.2.1",
"jszip": "~3.1.3",
"lodash": "~4.16.0",
"ng-dialog": "~0.6.4",
"ng-file-upload": "~11.2.3",
"ngstorage": "~0.3.11",
"ngBootbox": "~0.1.3",
"papaparse": "~4.1.2",
"pdfmake": "0.1.37",
"roboto-fontface": "~0.6.0"
},
"overrides": {
"pdfmake": {
"main": []
},
"pdfjs-dist": {
"main": "build/pdf.combined.js"
},
"roboto-fontface": {
"main": [
"fonts/Roboto/Roboto-Regular.woff",
"fonts/Roboto/Roboto-Medium.woff",
"fonts/Roboto-Condensed/Roboto-Condensed-Regular.woff",
"fonts/Roboto-Condensed/Roboto-Condensed-Light.woff"
]
}
},
"resolutions": {
"angular": ">=1.5 <1.6",
"jquery": ">=3.1 <3.2",
"angular-bootstrap": "~2.1.3"
}
}

View File

@ -17,7 +17,7 @@
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/client",
"outputPath": "../openslides/static",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",

View File

@ -5307,13 +5307,15 @@
"version": "1.0.0",
"resolved": false,
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": false,
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -5330,19 +5332,22 @@
"version": "1.1.0",
"resolved": false,
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"resolved": false,
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"resolved": false,
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -5473,7 +5478,8 @@
"version": "2.0.3",
"resolved": false,
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -5487,6 +5493,7 @@
"resolved": false,
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -5503,6 +5510,7 @@
"resolved": false,
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -5511,13 +5519,15 @@
"version": "0.0.8",
"resolved": false,
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"resolved": false,
"integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==",
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -5538,6 +5548,7 @@
"resolved": false,
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -5626,7 +5637,8 @@
"version": "1.0.1",
"resolved": false,
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -5640,6 +5652,7 @@
"resolved": false,
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -5777,6 +5790,7 @@
"resolved": false,
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",

View File

@ -10,8 +10,7 @@
"e2e": "ng e2e",
"compodoc": "./node_modules/.bin/compodoc --hideGenerator -p src/tsconfig.app.json -n 'OpenSlides Documentation' -d ../Compodoc -s -w -t -o --port",
"extract": "ngx-translate-extract -i ./src -o ./src/assets/i18n/{en,de,fr}.json --clean --sort --format-indentation ' ' --format namespaced-json",
"format:fix": "pretty-quick --staged",
"precommit": "run-s format:fix lint"
"format:fix": "pretty-quick --staged"
},
"private": true,
"dependencies": {

View File

@ -1,362 +0,0 @@
/**
* Gulp tasks for development and production.
*
* Run
*
* $ ./node_modules/.bin/gulp
*
* for development and
*
* $ ./node_modules/.bin/gulp --production
*
* for production mode.
*/
var argv = require('yargs').argv,
gulp = require('gulp'),
concat = require('gulp-concat'),
cssnano = require('gulp-cssnano'),
gulpif = require('gulp-if'),
gettext = require('gulp-angular-gettext'),
inject = require('gulp-inject-string'),
jshint = require('gulp-jshint'),
mainBowerFiles = require('main-bower-files'),
path = require('path'),
rename = require('gulp-rename'),
sass = require('gulp-sass'),
sourcemaps = require('gulp-sourcemaps'),
templateCache = require('gulp-angular-templatecache'),
through = require('through2'),
uglify = require('gulp-uglify'),
vsprintf = require('sprintf-js').vsprintf;
// Directory where the results go to
var output_directory = path.join('openslides', 'static');
// Container for all watchers
var watchers = [];
/**
* Default tasks to be run before start.
*/
// Catches all JavaScript files (excluded worker files) from all core apps and concats them to one
// file js/openslides.js. In production mode the file is uglified.
var js_src = [
path.join('openslides', '*', 'static', 'js', '**', '*.js'),
'!' + path.join('openslides', 'core', 'static', 'js', 'core', 'pdf-worker.js'),
];
gulp.task('js', function () {
return gulp.src(js_src)
.pipe(sourcemaps.init())
.pipe(concat('openslides.js'))
.pipe(sourcemaps.write())
//TODO: Needs rework in all js files that uglified code works correctly.
//.pipe(gulpif(argv.production, uglify()))
.pipe(gulp.dest(path.join(output_directory, 'js')));
});
watchers.push(function () {
gulp.watch(js_src, gulp.series('js'));
});
// Catches all JavaScript files from all bower components and concats them to
// one file js/openslides-libs.js. In production mode the file is uglified.
gulp.task('js_libs', function () {
return gulp.src(mainBowerFiles({
filter: /\.js$/
}))
.pipe(sourcemaps.init())
.pipe(concat('openslides-libs.js'))
.pipe(sourcemaps.write())
.pipe(inject.prepend("/* set basepath of CKEditor */\n" +
"window.CKEDITOR_BASEPATH = '/static/ckeditor/';\n\n"))
.pipe(inject.prepend("/* Workaround for IE and pdfjs-dist#1.3.100 (see PR#3714) */\n" +
"PDFJS = {workerSrc: 'not used but set'};\n\n"))
.pipe(gulpif(argv.production, uglify()))
.pipe(gulp.dest(path.join(output_directory, 'js')));
});
// Catches all pdfmake files for pdf worker and pdfmake library.
var pdf_worker_src = path.join('openslides', 'core', 'static', 'js', 'core', 'pdf-worker.js');
gulp.task('pdf_worker', function () {
return gulp.src(pdf_worker_src)
.pipe(gulpif(argv.production, uglify()))
.pipe(gulp.dest(path.join(output_directory, 'js', 'workers')));
});
gulp.task('pdf_worker_libs', function () {
return gulp.src(path.join('bower_components', 'pdfmake', 'build', 'pdfmake.min.js'))
.pipe(gulpif(argv.production, uglify()))
.pipe(rename('pdf-worker-libs.js'))
.pipe(gulp.dest(path.join(output_directory, 'js', 'workers')));
});
watchers.push(function () {
gulp.watch(pdf_worker_src, gulp.series('pdf_worker'));
});
// Catches all template files from all core apps and concats them to one
// file js/openslides-templates.js. In production mode the file is uglified.
var templates_src = path.join('openslides', '*', 'static', 'templates', '**', '*.html');
gulp.task('templates', function () {
return gulp.src(templates_src)
.pipe(templateCache('openslides-templates.js', {
module: 'OpenSlidesApp-templates',
standalone: true,
moduleSystem: 'IIFE',
transformUrl: function (url) {
var pathList = url.split(path.sep);
pathList.shift();
return pathList.join(path.sep);
},
}))
.pipe(gulpif(argv.production, uglify()))
.pipe(gulp.dest(path.join(output_directory, 'js')));
});
watchers.push(function () {
gulp.watch(templates_src, gulp.series('templates'));
});
// Build the openslides-site.css file from the main file core/static/css/site.scss.
// Minimizes the outputfile if the production flag is given.
gulp.task('css_site', function () {
return gulp.src(path.join('openslides', 'core', 'static', 'css', 'site.scss'))
.pipe(sass().on('error', sass.logError))
.pipe(gulpif(argv.production, cssnano({safe: true})))
.pipe(rename('openslides-site.css'))
.pipe(gulp.dest(path.join(output_directory, 'css')));
});
// Build the openslides-projector.css file from the main file core/static/css/projector.scss.
// Minimizes the outputfile if the production flag is given.
gulp.task('css_projector', function () {
return gulp.src(path.join('openslides', 'core', 'static', 'css', 'projector.scss'))
.pipe(sass().on('error', sass.logError))
.pipe(gulpif(argv.production, cssnano({safe: true})))
.pipe(rename('openslides-projector.css'))
.pipe(gulp.dest(path.join(output_directory, 'css')));
});
// Watcher for scss files.
// We cannot differentiate between all scss files which belong to each realm. So if
// one scss file changes the site and projector css is rebuild.
watchers.push(function () {
gulp.watch(path.join('openslides', '*', 'static', 'css', '**', '*.scss'), gulp.parallel('css_site', 'css_projector'));
});
// Catches all CSS files from all bower components and concats them to one file
// css/openslides-libs.css. In production mode the file is uglified.
gulp.task('css_libs', function () {
return gulp.src(mainBowerFiles({
filter: /\.css$/
}))
.pipe(concat('openslides-libs.css'))
.pipe(gulpif(argv.production, cssnano({safe: true})))
.pipe(gulp.dest(path.join(output_directory, 'css')));
});
// Catches all font files from all bower components.
gulp.task('fonts_libs', function () {
return gulp.src(mainBowerFiles({
filter: /\.(woff)|(woff2)$/
}))
.pipe(gulp.dest(path.join(output_directory, 'fonts')));
});
// Catches image files for angular-chosen.
gulp.task('angular_chosen_img', function () {
return gulp.src(path.join('bower_components', 'chosen', '*.png'))
.pipe(gulp.dest(path.join(output_directory, 'css')));
});
// Tasks for CKEditor
gulp.task('ckeditor_defaults', function () {
return gulp.src([
path.join('bower_components', 'ckeditor', 'styles.js'),
path.join('bower_components', 'ckeditor', 'contents.css'),
])
.pipe(gulp.dest(path.join(output_directory, 'ckeditor')));
});
gulp.task('ckeditor_skins', function () {
return gulp.src(
[
path.join('bower_components', 'ckeditor', 'skins', 'moono-lisa', '**', '*'),
],
{
base: path.join('bower_components', 'ckeditor', 'skins')
}
)
.pipe(gulp.dest(path.join(output_directory, 'ckeditor', 'skins')));
});
gulp.task('ckeditor_plugins', function () {
return gulp.src(
[
path.join('bower_components', 'ckeditor', 'plugins', 'clipboard', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'colorbutton', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'colordialog', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'dialog', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'find', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'image', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'justify', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'link', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'liststyle', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'magicline', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'pastefromword', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'panelbutton', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'showblocks', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'specialchar', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'sourcedialog', '**', '*'),
path.join('bower_components', 'ckeditor', 'plugins', 'table', '**', '*'),
],
{
base: path.join('bower_components', 'ckeditor', 'plugins')
}
)
.pipe(gulp.dest(path.join(output_directory, 'ckeditor', 'plugins')));
});
gulp.task('ckeditor_lang', function () {
return gulp.src([
path.join('bower_components', 'ckeditor', 'lang', 'en.js'),
path.join('bower_components', 'ckeditor', 'lang', 'de.js'),
path.join('bower_components', 'ckeditor', 'lang', 'pt.js'),
path.join('bower_components', 'ckeditor', 'lang', 'es.js'),
path.join('bower_components', 'ckeditor', 'lang', 'fr.js'),
path.join('bower_components', 'ckeditor', 'lang', 'cs.js'),
path.join('bower_components', 'ckeditor', 'lang', 'ru.js'),
])
.pipe(gulp.dest(path.join(output_directory, 'ckeditor', 'lang')));
});
gulp.task('ckeditor', gulp.parallel('ckeditor_defaults', 'ckeditor_skins', 'ckeditor_plugins', 'ckeditor_lang'));
// Compiles translation files (*.po) to *.json and saves them in the directory
// openslides/static/i18n/.
gulp.task('translations', function () {
return gulp.src(path.join('openslides', 'locale', 'angular-gettext', '*.po'))
.pipe(gettext.compile({
format: 'json'
}))
.pipe(gulp.dest(path.join(output_directory, 'i18n')));
});
// Gulp default task. Runs all other tasks before.
gulp.task('default', gulp.parallel(
'js',
'js_libs',
'pdf_worker',
'pdf_worker_libs',
'templates',
'css_site',
'css_projector',
'css_libs',
'fonts_libs',
'angular_chosen_img',
'ckeditor',
'translations'
));
/**
* Extra tasks that have to be called manually. Useful for development.
*/
// Watches changes in JavaScript and templates.
gulp.task('watching', function () {
// This tasks never completes because it starts all watchers and let them
// watch forever ...
for (var i = 0; i < watchers.length; i++) {
watchers[i]();
}
});
gulp.task('watch', gulp.series(gulp.parallel('js', 'pdf_worker', 'templates', 'css_site', 'css_projector'), 'watching'));
// Extracts translatable strings using angular-gettext and saves them in file
// openslides/locale/angular-gettext/template-en.pot.
gulp.task('pot', function () {
return gulp.src([
templates_src,
path.join('openslides', '*', 'static', 'js', '**', '*.js'),
])
.pipe(gettext.extract('template-en.pot', {}))
.pipe(gulp.dest('openslides/locale/angular-gettext/'));
});
// Checks JavaScript using JSHint
gulp.task('jshint', function () {
return gulp.src([
'gulpfile.js',
path.join('openslides', '*', 'static', 'js', '**', '*.js'),
])
.pipe(jshint())
.pipe(jshint.reporter('default'))
.pipe(jshint.reporter('fail'));
});
// Extracts names, URLs and licensed of all uses bower components and prints
// it to the console. This is useful to update the README.rst during release
// process.
gulp.task('bower-components-for-readme', function () {
var files = [];
return gulp.src([
path.join('bower_components', '*', 'bower.json'),
path.join('bower_components', '*', 'package.json'),
path.join('bower_components', '*', 'component.json'),
])
.pipe(
through.obj(
function (chunk, encoding, callback) {
files.push(chunk);
callback();
},
function (callback) {
// Extract JSON from bower.json or components.json file.
var extracted = [];
for (var index = 0; index < files.length; index++) {
extracted.push(JSON.parse(files[index].contents.toString()));
}
// Sort files.
extracted.sort(function (a, b) {
return a.name < b.name ? -1 : 1;
});
// Print out line for README.rst.
for (var index2 = 0; index2 < extracted.length; index2++) {
var data = [
extracted[index2].name,
extracted[index2].homepage,
extracted[index2].license,
];
console.log(vsprintf(' * `%s <%s>`_, License: %s', data));
}
// End stream without further file processing.
callback();
}
)
);
});

View File

@ -1,21 +0,0 @@
/* List of speakers */
.lastSpeakers {
color: #9a9898;
margin-left: 27px;
}
.currentSpeaker {
font-weight: bold;
i.fa-microphone {
padding-right: 5px;
}
}
.nextSpeakers {
margin-left: 13px;
li {
line-height: 150%;
}
}

View File

@ -1,22 +0,0 @@
@import 'list_of_speakers';
/* Agenda list */
.agendalist-table {
td {
vertical-align: top;
padding-left: 15px;
}
td.number {
padding-left: 0;
white-space: nowrap;
}
p.mainitem {
font-size: 140%;
}
p.subitem {
font-size: 100%;
}
}

View File

@ -1,37 +0,0 @@
@import 'list_of_speakers';
#agenda-table {
.icon-column {
padding: 3px;
width: 5%;
}
.title-column {
padding-left: 3px;
padding-right: 10px;
width: calc(95% - 15px );
}
.caret-spacer {
width: 15px;
padding: 3px;
color: #337ab7;
font-size: 115%;
}
}
.agenda-sort {
.internal {
padding: 7px;
opacity: 0.6;
}
.angular-ui-tree-node {
min-height: 0;
}
}
div.speakers-toolbar {
margin: -20px -20px 30px -20px;
padding: 12px 15px 10px 15px;
}

View File

@ -1,441 +0,0 @@
(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', [
'$http',
'DS',
'Speaker',
'jsDataModel',
'Projector',
'ProjectHelper',
'gettextCatalog',
'gettext',
'CamelCase',
'EditForm',
function($http, DS, Speaker, jsDataModel, Projector, ProjectHelper, gettextCatalog,
gettext, CamelCase, EditForm) {
var name = 'agenda/item';
return DS.defineResource({
name: name,
useClass: jsDataModel,
verboseName: gettext('Agenda'),
computed: {
is_public: function () {
return !this.is_internal && !this.is_hidden;
},
},
methods: {
getResourceName: function () {
return name;
},
getContentObject: function () {
return DS.get(this.content_object.collection, this.content_object.id);
},
getContentObjectDetailState: function () {
return CamelCase(this.content_object.collection).replace('/', '.') +
'.detail({id: ' + this.content_object.id + '})';
},
getContentObjectForm: function () {
return EditForm.fromCollectionString(this.content_object.collection);
},
getContentResource: function () {
return DS.definitions[this.content_object.collection];
},
getTitle: function () {
var title;
try {
title = this.getContentObject().getAgendaTitle();
} catch (e) {
// when the content object is not in the DS store.
title = this.title;
}
if (this.item_number) {
title = this.item_number + ' · ' + title;
}
return title;
},
getListOfSpeakersTitle: function () {
var title;
try {
title = this.getContentObject().getListOfSpeakersTitle();
if (this.item_number) {
title = this.item_number + ' · ' + title;
}
} catch (e) {
title = this.getTitle();
}
return title;
},
getAgendaTitle: function () {
return this.title;
},
getCSVExportText: function () {
var text;
try {
text = this.getContentObject().getCSVExportText();
} catch (e) {
// when the content object is not in the DS store
// or 'getCSVExportText' is not defined return nothing.
}
return text;
},
// link name which is shown in search result
getSearchResultName: function () {
return this.getAgendaTitle();
},
// return true if a specific relation matches for given searchquery
// (here: speakers)
hasSearchResult: function (results) {
var item = this;
// search for speakers (check if any user.id from already found users matches)
return _.some(results, function(result) {
if (result.getResourceName() === "users/user") {
if (_.some(item.speakers, {'user_id': result.id})) {
return true;
}
}
});
},
getProjectorTitle: function () {
try {
return this.getContentObject().getAgendaListViewTitle();
} catch (e) {
// when the content object is not in the DS store
return this.list_view_title;
}
},
getListViewTitle: function () {
var title = this.getProjectorTitle();
if (this.item_number) {
title = this.item_number + ' · ' + title;
}
return title;
},
getItemNumberWithAncestors: function (agendaId) {
if (!agendaId) {
agendaId = this.id;
}
var agendaItem = DS.get(name, agendaId);
if (!agendaItem) {
return '';
} else if (agendaItem.item_number) {
return agendaItem.item_number;
} else if (agendaItem.parent_id) {
return this.getItemNumberWithAncestors(agendaItem.parent_id);
} else {
return '';
}
},
// override project function of jsDataModel factory
project: function (projectorId, tree) {
if (tree) {
var isProjectedIds = this.isProjected(tree);
var requestData = {
clear_ids: isProjectedIds,
};
// Activate, if the projector_id is a new projector.
if (_.indexOf(isProjectedIds, projectorId) == -1) {
requestData.prune = {
id: projectorId,
element: {
name: 'agenda/item-list',
tree: true,
id: this.id,
},
};
}
return ProjectHelper.project(requestData);
} else { // Project the content object
var contentObject = DS.get(this.content_object.collection, this.content_object.id);
return contentObject.project(projectorId);
}
},
// override isProjected function of jsDataModel factory
isProjected: function (tree) {
// Returns all ids of all projectors with an agenda-item element. Otherwise an empty list.
if (typeof tree === 'undefined') {
tree = false;
}
var self = this;
var predicate = function (element) {
var value;
if (tree) {
// Item tree slide for sub tree
value = element.name == 'agenda/item-list' &&
typeof element.id !== 'undefined' &&
element.id == self.id;
} else {
// Releated item detail slide
value = element.name == self.content_object.collection &&
typeof element.id !== 'undefined' &&
element.id == self.content_object.id;
}
return value;
};
var isProjectedIds = [];
Projector.getAll().forEach(function (projector) {
if (typeof _.findKey(projector.elements, predicate) === 'string') {
isProjectedIds.push(projector.id);
}
});
return isProjectedIds;
},
isRelatedProjected: function () {
// related objects for agenda items: list of speakers slide.
return this.isListOfSpeakersProjected();
},
// project list of speakers
projectListOfSpeakers: function(projectorId) {
var isProjectedIds = this.isListOfSpeakersProjected();
var requestData = {
clear_ids: isProjectedIds,
};
if (_.indexOf(isProjectedIds, projectorId) == -1) {
requestData.prune = {
id: projectorId,
element: {
name: 'agenda/list-of-speakers',
id: this.id,
},
};
}
return ProjectHelper.project(requestData);
},
// check if list of speakers is projected
isListOfSpeakersProjected: function () {
// Returns all ids of all projectors with an element with the
// name 'agenda/list-of-speakers' and the same id. Else returns an empty list.
var self = this;
var predicate = function (element) {
return element.name == 'agenda/list-of-speakers' &&
typeof element.id !== 'undefined' &&
element.id == self.id;
};
var isProjecteds = [];
Projector.getAll().forEach(function (projector) {
if (typeof _.findKey(projector.elements, predicate) === 'string') {
isProjecteds.push(projector.id);
}
});
return isProjecteds;
},
hasSubitems: function(items) {
var self = this;
var hasChild = false;
// Returns true if the item has at least one child item.
_.each(items, function (item) {
if (item.parent_id == self.id) {
hasChild = true;
}
});
return hasChild;
}
},
relations: {
hasMany: {
'core/tag': {
localField: 'tags',
localKeys: 'tags_id',
},
'agenda/speaker': {
localField: 'speakers',
foreignKey: 'item_id',
}
}
},
beforeInject: function (resource, instance) {
Speaker.ejectAll({where: {item_id: {'==': instance.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
getFlatTree: function(items) {
var tree = this.getTree(items);
var flatItems = [];
function generateFlatTree(tree, parentCount) {
_.each(tree, function (item) {
item.item.childrenCount = item.children.length;
item.item.parentCount = parentCount;
flatItems.push(item.item);
generateFlatTree(item.children, parentCount + 1);
});
}
generateFlatTree(tree, 0);
return flatItems;
}
};
}
])
.factory('CurrentListOfSpeakersItem', [
'Projector',
'Agenda',
function (Projector, Agenda) {
return {
getItem: function (projectorId) {
var projector = Projector.get(projectorId), item;
if (projector) {
_.forEach(projector.elements, function(element) {
if (element.agenda_item_id) {
item = Agenda.get(element.agenda_item_id);
}
});
}
return item;
}
};
}
])
.factory('CurrentListOfSpeakersSlide', [
'$http',
'Projector',
function($http, Projector) {
var name = 'agenda/current-list-of-speakers';
return {
project: function (projectorId, overlay) {
var isProjected = this.isProjectedWithOverlayStatus();
_.forEach(isProjected, function (mapping) {
$http.post('/rest/core/projector/' + mapping.projectorId + '/deactivate_elements/',
[mapping.uuid]
);
});
// The slide was projected, if the id matches. If the overlay is given, also
// the overlay is checked
var wasProjectedBefore = _.some(isProjected, function (mapping) {
var value = (mapping.projectorId === projectorId);
if (overlay !== undefined) {
value = value && (mapping.overlay === overlay);
}
return value;
});
overlay = overlay || false; // set overlay if it wasn't defined
if (!wasProjectedBefore) {
var activate = function () {
return $http.post(
'/rest/core/projector/' + projectorId + '/activate_elements/',
[{name: name,
stable: overlay, // if this is an overlay, it should not be
// removed by changing the main content
overlay: overlay}]
);
};
if (!overlay) {
// clear all elements on this projector, because we are _not_ using the overlay.
$http.post('/rest/core/projector/' + projectorId + '/clear_elements/').then(activate);
} else {
activate();
}
}
},
isProjected: function () {
// Returns the ids of all projectors with an agenda-item element. Else return an empty list.
var predicate = function (element) {
return element.name === name;
};
var isProjectedIds = [];
Projector.getAll().forEach(function (projector) {
if (typeof _.findKey(projector.elements, predicate) === 'string') {
isProjectedIds.push(projector.id);
}
});
return isProjectedIds;
},
// Returns a list of mappings between pojector id, overlay and uuid.
isProjectedWithOverlayStatus: function () {
var mapping = [];
_.forEach(Projector.getAll(), function (projector) {
_.forEach(projector.elements, function (element, uuid) {
if (element.name === name) {
mapping.push({
projectorId: projector.id,
uuid: uuid,
overlay: element.overlay || false,
});
}
});
});
return mapping;
},
};
}
])
// Make sure that the Agenda resource is loaded.
.run(['Agenda', function(Agenda) {}]);
}());

View File

@ -1,41 +0,0 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.agenda.csv', [])
.factory('AgendaCsvExport', [
'HumanTimeConverter',
'gettextCatalog',
'CsvDownload',
function (HumanTimeConverter, gettextCatalog, CsvDownload) {
var makeHeaderline = function () {
var headerline = ['Title', 'Text', 'Duration', 'Comment', 'Internal item'];
return _.map(headerline, function (entry) {
return gettextCatalog.getString(entry);
});
};
return {
export: function (agenda) {
var csvRows = [
makeHeaderline()
];
_.forEach(agenda, function (item) {
var row = [];
var duration = item.duration ? HumanTimeConverter.secondsToHumanTime(item.duration*60,
{ seconds: 'disabled',
hours: 'enabled' }) : '';
row.push('"' + (item.title || '') + '"');
row.push('"' + (item.getCSVExportText() || '') + '"');
row.push('"' + duration + '"');
row.push('"' + (item.comment || '') + '"');
row.push('"' + (item.is_hidden ? '1' : '') + '"');
csvRows.push(row);
});
CsvDownload(csvRows, gettextCatalog.getString('Agenda') + '.csv');
},
};
}
]);
}());

View File

@ -1,84 +0,0 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.agenda.docx', ['OpenSlidesApp.core.docx'])
.factory('AgendaDocxExport', [
'$http',
'gettextCatalog',
'FileSaver',
'Agenda',
'AgendaTree',
'Config',
function ($http, gettextCatalog, FileSaver, Agenda, AgendaTree, Config) {
var getData = function (items) {
// Item structure: The top layer has subitems, that are flat.
// The first layer is bold and all sublayers not. The docx
// templater cannot render items recursively, so the second
// layer are all subitems flated out. Spacing is done with tabs.
var tree = AgendaTree.getTree(items);
var subitems = []; // This will be used as a temporary variable.
var flatSubitems = function (children, parentCount) {
_.forEach(children, function (child) {
var taps = _.repeat('\t', parentCount - 1);
subitems.push({
item_number: taps + child.item.item_number,
item_title: child.item.list_view_title,
});
flatSubitems(child.children, parentCount + 1);
});
};
var twoLayerTree = _.map(tree, function (mainItem) {
subitems = [];
flatSubitems(mainItem.children, 1);
return {
item_number: mainItem.item.item_number,
item_title: mainItem.item.list_view_title,
subitems: subitems,
};
});
// header
var headerline1 = [
Config.translate(Config.get('general_event_name').value),
Config.translate(Config.get('general_event_description').value)
].filter(Boolean).join(' ');
var headerline2 = [
Config.get('general_event_location').value,
Config.get('general_event_date').value
].filter(Boolean).join(', ');
// Data structure for the docx templater.
return {
header: [headerline1, headerline2].join('\n'),
agenda_translation: gettextCatalog.getString('Agenda'),
top_list: twoLayerTree,
};
};
return {
export: function (items) {
// TODO: use filtered items.
var filename = gettextCatalog.getString('Agenda') + '.docx';
$http.get('/agenda/docxtemplate/').then(function (success) {
var content = window.atob(success.data);
var doc = new Docxgen(content);
var data = getData(items);
doc.setData(data);
doc.render();
var zip = doc.getZip();
//zip = converter.updateZipFile(zip);
var out = zip.generate({type: 'blob'});
FileSaver.saveAs(out, filename);
});
},
};
}
]);
})();

View File

@ -1,93 +0,0 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.agenda.pdf', ['OpenSlidesApp.core.pdf'])
.factory('AgendaContentProvider', [
'gettextCatalog',
'PDFLayout',
function(gettextCatalog, PDFLayout) {
var createInstance = function(items) {
// page title
var title = PDFLayout.createTitle(gettextCatalog.getString("Agenda"));
// generate the item list with all subitems
var createItemList = function() {
var agenda_items = [];
_.forEach(items, function (item) {
if (item.is_public) {
var itemIndent = item.parentCount * 15;
var itemStyle;
if (item.parentCount === 0) {
itemStyle = 'listParent';
} else {
itemStyle = 'listChild';
}
var agendaJsonString = {
style: itemStyle,
columns: [
{
width: itemIndent,
text: ''
},
{
width: 60,
text: item.item_number
},
{
text: item.title
}
]
};
agenda_items.push(agendaJsonString);
}
});
return agenda_items;
};
var getContent = function() {
return [
title,
createItemList()
];
};
return {
getContent: getContent
};
};
return {
createInstance: createInstance
};
}])
.factory('AgendaPdfExport', [
'gettextCatalog',
'AgendaContentProvider',
'PdfMakeDocumentProvider',
'PdfCreate',
'Messaging',
function (gettextCatalog, AgendaContentProvider, PdfMakeDocumentProvider, PdfCreate, Messaging) {
return {
export: function (items) {
var filename = gettextCatalog.getString('Agenda') + '.pdf';
var agendaContentProvider = AgendaContentProvider.createInstance(items);
PdfMakeDocumentProvider.createInstance(agendaContentProvider).then(function (documentProvider) {
PdfCreate.download(documentProvider, filename);
}, function (error) {
Messaging.addMessage(error.msg, 'error');
});
},
};
}
]);
}());

View File

@ -1,137 +0,0 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
.config([
'slidesProvider',
function(slidesProvider) {
slidesProvider.registerSlide('agenda/list-of-speakers', {
template: 'static/templates/agenda/slide-list-of-speakers.html',
});
slidesProvider.registerSlide('agenda/item-list', {
template: 'static/templates/agenda/slide-item-list.html',
});
slidesProvider.registerSlide('agenda/current-list-of-speakers', {
template: 'static/templates/agenda/slide-current-list-of-speakers.html',
});
}
])
.controller('SlideCurrentListOfSpeakersCtrl', [
'$scope',
'Agenda',
'CurrentListOfSpeakersItem',
'Config',
'Projector',
function ($scope, Agenda, CurrentListOfSpeakersItem, Config, Projector) {
$scope.overlay = $scope.element.overlay;
// Watch for changes in the current list of speakers reference
$scope.$watch(function () {
return Config.lastModified('projector_currentListOfSpeakers_reference');
}, function () {
$scope.currentListOfSpeakersReference = $scope.config('projector_currentListOfSpeakers_reference');
$scope.updateCurrentListOfSpeakers();
});
// Watch for changes in the referenced projector
$scope.$watch(function () {
return Projector.lastModified($scope.currentListOfSpeakersReference);
}, function () {
$scope.updateCurrentListOfSpeakers();
});
// Watch for changes in the current item.
$scope.$watch(function () {
return Agenda.lastModified();
}, function () {
$scope.updateCurrentListOfSpeakers();
});
$scope.updateCurrentListOfSpeakers = function () {
$scope.agendaItem = CurrentListOfSpeakersItem.getItem($scope.currentListOfSpeakersReference);
};
}
])
.controller('SlideListOfSpeakersCtrl', [
'$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.bindOne(id, $scope, 'item');
}
])
.controller('SlideItemListCtrl', [
'$scope',
'$http',
'$filter',
'Agenda',
'AgendaTree',
'Config',
function ($scope, $http, $filter, Agenda, AgendaTree, Config) {
// 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.
// Bind agenda tree to the scope
var items;
$scope.$watch(function () {
return Agenda.lastModified() +
Config.lastModified('agenda_hide_internal_items_on_projector');
}, function () {
if ($scope.element.id) {
// remove hidden items
items = _.filter(Agenda.getAll(), function (item) {
return !item.is_hidden;
});
if (Config.get('agenda_hide_internal_items_on_projector').value) {
items = _.filter(items, function (item) {
return item.is_public;
});
}
var tree = AgendaTree.getTree(items);
var getRootNode = function (node) {
if (node.id == $scope.element.id) {
return node;
}
for (var i = 0; i < node.children.length; i++) {
var result = getRootNode(node.children[i]);
if (result) {
return result;
}
}
return false;
};
_.forEach(tree, function (node) {
var result = getRootNode(node);
if (result) {
$scope.rootItem = result.item;
$scope.tree = result.children;
return false;
}
});
} else if ($scope.element.tree) {
items = _.filter(Agenda.getAll(), function (item) {
return item.is_public;
});
$scope.tree = AgendaTree.getTree(items);
} else {
items = Agenda.filter({
where: { parent_id: null },
orderBy: 'weight'
});
items = _.filter(items, function (item) {
return item.is_public;
});
$scope.tree = AgendaTree.getTree(items);
}
});
}
]);
}());

View File

@ -1,890 +0,0 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.agenda.site', [
'OpenSlidesApp.agenda',
'OpenSlidesApp.core.pdf',
'OpenSlidesApp.agenda.pdf',
'OpenSlidesApp.agenda.csv',
'OpenSlidesApp.agenda.docx',
])
.config([
'mainMenuProvider',
'gettext',
function (mainMenuProvider, gettext) {
mainMenuProvider.register({
'ui_sref': 'agenda.item.list',
'img_class': 'calendar-o',
'title': gettext('Agenda'),
'weight': 200,
'perm': 'agenda.can_see',
});
}
])
.config([
'SearchProvider',
'gettext',
function (SearchProvider, gettext) {
SearchProvider.register({
'verboseName': gettext('Agenda'),
'collectionName': 'agenda/item',
'urlDetailState': 'agenda.item.detail',
'weight': 200,
});
}
])
.config([
'$stateProvider',
'gettext',
function ($stateProvider, gettext) {
$stateProvider
.state('agenda', {
url: '/agenda',
abstract: true,
template: "<ui-view/>",
data: {
title: gettext('Agenda'),
basePerm: 'agenda.can_see',
},
})
.state('agenda.item', {
abstract: true,
template: "<ui-view/>",
})
.state('agenda.item.list', {})
.state('agenda.item.detail', {
resolve: {
itemId: ['$stateParams', function($stateParams) {
return $stateParams.id;
}],
}
})
.state('agenda.item.sort', {
url: '/sort',
controller: 'AgendaSortCtrl',
})
.state('agenda.current-list-of-speakers', {
url: '/speakers',
controller: 'CurrentListOfSpeakersViewCtrl',
data: {
title: gettext('Current list of speakers'),
},
});
}
])
// Set the sensitivity of moving nodes horizontal for the ui-tree.
.config([
'treeConfig',
function (treeConfig) {
treeConfig.dragMoveSensitivity = 20;
}
])
.factory('ShowAsAgendaItemField', [
'operator',
'gettext',
'gettextCatalog',
function (operator, gettext, gettextCatalog) {
return function (managePermission) {
return {
key: 'agenda_type',
type: 'select-single',
templateOptions: {
label: gettextCatalog.getString('Agenda visibility'),
options: [
{type: 1, displayName: gettext('Public item')},
{type: 2, displayName: gettext('Internal item')},
{type: 3, displayName: gettext('Hidden item')}
],
ngOptions: 'type.type as (type.displayName | translate) for type in to.options',
},
hide: !(operator.hasPerms(managePermission) && operator.hasPerms('agenda.can_manage'))
};
};
}
])
.controller('ItemListCtrl', [
'$scope',
'$filter',
'$http',
'$state',
'DS',
'operator',
'ngDialog',
'Agenda',
'TopicForm', // TODO: Remove this dependency. Use template hook for "New" and "Import" buttons.
'AgendaTree',
'Projector',
'ProjectionDefault',
'gettextCatalog',
'gettext',
'osTableFilter',
'osTablePagination',
'AgendaCsvExport',
'AgendaPdfExport',
'AgendaDocxExport',
'ErrorMessage',
function($scope, $filter, $http, $state, DS, operator, ngDialog, Agenda, TopicForm,
AgendaTree, Projector, ProjectionDefault, gettextCatalog, gettext, osTableFilter,
osTablePagination, AgendaCsvExport, AgendaPdfExport, AgendaDocxExport, ErrorMessage) {
$scope.AGENDA_ITEM = 1;
$scope.INTERNAL_ITEM = 2;
$scope.HIDDEN_ITEM = 3;
// Bind agenda tree to the scope
$scope.$watch(function () {
return Agenda.lastModified();
}, function () {
// Filter out items that doesn't have the list_item_title. This happens, if the
// item is a hidden item but provides the list of speakers, but should not be
// visible in the list view.
var allowedItems = _.filter(Agenda.getAll(), function (item) {
return item.list_view_title;
});
$scope.items = AgendaTree.getFlatTree(allowedItems);
$scope.agendaHasSubitems = $filter('filter')($scope.items, {'parent_id': ''}).length;
});
Projector.bindAll({}, $scope, 'projectors');
$scope.mainListTree = true;
$scope.$watch(function () {
return Projector.lastModified();
}, function () {
var projectiondefault = ProjectionDefault.filter({name: 'agenda_all_items'})[0];
if (projectiondefault) {
$scope.defaultProjectorId_all_items = projectiondefault.projector_id;
}
$scope.projectionDefaults = ProjectionDefault.getAll();
});
$scope.alert = {};
// Filtering
$scope.filter = osTableFilter.createInstance('AgendaTableFilter');
if (!$scope.filter.existsStorageEntry()) {
$scope.filter.booleanFilters = {
closed: {
value: undefined,
defaultValue: undefined,
displayName: gettext('Closed items'),
choiceYes: gettext('Closed items'),
choiceNo: gettext('Open items'),
},
// The next filters are just on-off, so no undefined there
is_public: {
value: true,
defaultValue: true,
choiceYes: gettext('Public items'),
choiceNo: gettext('No public items'),
},
is_internal: {
value: true,
defaultValue: true,
choiceYes: gettext('Internal items'),
choiceNo: gettext('No internal items'),
permission: 'agenda.can_see_internal_items',
},
is_hidden: {
value: false,
defaultValue: false,
choiceYes: gettext('Hidden items'),
choiceNo: gettext('No hidden items'),
permission: 'agenda.can_manage',
},
};
}
$scope.filter.propertyList = ['item_number', 'title', 'title_list_view', 'comment', 'duration'];
$scope.filter.propertyFunctionList = [
function (item) {return item.getListViewTitle();},
];
$scope.areFiltersSet = function () {
return ($scope.areVisibilityFiltersSet() ||
$scope.filter.booleanFilters.closed.value !== $scope.filter.booleanFilters.closed.defaultValue);
};
$scope.areVisibilityFiltersSet = function () {
return ($scope.filter.booleanFilters.is_public.value !== $scope.filter.booleanFilters.is_public.defaultValue ||
$scope.filter.booleanFilters.is_internal.value !== $scope.filter.booleanFilters.is_internal.defaultValue ||
$scope.filter.booleanFilters.is_hidden.value !== $scope.filter.booleanFilters.is_hidden.defaultValue);
};
$scope.resetFilters = function (isSelectMode) {
if (!isSelectMode) {
_.forEach($scope.filter.booleanFilters, function (filter) {
filter.value = filter.defaultValue;
});
$scope.filter.save();
}
};
// Expand all items during searching.
$scope.filter.changed = function () {
$scope.collapseState = true;
$scope.toggleCollapseState();
};
// pagination
$scope.pagination = osTablePagination.createInstance('AgendaTablePagination', 50);
// parse duration for inline editing
$scope.generateDurationText = function (item) {
//convert data from model format (m) to view format (hh:mm)
if (item.duration) {
var time = "",
totalminutes = item.duration;
if (totalminutes < 0) {
time = "-";
totalminutes = -totalminutes;
}
var hh = Math.floor(totalminutes / 60);
var mm = Math.floor(totalminutes % 60);
// Add leading "0" for double digit values
mm = ("0"+mm).slice(-2);
time += hh + ":" + mm;
item.durationText = time;
} else {
item.durationText = "";
}
};
$scope.setDurationText = function (item) {
//convert data from view format (hh:mm) to model format (m)
var time = item.durationText.replace('h', '').split(':');
var data;
if (time.length > 1 && !isNaN(time[0]) && !isNaN(time[1])) {
data = (+time[0]) * 60 + (+time[1]);
if (data < 0) {
data = "-"+data;
}
item.duration = parseInt(data);
} else if (time.length == 1 && !isNaN(time[0])) {
data = (+time[0]);
item.duration = parseInt(data);
} else {
item.duration = 0;
}
$scope.save(item);
};
/** Duration calculations **/
$scope.sumDurations = function () {
var totalDuration = 0;
$scope.items.forEach(function (item) {
if (item.duration) {
totalDuration += item.duration;
}
});
return totalDuration;
};
$scope.calculateEndTime = function () {
var totalDuration = $scope.sumDurations();
var startTimestamp = $scope.config('agenda_start_event_date_time');
if (startTimestamp) {
var endTimestamp = startTimestamp + totalDuration * 60 * 1000;
var endDate = new Date(endTimestamp);
var mm = ("0" + endDate.getMinutes()).slice(-2);
var dateStr = endDate.getHours() + ':' + mm;
return dateStr;
} else {
return '';
}
};
// Agenda collapse function
$scope.toggleCollapseState = function () {
$scope.collapseState = !$scope.collapseState;
_.forEach($scope.items, function (item) {
item.hideChildren = $scope.collapseState;
});
};
// Check, if an item has childs in all filtered items
$scope.hasChildren = function (item) {
return _.some($scope.itemsFiltered, function (_item) {
return _item.parent_id == item.id;
});
};
// returns true, if the agenda has at least two layers
$scope.agendaHasMultipleLayers = function () {
return _.some($scope.items, function (item) {
return item.parent_id;
});
};
/** Agenda item functions **/
// open dialog for new topics // TODO Remove this. Don't forget import button in template.
$scope.newDialog = function () {
ngDialog.open(TopicForm.getDialog());
};
// save changed item
$scope.save = function (item) {
Agenda.save(item).then(
function (success) {
$scope.alert.show = false;
},
function (error) {
$scope.alert = ErrorMessage.forAlert(error);
});
};
// delete related item
$scope.deleteRelatedItem = function (item) {
DS.destroy(item.content_object.collection, item.content_object.id);
};
// auto numbering of agenda items
$scope.autoNumbering = function() {
$http.post('/rest/agenda/item/numbering/', {});
};
// check open permission
// TODO: Use generic solution here.
$scope.isAllowedToSeeOpenLink = function (item) {
var collection = item.content_object.collection;
switch (collection) {
case 'topics/topic':
return operator.hasPerms('agenda.can_see');
case 'motions/motion':
return operator.hasPerms('motions.can_see');
case 'motions/motion-block':
return operator.hasPerms('motions.can_see');
case 'assignments/assignment':
return operator.hasPerms('assignments.can_see');
default:
return false;
}
};
$scope.edit = function (item) {
ngDialog.open(item.getContentObjectForm().getDialog({id: item.content_object.id}));
};
// export
$scope.pdfExport = function () {
AgendaPdfExport.export($scope.itemsFiltered);
};
$scope.csvExport = function () {
AgendaCsvExport.export($scope.itemsFiltered);
};
$scope.docxExport = function () {
AgendaDocxExport.export($scope.itemsFiltered);
};
/** select mode functions **/
$scope.isSelectMode = false;
// check all checkboxes
$scope.checkAll = function () {
$scope.selectedAll = !$scope.selectedAll;
angular.forEach($scope.items, function (item) {
item.selected = $scope.selectedAll;
});
};
// uncheck all checkboxes if isDeleteMode is closed
$scope.uncheckAll = function () {
if (!$scope.isSelectMode) {
$scope.selectedAll = false;
angular.forEach($scope.items, function (item) {
item.selected = false;
});
}
};
// set type for selected items
$scope.setTypeMultiple = function (type) {
_.forEach($scope.items, function (item) {
if (item.selected) {
item.type = type;
$scope.save(item);
}
});
$scope.isSelectMode = false;
$scope.uncheckAll();
};
// set closed for selected items
$scope.setStateMultiple = function (closed) {
_.forEach($scope.items, function (item) {
if (item.selected) {
item.closed = closed;
$scope.save(item);
}
});
$scope.isSelectMode = false;
$scope.uncheckAll();
};
// delete selected items
$scope.deleteMultiple = function () {
_.forEach($scope.items, function (item) {
if (item.selected) {
DS.destroy(item.content_object.collection, item.content_object.id);
}
});
$scope.isSelectMode = false;
$scope.uncheckAll();
};
/** Project functions **/
// get ProjectionDefault for item
$scope.getProjectionDefault = function (item) {
if (item.tree) {
return $scope.defaultProjectorId_all_items;
} else {
var app_name = item.content_object.collection.split('/')[0];
var id = 1;
$scope.projectionDefaults.forEach(function (projectionDefault) {
if (projectionDefault.name == app_name) {
id = projectionDefault.projector_id;
}
});
return id;
}
};
// project agenda
$scope.projectAgenda = function (projectorId, tree, id) {
var isAgendaProjectedIds = $scope.isAgendaProjected($scope.mainListTree);
_.forEach(isAgendaProjectedIds, function (id) {
$http.post('/rest/core/projector/' + id + '/clear_elements/');
});
if (_.indexOf(isAgendaProjectedIds, projectorId) == -1) {
$http.post('/rest/core/projector/' + projectorId + '/prune_elements/',
[{name: 'agenda/item-list', tree: tree, id: id}]);
}
};
// change whether all items or only main items should be projected
$scope.changeMainListTree = function () {
var isAgendaProjectedId = $scope.isAgendaProjected($scope.mainListTree);
$scope.mainListTree = !$scope.mainListTree;
if (isAgendaProjectedId > 0) {
$scope.projectAgenda(isAgendaProjectedId, $scope.mainListTree);
}
};
// change whether one item or all subitems should be projected
$scope.changeItemTree = function (item) {
var tree = item.tree;
item.tree = !item.tree;
var isProjected = item.isProjected(tree);
_.forEach(isProjected, function (projectorId) {
// Deactivate and reactivate
item.project(projectorId, tree).then(function (s) {
item.project(projectorId, !tree);
});
});
};
// check if agenda is projected
$scope.isAgendaProjected = function (tree) {
// Returns the ids of all projectors with an element with
// the name 'agenda/item-list'. Else returns an empty list.
var predicate = function (element) {
var value;
if (tree) {
// tree with all agenda items
value = element.name == 'agenda/item-list' &&
typeof element.id === 'undefined' &&
element.tree;
} else {
// only main agenda items
value = element.name == 'agenda/item-list' &&
typeof element.id === 'undefined' &&
!element.tree;
}
return value;
};
var projectorIds = [];
$scope.projectors.forEach(function (projector) {
if (typeof _.findKey(projector.elements, predicate) === 'string') {
projectorIds.push(projector.id);
}
});
return projectorIds;
};
}
])
// Filter for the item type that filters the selected items by type. filters
// are the boolean filters from the ui.
.filter('itemTypeFilter', [
function () {
return function (items, filters) {
return _.filter(items, function (item) {
return (item.is_public && filters.is_public.value) ||
(item.is_internal && filters.is_internal.value) ||
(item.is_hidden && filters.is_hidden.value);
});
};
}
])
// filter to hide collapsed items. Items has to be a flat tree.
.filter('collapsedItemFilter', [
function () {
return function (items) {
return _.filter(items, function (item) {
var index = _.findIndex(items, item);
var parentId = item.parent_id;
// Search for parents, if one has the hideChildren attribute set. All parents
// have a higher index as this item, because items is a flat tree.
// If a parent has this attribute, we should remove this item. Else if we hit
// the top or an item on the first layer, the item is not collapsed.
for (--index; index >= 0 && parentId !== null; index--) {
var p = items[index];
if (p.id === parentId) {
if (p.hideChildren) {
return false;
} else {
parentId = p.parent_id;
}
}
}
return true;
});
};
}
])
.controller('ItemDetailCtrl', [
'$scope',
'$filter',
'Agenda',
'itemId',
'Projector',
'ProjectionDefault',
'gettextCatalog',
'WebpageTitle',
'ErrorMessage',
function ($scope, $filter, Agenda, itemId, Projector, ProjectionDefault, gettextCatalog, WebpageTitle,
ErrorMessage) {
$scope.alert = {};
$scope.$watch(function () {
return Agenda.lastModified(itemId);
}, function () {
$scope.item = Agenda.get(itemId);
WebpageTitle.updateTitle(gettextCatalog.getString('List of speakers') + ' ' +
gettextCatalog.getString('of') + ' ' + $scope.item.getTitle());
// all speakers
$scope.speakers = $filter('orderBy')($scope.item.speakers, 'weight');
// next speakers
$scope.nextSpeakers = $filter('filter')($scope.speakers, {'begin_time': null});
// current speaker
$scope.currentSpeaker = $filter('filter')($scope.speakers, {'begin_time': '!!', 'end_time': null});
// last speakers
$scope.lastSpeakers = $filter('filter')($scope.speakers, {'end_time': '!!'});
$scope.lastSpeakers = $filter('orderBy')($scope.lastSpeakers, 'begin_time');
});
$scope.$watch(function () {
return Projector.lastModified();
}, function () {
var item_app_name = $scope.item.content_object.collection.split('/')[0];
var projectiondefaultItem = ProjectionDefault.filter({name: item_app_name})[0];
if (projectiondefaultItem) {
$scope.defaultProjectorItemId = projectiondefaultItem.projector_id;
}
var projectiondefaultListOfSpeakers = ProjectionDefault.filter({name: 'agenda_list_of_speakers'})[0];
if (projectiondefaultListOfSpeakers) {
$scope.defaultProjectorListOfSpeakersId = projectiondefaultListOfSpeakers.projector_id;
}
});
}
])
/* This is the controller for the list of speakers partial management template.
* The parent controller needs to provide a $scope.item, $scope.speakers, $scope.nextSpeakers,
* $scope.currentSpeakers, $scope.lastSpeakers. See (as example) ItemDetailCtrl. */
.controller('ListOfSpeakersManagementCtrl', [
'$scope',
'$http',
'$filter',
'Agenda',
'User',
'operator',
'ErrorMessage',
function ($scope, $http, $filter, Agenda, User, operator, ErrorMessage) {
User.bindAll({}, $scope, 'users');
$scope.speakerSelectBox = {};
// close/open list of speakers of current item
$scope.closeList = function (listClosed) {
$scope.item.speaker_list_closed = listClosed;
Agenda.save($scope.item);
};
// add user to list of speakers
$scope.addSpeaker = function (userId) {
$http.post('/rest/agenda/item/' + $scope.item.id + '/manage_speaker/', {'user': userId}).then(
function (success) {
$scope.alert.show = false;
$scope.speakerSelectBox = {};
}, function (error) {
$scope.alert = ErrorMessage.forAlert(error);
$scope.speakerSelectBox = {};
}
);
};
// delete speaker(!) from list of speakers
$scope.removeSpeaker = function (speakerId) {
$http.delete(
'/rest/agenda/item/' + $scope.item.id + '/manage_speaker/',
{headers: {'Content-Type': 'application/json'},
data: JSON.stringify({speaker: speakerId})}
)
.then(function (success) {
}, function (error) {
$scope.alert = ErrorMessage.forAlert(error);
});
};
//delete all speakers from list of speakers
$scope.removeAllSpeakers = function () {
var speakersOnList = [];
angular.forEach($scope.item.speakers, function (speaker) {
speakersOnList.push(speaker.id);
});
$http.delete(
'/rest/agenda/item/' + $scope.item.id + '/manage_speaker/',
{headers: {'Content-Type': 'application/json'},
data: JSON.stringify({speaker: speakersOnList})}
)
.then(function (success) {
}, function (error) {
$scope.alert = ErrorMessage.forAlert(error);
});
};
// Return true if the requested user is allowed to do a specific action
// and see the corresponding button (e.g. 'add me' or 'remove me').
$scope.isAllowed = function (action) {
var nextUsers = [];
angular.forEach($scope.nextSpeakers, function (speaker) {
nextUsers.push(speaker.user_id);
});
switch (action) {
case 'add':
return (operator.hasPerms('agenda.can_be_speaker') &&
!$scope.item.speaker_list_closed &&
$.inArray(operator.user.id, nextUsers) == -1);
case 'remove':
if (operator.user) {
return ($.inArray(operator.user.id, nextUsers) != -1);
}
return false;
case 'removeAll':
return (operator.hasPerms('agenda.can_manage_list_of_speakers') &&
$scope.speakers.length > 0);
case 'showLastSpeakers':
return $scope.lastSpeakers.length > 0;
}
};
// begin speech of selected/next speaker
$scope.beginSpeech = function (speakerId) {
$http.put('/rest/agenda/item/' + $scope.item.id + '/speak/', {'speaker': speakerId})
.then(function (success) {
$scope.alert.show = false;
}, function (error) {
$scope.alert = ErrorMessage.forAlert(error);
});
};
// end speech of current speaker
$scope.endSpeech = function () {
$http.delete(
'/rest/agenda/item/' + $scope.item.id + '/speak/',
{headers: {'Content-Type': 'application/json'}, data: {}}
).then(
function (success) {},
function (error) {
$scope.alert = ErrorMessage.forAlert(error);
}
);
};
// gets speech duration of selected speaker in seconds
$scope.getDuration = function (speaker) {
var beginTimestamp = new Date(speaker.begin_time).getTime();
var endTimestamp = new Date(speaker.end_time).getTime();
// calculate duration in seconds
return Math.floor((endTimestamp - beginTimestamp) / 1000);
};
// save reordered list of speakers
$scope.treeOptions = {
dropped: function (event) {
var sortedSpeakers = _.map($scope.nextSpeakers, function (speaker) {
return speaker.id;
});
$http.post('/rest/agenda/item/' + $scope.item.id + '/sort_speakers/',
{speakers: sortedSpeakers}
);
}
};
// Marking a speaker
$scope.toggleMarked = function (speaker) {
$http.patch('/rest/agenda/item/' + $scope.item.id + '/manage_speaker/', {
user: speaker.user.id,
marked: !speaker.marked,
}).then(function (success) {
$scope.alert.show = false;
}, function (error) {
$scope.alert = ErrorMessage.forAlert(error);
});
};
}
])
.controller('AgendaSortCtrl', [
'$scope',
'$http',
'Agenda',
'AgendaTree',
'ErrorMessage',
function($scope, $http, Agenda, AgendaTree, ErrorMessage) {
// Bind agenda tree to the scope
$scope.$watch(function () {
return Agenda.lastModified();
}, function () {
$scope.items = AgendaTree.getTree(Agenda.getAll());
});
$scope.showInternalItems = true;
$scope.alert = {};
// save parent and weight of moved agenda item (and all items on same level)
$scope.treeOptions = {
dropped: function(event) {
var parentID = null;
var droppedItemID = event.source.nodeScope.$modelValue.id;
if (event.dest.nodesScope.item) {
parentID = event.dest.nodesScope.item.id;
}
$http.post('/rest/agenda/item/sort/', {
nodes: event.dest.nodesScope.$modelValue,
parent_id: parentID}
).then(
function(success) {},
function(error){
$scope.alert = ErrorMessage.forAlert(error);
}
);
}
};
}
])
.controller('CurrentListOfSpeakersViewCtrl', [
'$scope',
'$http',
'$filter',
'Projector',
'ProjectionDefault',
'Agenda',
'Config',
'CurrentListOfSpeakersItem',
'CurrentListOfSpeakersSlide',
'gettextCatalog',
'WebpageTitle',
function($scope, $http, $filter, Projector, ProjectionDefault, Agenda, Config,
CurrentListOfSpeakersItem, CurrentListOfSpeakersSlide, gettextCatalog, WebpageTitle) {
$scope.alert = {};
$scope.currentListOfSpeakers = CurrentListOfSpeakersSlide;
// Watch for changes in the current list of speakers reference
$scope.$watch(function () {
return Config.lastModified('projector_currentListOfSpeakers_reference');
}, function () {
$scope.currentListOfSpeakersReference = $scope.config('projector_currentListOfSpeakers_reference');
$scope.updateCurrentListOfSpeakersItem();
});
$scope.$watch(function () {
return Projector.lastModified();
}, function() {
$scope.projectors = Projector.getAll();
// If there is just one projector we provide just the overlay.
if ($scope.projectors.length === 1) {
$scope.currentListOfSpeakersAsOverlay = true;
}
$scope.updateCurrentListOfSpeakersItem();
$scope.listOfSpeakersDefaultProjectorId = ProjectionDefault.filter({name: 'agenda_current_list_of_speakers'})[0].projector_id;
});
$scope.$watch(function () {
return $scope.item ? Agenda.lastModified($scope.item.id) : void 0;
}, function () {
$scope.updateCurrentListOfSpeakersItem();
});
$scope.updateCurrentListOfSpeakersItem = function () {
$scope.item = CurrentListOfSpeakersItem.getItem($scope.currentListOfSpeakersReference);
if ($scope.item) {
// all speakers
$scope.speakers = $filter('orderBy')($scope.item.speakers, 'weight');
// next speakers
$scope.nextSpeakers = $filter('filter')($scope.speakers, {'begin_time': null});
// current speaker
$scope.currentSpeaker = $filter('filter')($scope.speakers, {'begin_time': '!!', 'end_time': null});
// last speakers
$scope.lastSpeakers = $filter('filter')($scope.speakers, {'end_time': '!!'});
$scope.lastSpeakers = $filter('orderBy')($scope.lastSpeakers, 'begin_time');
} else {
$scope.speakers = void 0;
$scope.nextSpeakers = void 0;
$scope.currentSpeaker = void 0;
$scope.lastSpeakers = void 0;
}
if ($scope.item) {
WebpageTitle.updateTitle(gettextCatalog.getString('Current list of speakers') + ' ' +
gettextCatalog.getString('of') + ' ' + $scope.item.getTitle());
} else {
WebpageTitle.updateTitle(gettextCatalog.getString('Current list of speakers'));
}
};
// Set the current overlay status
if ($scope.currentListOfSpeakers.isProjected().length) {
var isProjected = $scope.currentListOfSpeakers.isProjectedWithOverlayStatus();
$scope.currentListOfSpeakersAsOverlay = isProjected[0].overlay;
} else {
$scope.currentListOfSpeakersAsOverlay = false;
}
$scope.setOverlay = function (overlay) {
$scope.currentListOfSpeakersAsOverlay = overlay;
var isProjected = $scope.currentListOfSpeakers.isProjectedWithOverlayStatus();
if (isProjected.length) {
_.forEach(isProjected, function (mapping) {
if (mapping.overlay != overlay) { // change the overlay if it is different
$scope.currentListOfSpeakers.project(mapping.projectorId, overlay);
}
});
}
};
}
])
//mark all agenda config strings for translation with Javascript
.config([
'gettext',
function (gettext) {
gettext('Enable numbering for agenda items');
gettext('Numbering prefix for agenda items');
gettext('This prefix will be set if you run the automatic agenda numbering.');
gettext('Agenda');
gettext('Invalid input.');
gettext('Numeral system for agenda items');
gettext('Arabic');
gettext('Roman');
gettext('Begin of event');
gettext('Input format: DD.MM.YYYY HH:MM');
gettext('Hide internal items when projecting subitems');
gettext('Number of last speakers to be shown on the projector');
gettext('List of speakers');
gettext('Show orange countdown in the last x seconds of speaking time');
gettext('Enter duration in seconds. Choose 0 to disable warning color.');
gettext('Couple countdown with the list of speakers');
gettext('[Begin speech] starts the countdown, [End speech] stops the ' +
'countdown.');
gettext('Agenda visibility');
gettext('Default visibility for new agenda items (except topics)');
}
]);
}());

View File

@ -1,60 +0,0 @@
<div class="header">
<div class="title">
<div class="submenu">
<a ui-sref="agenda.item.list" class="btn btn-sm btn-default">
<i class="fa fa-angle-double-left fa-lg"></i>
<translate>Back to overview</translate>
</a>
<!-- Current list of speakers projector button -->
<div class="btn-group button" uib-dropdown
uib-tooltip="{{ 'Project the current list of speakers' | translate }}"
os-perms="core.can_manage_projector">
<button type="button" class="btn btn-default btn-sm"
title="{{ 'Project current list of speakers' | translate }}"
ng-click="currentListOfSpeakers.project(listOfSpeakersDefaultProjectorId, currentListOfSpeakersAsOverlay)"
ng-class="{ 'btn-primary': currentListOfSpeakers.isProjected().length && inArray(currentListOfSpeakers.isProjected(), listOfSpeakersDefaultProjectorId)}">
<i class="fa fa-video-camera"></i>
<translate>Current list of speakers</translate>
</button>
<button type="button" class="btn btn-default btn-sm"
ng-if="projectors.length > 1"
uib-dropdown-toggle
ng-class="{ 'btn-primary': currentListOfSpeakers.isProjected().length && !inArray(currentListOfSpeakers.isProjected(), listOfSpeakersDefaultProjectorId)}">
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="split-button" ng-if="projectors.length > 1">
<li role="menuitem">
<a href="" ng-click="setOverlay(false); $event.stopPropagation();">
<i class="fa" ng-class="currentListOfSpeakersAsOverlay ? 'fa-circle-o' : 'fa-check-circle-o'"></i>
<translate>Project as slide</translate>
</a>
</li>
<li role="menuitem">
<a href="" ng-click="setOverlay(true); $event.stopPropagation();">
<i class="fa" ng-class="currentListOfSpeakersAsOverlay ? 'fa-check-circle-o' : 'fa-circle-o'"></i>
<translate>Project as overlay</translate>
</a>
</li>
<li class="divider"></li>
<li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'">
<a href="" ng-click="currentListOfSpeakers.project(projector.id, currentListOfSpeakersAsOverlay)"
ng-class="{ 'projected': inArray(currentListOfSpeakers.isProjected(), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(currentListOfSpeakers.isProjected(), projector.id)"></i>
{{ projector.name | translate }}
<span ng-if="projector.id == listOfSpeakersDefaultProjectorId">(<translate>Default</translate>)</span>
</a>
</li>
</ul>
</div>
</div>
<h1 translate>Current list of speakers</h1>
<h2> {{ item.getTitle() }}
<span class="slimlabel label label-danger ng-scope" style="" ng-if="item.speaker_list_closed" translate>
Closed
</span>
</h2>
</div>
</div>
<ng-include src="'static/templates/agenda/list-of-speakers-partial-management.html'"></ng-include>

View File

@ -1,54 +0,0 @@
<div ng-if="item" class="header">
<div class="title">
<div class="submenu">
<a ui-sref="agenda.item.list" class="btn btn-sm btn-default">
<i class="fa fa-angle-double-left fa-lg"></i>
<translate>Agenda</translate>
</a>
<a ui-sref="{{ item.getContentObjectDetailState() }}" class="btn btn-sm btn-default">
<i class="fa fa-angle-double-left fa-lg"></i>
{{ item.getContentResource().verboseName | translate }}
</a>
<!-- project list of speakers -->
<span class="btn-group" style="min-width:54px;" uib-dropdown
uib-tooltip="{{ 'Projector' | translate }} {{ item.isListOfSpeakersProjected()[0] || '' }}"
tooltip-enable="item.isListofSpeakersProjected().length"
os-perms="core.can_manage_projector">
<button type="button" class="btn btn-default btn-sm"
ng-click="item.projectListOfSpeakers(defaultProjectorListOfSpeakersId)"
ng-class="{ 'btn-primary': inArray(item.isListOfSpeakersProjected(), defaultProjectorListOfSpeakersId) }">
<i class="fa fa-video-camera"></i>
<translate>List of speakers</translate>
</button>
<button type="button" class="btn btn-default btn-sm slimDropDown"
ng-class="{ 'btn-primary': (item.isListOfSpeakersProjected().length && !inArray(item.isListOfSpeakersProjected(), defaultProjectorListOfSpeakersId) ) }"
ng-if="projectors.length > 1"
uib-dropdown-toggle>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" ng-if="projectors.length > 1">
<li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'">
<a href="" ng-click="item.projectListOfSpeakers(projector.id)"
ng-class="{ 'projected': inArray(item.isListOfSpeakersProjected(), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(item.isListOfSpeakersProjected(), projector.id) "></i>
{{ projector.name | translate }}
<span ng-if="defaultProjectorListOfSpeakersId == projector.id">(<translate>Default</translate>)</span>
</a>
</li>
</ul>
</span>
<!-- project -->
<projector-button model="item" default-projector-id="defaultProjectorItemId"
content="{{ item.getContentResource().verboseName | translate }}">
</projector-button>
</div>
<h1>{{ item.getTitle() }}</h1>
<h2>
<translate>List of speakers</translate>
<span ng-if="item.speaker_list_closed" class="slimlabel label label-danger"
translate>Closed</span>
</h2>
</div>
</div>
<ng-include src="'static/templates/agenda/list-of-speakers-partial-management.html'"></ng-include>

View File

@ -1,551 +0,0 @@
<div class="header">
<div class="title">
<div class="submenu">
<!-- new -->
<a ng-click="newDialog()" os-perms="agenda.can_manage"
class="btn btn-primary btn-sm">
<i class="fa fa-plus fa-lg"></i>
<translate>New</translate>
</a>
<!-- import -->
<span os-perms="agenda.can_manage">
<a ui-sref="topics.topic.import"
os-perms="agenda.can_see_internal_items"
class="btn btn-default btn-sm">
<i class="fa fa-download fa-lg"></i>
<translate>Import</translate>
</a>
</span>
<!-- current list of speakers -->
<a ui-sref="agenda.current-list-of-speakers" os-perms="users.can_see_name"
class="btn btn-default btn-sm">
<i class="fa fa-microphone fa-lg"></i>
<translate>Current list of speakers</translate>
</a>
<!-- project agenda button -->
<div class="btn-group button" uib-dropdown
uib-tooltip="{{ 'Projector' | translate }} {{ isAgendaProjected(mainListTree)[0] || '' }}"
tooltip-enable="isAgendaProjected(mainListTree).length"
os-perms="core.can_manage_projector">
<button type="button" class="btn btn-default btn-sm"
title="{{ 'Project agenda' | translate }}"
ng-click="projectAgenda(defaultProjectorId_all_items, mainListTree)"
ng-class="{ 'btn-primary': isAgendaProjected(mainListTree).length && inArray(isAgendaProjected(mainListTree), defaultProjectorId_all_items)}">
<i class="fa fa-video-camera"></i>
<translate>Agenda</translate>
</button>
<button type="button" class="btn btn-default btn-sm" uib-dropdown-toggle
ng-class="{ 'btn-primary': isAgendaProjected(mainListTree).length && !inArray(isAgendaProjected(mainListTree), defaultProjectorId_all_items)}"
ng-if="agendaHasSubitems || projectors.length > 1">
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="split-button" ng-if="agendaHasSubitems || projectors.length > 1">
<li role="menuitem" ng-show="agendaHasSubitems">
<a href="" ng-click="changeMainListTree(); $event.stopPropagation();">
<i class="fa" ng-class="mainListTree ? 'fa-square-o' : 'fa-check-square-o'"></i>
<translate>Only main agenda items</translate>
</a>
</li>
<li class="divider" ng-show="agendaHasSubitems && projectors.length > 1"></li>
<li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'" ng-show="projectors.length > 1">
<a href="" ng-click="projectAgenda(projectorId=projector.id, tree=mainListTree)"
ng-class="{ 'projected': inArray(isAgendaProjected(mainListTree), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(isAgendaProjected(mainListTree), projector.id) "></i>
{{ projector.name | translate }}
<span ng-if="projector.id == defaultProjectorId_all_items">(<translate>Default</translate>)</span>
</a>
</li>
</ul>
</div>
</div>
<h1 translate>Agenda</h1>
</div>
</div>
<div class="details">
<div class="row">
<div class="col-sm-12">
<!-- select mode -->
<button os-perms="agenda.can_manage" class="btn btn-sm"
ng-class="$parent.isSelectMode ? 'btn-primary' : 'btn-default'"
ng-click="$parent.isSelectMode = !$parent.isSelectMode; uncheckAll()">
<i class="fa fa-check-square-o"></i>
<translate>Select ...</translate>
</button>
<!-- sort button -->
<span os-perms="agenda.can_see_internal_items">
<a ui-sref="agenda.item.sort" os-perms="agenda.can_manage" class="btn btn-default btn-sm">
<i class="fa fa-sitemap fa-lg"></i>
<translate>Sort ...</translate>
</a>
</span>
<!-- auto numbering button -->
<span ng-if="config('agenda_enable_numbering')">
<button os-perms="agenda.can_manage" class="btn btn-default btn-sm"
ng-bootbox-confirm="{{ 'Are you sure you want to number all agenda items?' | translate }}"
ng-bootbox-confirm-action="autoNumbering()">
<i class="fa fa-sort-numeric-asc"></i>
<translate>Numbering</translate>
</button>
</span>
<!-- Export drop down list (for manager) -->
<div os-perms="motions.can_manage" class="pull-right" uib-dropdown>
<button type="button" class="btn btn-default btn-sm" id="dropdownExport" uib-dropdown-toggle>
<i class="fa fa-upload"></i>
<span ng-if="itemsFiltered.length == items.length" translate>
Export all
</span>
<span ng-if="itemsFiltered.length != items.length" translate>
Export filtered
</span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownExport">
<!-- PDF export -->
<li>
<a href="" ng-click="pdfExport()">
<i class="fa fa-file-pdf-o"></i>
PDF
</a>
</li>
<!-- CSV export -->
<li os-perms="agenda.can_manage">
<a href="" id="downloadLinkCSV"
ng-click="csvExport()">
<i class="fa fa-file-text-o"></i>
CSV
</a>
</li>
<!-- DOCX export -->
<li>
<a href="" ng-click="docxExport()">
<i class="fa fa-file-word-o"></i>
DOCX
</a>
</li>
</ul>
</div>
<!-- Export button (for normal users) -->
<button type="button" class="btn btn-default btn-sm pull-right"
os-perms="!motions.can_manage" ng-click="pdfExport()">
<i class="fa fa-file-pdf-o"></i>
<span ng-if="itemsFiltered.length == items.length" translate>
Export all
</span>
<span ng-if="itemsFiltered.length != items.length" translate>
Export filtered
</span>
</button>
</div>
</div>
<div uib-collapse="!isSelectMode" class="row spacer">
<div class="col-sm-12 text-left form-inline" os-perms="agenda.can_manage">
<!-- actions -->
<select ng-model="selectedAction" class="form-control input-sm">
<option value="" translate>--- Select action ---</option>
<option value="delete" translate>Delete</option>
<option value="setType" translate>Set visibility</option>
<option value="setState" translate>Set state</option>
</select>
<!-- Type (visibility) -->
<select ng-show="selectedAction == 'setType'" ng-model="selectedType" class="form-control input-sm">
<option value="" translate>--- Select visibility ---</option>
<option value="{{ AGENDA_ITEM }}" translate>
Public
</option>
<option value="{{ INTERNAL_ITEM }}" translate>
Internal
</option>
<option value="{{ HIDDEN_ITEM }}" translate>
Hidden
</option>
</select>
<!-- set type button -->
<a ng-show="selectedAction == 'setType' && selectedType"
ng-click="setTypeMultiple(selectedType)" class="btn btn-default btn-sm">
<translate>Set visibility</translate>
</a>
<!-- set state buttons -->
<a ng-show="selectedAction == 'setState'"
ng-click="setStateMultiple(true)" class="btn btn-default btn-sm">
<i class="fa fa-check-square-o"></i>
<translate>Done</translate>
</a>
<a ng-show="selectedAction == 'setState'"
ng-click="setStateMultiple(false)" class="btn btn-default btn-sm">
<i class="fa fa-square-o"></i>
<translate>Open</translate>
</a>
<!-- delete button -->
<a ng-if="selectedAction === 'delete'"
ng-bootbox-confirm="{{ 'Are you sure you want to delete all selected agenda items?' | translate }}"
ng-bootbox-confirm-action="deleteMultiple()"
class="btn btn-default btn-sm btn-danger">
<i class="fa fa-trash fa-lg"></i>
<translate>Delete selected items</translate>
</a>
</div>
</div>
<div class="spacer-top-lg italic row">
<div class="col-md-6">
<span os-perms="agenda.can_see_internal_items">{{ itemsFiltered.length }} /</span>
{{ (items|filter:{is_hidden:false}).length }} {{ "items" | translate }}<span ng-if="(items|filter:{selected:true}).length > 0">,
{{(items|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
<span os-perms="agenda.can_see_internal_items" class="optional">
<span ng-if="sumDurations() > 0">&middot;
<translate>Duration</translate>:
{{ sumDurations() | osMinutesToTime }}h
<span ng-if="config('agenda_start_event_date_time')">
(<translate>Estimated end:</translate> {{ calculateEndTime() }})
</span>
</span>
</span>
<span ng-if="agendaHasMultipleLayers() && items.length === itemsSearched.length">
&middot;
<a href="" ng-click="toggleCollapseState()">
<span ng-if="collapseState" translate>Expand all</span>
<span ng-if="!collapseState" translate>Collapse all</span>
</a>
</span>
</div>
<div class="col-md-6" ng-show="itemsFiltered.length > pagination.itemsPerPage">
<span class="pull-right">
<a href="" class="pagination-arrow" ng-click="pagination.prevPage()"
ng-if="pagination.showPrevPageArrow()">
&laquo;
</a>
<translate>Page</translate> {{ pagination.currentPage }} /
{{ pagination.getPageCount(itemsFiltered) }}
<a href="" class="pagination-arrow" ng-click="pagination.nextPage(itemsFiltered)"
ng-if="pagination.showNextPageArrow(itemsFiltered)">
&raquo;
</a>
</span>
</div>
</div>
<div id="agenda-table" class="os-table container-fluid">
<div class="row header-row">
<div class="col-xs-1 centered" ng-show="isSelectMode">
<i class="fa text-danger pointer" ng-class="selectedAll ? 'fa-check-square-o' : 'fa-square-o'"
ng-click="checkAll()"></i>
</div>
<div class="col-xs-11 main-header">
<span class="form-inline text-right pull-right">
<!-- clear all filters -->
<span class="sort-spacer pointer" ng-click="resetFilters(isSelectMode)"
ng-if="areFiltersSet()" ng-disabled="isSelectMode"
ng-class="{'disabled': isSelectMode}">
<i class="fa fa-window-close"></i>
<translate>Filter</translate>
</span>
<!-- boolean Filters -->
<!-- State -->
<span uib-dropdown>
<span class="sort-spacer pointer" id="dropdownItems" uib-dropdown-toggle
ng-class="{'bold': filter.booleanFilters.closed.value !== filter.booleanFilters.closed.defaultValue,
'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>State</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownItems">
<li>
<a href ng-click="filter.booleanFilters.closed.value = (filter.booleanFilters.closed.value ? undefined : true); filter.save();">
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.closed.value === true}"></i>
{{ filter.booleanFilters.closed.choiceYes | translate }}
</a>
</li>
<li>
<a href ng-click="filter.booleanFilters.closed.value = (filter.booleanFilters.closed.value === false) ? undefined : false; filter.save();">
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.closed.value === false}"></i>
{{ filter.booleanFilters.closed.choiceNo | translate }}
</a>
</li>
</ul>
</span>
<!-- Visibility -->
<span uib-dropdown>
<span class="sort-spacer pointer" id="dropdownItems" uib-dropdown-toggle
ng-class="{'bold': areVisibilityFiltersSet(),
'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Visibility</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownItems">
<li>
<a href ng-click="filter.booleanFilters.is_public.value = !filter.booleanFilters.is_public.value; filter.save();">
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.is_public.value}"></i>
<translate>Public items</translate>
</a>
</li>
<li os-perms="agenda.can_see_internal_items">
<a href ng-click="filter.booleanFilters.is_internal.value = !filter.booleanFilters.is_internal.value; filter.save();">
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.is_internal.value}"></i>
<translate>Internal items</translate>
</a>
</li>
<li os-perms="agenda.can_see_internal_items agenda.can_manage">
<a href ng-click="filter.booleanFilters.is_hidden.value = !filter.booleanFilters.is_hidden.value; filter.save();">
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.is_hidden.value}"></i>
<translate>Hidden items</translate>
</a>
</li>
</ul>
</span>
<!-- search field -->
<span class="form-group">
<span class="input-group">
<span class="input-group-addon"><i class="fa fa-search"></i></span>
<input type="text" ng-model="filter.filterString" class="form-control"
placeholder="{{ 'Search' | translate}}" ng-disabled="isSelectMode"
ng-change="filter.save()">
</span>
</span>
</span>
<!-- show all selected multiselectoptions -->
<span>
<!-- for all boolean Filters -->
<span ng-repeat="(name, booleanFilter) in filter.booleanFilters"
ng-hide="booleanFilter.value === booleanFilter.defaultValue"
class="pointer spacer-left-lg"
ng-click="booleanFilter.value = booleanFilter.defaultValue; filter.save();"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ booleanFilter.value ? booleanFilter.choiceYes : booleanFilter.choiceNo | translate }}
</span>
</span>
</span>
</div>
</div>
<!-- main table -->
<div class="row data-row" ng-mouseover="item.hover=true"
ng-mouseleave="item.hover=false"
ng-class="{'projected': item.isProjected().length,
'related-projected': item.isRelatedProjected().length}"
ng-repeat="item in itemsFiltered = (itemsSearched = (items
| osFilter : filter.filterString : filter.getObjectQueryString)
| filter : {closed: filter.booleanFilters.closed.value}
| itemTypeFilter : filter.booleanFilters)
| collapsedItemFilter
| limitTo : pagination.itemsPerPage : pagination.limitBegin">
<!-- select column -->
<div ng-show="isSelectMode" os-perms="agenda.can_manage" class="col-xs-1 centered">
<i class="fa text-danger pointer" ng-click="item.selected=!item.selected"
ng-class="item.selected ? 'fa-check-square-o' : 'fa-square-o'"></i>
</div>
<!-- projector column -->
<div class="col-xs-1 centered projector" os-perms="core.can_manage_projector">
<div class="btn-group" style="min-width:{{ (item.hasSubitems(items) || projectors.length > 1) ? '54' : '34' }}px;" uib-dropdown
uib-tooltip="{{ 'Projector' | translate }} {{ item.isProjected(item.tree)[0] || '' }}"
tooltip-enable="item.isProjected(item.tree).length">
<button class="btn btn-default btn-sm"
ng-click="item.project(getProjectionDefault(item), item.tree)"
ng-class="{ 'btn-primary': item.isProjected(item.tree).length && inArray(item.isProjected(item.tree), getProjectionDefault(item))}">
<i class="fa fa-video-camera"></i>
</button>
<button type="button" class="btn btn-default btn-sm slimDropDown"
ng-class="{ 'btn-primary': item.isProjected(item.tree).length && !inArray(item.isProjected(item.tree), getProjectionDefault(item))}"
ng-if="item.hasSubitems(items) || projectors.length > 1"
uib-dropdown-toggle>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="split-button"
ng-if="item.hasSubitems(items) || projectors.length > 1">
<li role="menuitem" ng-show="item.hasSubitems(items)">
<a href="" ng-click="changeItemTree(item); $event.stopPropagation();">
<i class="fa" ng-class="item.tree ? 'fa-check-square-o' : 'fa-square-o'"></i>
<translate>Include all sub items</translate>
</a>
</li>
<li class="divider" ng-show="item.hasSubitems(items)"></li>
<li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'">
<a href="" ng-click="item.project(projector.id, item.tree)"
ng-class="{ 'projected': inArray(item.isProjected(item.tree), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(item.isProjected(item.tree), projector.id)"></i>
{{ projector.name | translate }}
<span ng-if="projector.id == getProjectionDefault(item)">(<translate>Default</translate>)</span>
</a>
</li>
</ul>
</div>
</div>
<div class="no-projector-spacer" os-perms="!core.can_manage_projector"></div>
<!-- main content column -->
<div class="col-xs-6 content"
style="padding-left: calc({{ items.length === itemsSearched.length ? item.parentCount : 0 }}*25px)">
<div class="caret-spacer" ng-if="items.length === itemsSearched.length">
<i class="fa pointer"
ng-style="{visibility: hasChildren(item) ? 'visible' : 'hidden'}"
ng-class="item.hideChildren ? 'fa-caret-right' : 'fa-caret-down'"
ng-click="item.hideChildren = !item.hideChildren"></i>
</div>
<div class="title-column">
<!-- ID and title -->
<div>
<a class="title" ui-sref="{{ item.getContentObjectDetailState() }}" ng-show="isAllowedToSeeOpenLink(item)">
{{ item.getListViewTitle() }}
</a>
<span class="title" ng-hide="isAllowedToSeeOpenLink(item)">
{{ item.getListViewTitle() }}
</span>
</div>
<!-- hover menu -->
<div os-perms="agenda.can_see" ng-class="{'hiddenDiv': !item.hover}">
<small>
<a ui-sref="agenda.item.detail({id: item.id})" translate>List of speakers</a>
<span os-perms="agenda.can_manage"> &middot;
<a href="" ng-click="edit(item)" translate>Edit</a> &middot;
<a href="" class="text-danger"
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
<b>{{ item.getTitle() }}</b>"
ng-bootbox-confirm-action="deleteRelatedItem(item)" translate>Delete</a>
</span>
</small>
</div>
</div>
</div>
<!-- additional content column -->
<div class="col-xs-4 content" ng-style="{'width': isSelectMode ? 'calc(50% - 120px)' : 'calc(50% - 70px)'}">
<div style="width: 60%;" class="optional">
<small>
<!-- type dropdown for managers -->
<div os-perms="agenda.can_manage">
<div ng-mouseover="typeHover=true" ng-mouseleave="typeHover=false">
<span uib-dropdown>
<span id="dropdownType{{ item.id }}" class="pointer"
uib-dropdown-toggle uib-tooltip="{{ 'Change visibility' | translate }}"
tooltip-class="nobr">
<span ng-if="item.is_public && item.hover">
<i class="fa fa-eye"></i>
<translate>Public</translate>
</span>
<span ng-if="item.is_internal">
<i class="fa fa-eye"></i>
<translate>Internal</translate>
</span>
<span ng-if="item.is_hidden">
<i class="fa fa-eye"></i>
<translate>Hidden</translate>
</span>
<i class="fa fa-cog fa-lg spacer-left" ng-show="typeHover"></i>
</span>
<ul class="dropdown-menu" aria-labelledby="dropdownType{{ item.id }}">
<li>
<a href ng-click="item.type = AGENDA_ITEM; save(item);" translate>
Public item
</a>
</li>
<li>
<a href ng-click="item.type = INTERNAL_ITEM; save(item);" translate>
Internal item
</a>
</li>
<li>
<a href ng-click="item.type = HIDDEN_ITEM; save(item);" translate>
Hidden item
</a>
</li>
</ul>
</span>
</div>
</div>
<!-- type for non-managers -->
<div os-perms="!agenda.can_manage">
<span ng-if="item.is_public && item.hover">
<i class="fa fa-eye"></i>
<translate>Public</translate>
</span>
<span ng-if="item.is_internal">
<i class="fa fa-eye"></i>
<translate>Internal</translate>
</span>
<span ng-if="item.is_hidden">
<i class="fa fa-eye"></i>
<translate>Hidden</translate>
</span>
</div>
<!-- Duration -->
<div ng-style="{'visibility': (item.duration || item.hover) ? 'visible' : 'hidden'}">
<div class="popover-wrapper" os-perms="agenda.can_manage">
<i class="fa fa-clock-o"></i>
<span editable-text="item.durationText" e-placeholder="hh:mm"
onshow="generateDurationText(item)" onaftersave="setDurationText(item)">
<span ng-if="!item.duration" translate>Set duration ...</span>
<span ng-if="item.duration">
{{ (item.duration | osMinutesToTime)}}
<translate translate-comment="'h' means time in hours">h</translate>
</span>
</span>
</div>
<div os-perms="!agenda.can_manage">
<div os-perms="agenda.can_see_internal_items">
<span ng-if="item.duration">
<i class="fa fa-clock-o"></i> {{ item.duration | osMinutesToTime }}
<translate translate-comment="'h' means time in hours">h</translate>
</span>
</div>
</div>
</div>
<!-- Comment -->
<div ng-style="{'visibility': (item.comment || item.hover) ? 'visible' : 'hidden'}">
<div class="popover-wrapper" os-perms="agenda.can_manage">
<i class="fa fa-info-circle"></i>
<span editable-text="item.comment" onaftersave="save(item)">
<span ng-if="!item.comment" translate>Set comment ...</span>
<span ng-if="item.comment">{{ item.comment }}</span>
</span>
</div>
</div>
<!-- Number -->
<div ng-style="{'visibility': ((item.is_public) && item.hover) ? 'visible' : 'hidden'}" os-perms="agenda.can_manage">
<div class="popover-wrapper" os-perms="agenda.can_manage">
<i class="fa fa-info-circle"></i>
<span editable-text="item.item_number" onaftersave="save(item)">
<span ng-if="!item.item_number" translate>Set item number ...</span>
<span ng-if="item.item_number"><em translate>Change item number ...</em></span>
</span>
</div>
</div>
<template-hook hook-name="agendaListAdditionalContentColumn"></template-hook>
</small>
</div>
<div style="width: 40%;" class="pull-right">
<div os-perms="agenda.can_manage">
<div class="pointer nobr" ng-click="item.closed = !item.closed; save(item);" ng-show="item.hover || item.closed">
<i class="fa" ng-class="item.closed ? 'fa-check-square-o' : 'fa-square-o'"></i>
<span class="spacer-left" translate>Done</span>
</div>
</div>
<div os-perms="!agenda.can_manage" >
<div ng-show="item.closed">
<i class="fa fa-check-square-o"></i>
<span class="spacer-left" translate>Done</span>
</div>
</div>
</div>
</div>
</div> <!-- data row -->
</div> <!-- container -->
<ul uib-pagination
ng-show="itemsFiltered.length > pagination.itemsPerPage"
total-items="itemsFiltered.length"
items-per-page="pagination.itemsPerPage"
ng-model="pagination.currentPage"
ng-change="pagination.pageChanged()"
class="pagination-sm"
direction-links="false"
boundary-links="true"
first-text="&laquo;"
last-text="&raquo;">
</ul>
</div> <!-- details -->

View File

@ -1,48 +0,0 @@
<div class="header">
<div class="title">
<div class="submenu">
<a ui-sref="agenda.item.list" class="btn btn-sm btn-default">
<i class="fa fa-angle-double-left fa-lg"></i>
<translate>Back to overview</translate>
</a>
</div>
<h1 translate>Sort agenda</h1>
</div>
</div>
<div class="details agenda-sort">
<p translate>Drag and drop items to change the order of the agenda. Your modification will be saved immediately.</p>
<p>
<button class="btn btn-default btn-sm" ng-click="showInternalItems=!showInternalItems">
<translate ng-if="showInternalItems">Hide internal items</translate>
<translate ng-if="!showInternalItems">Show internal items</translate>
</button>
<div uib-alert ng-show="alert.show" ng-class="'alert-' + (alert.type || 'warning')" ng-click="alert={}" close="alert={}">
{{ alert.msg }}
</div>
<div ui-tree="treeOptions" id="tree-root" data-empty-placeholder-enabled="false">
<ol ui-tree-nodes ng-model="items">
<li ng-repeat="item in items" ui-tree-node ng-include="'nodes_renderer.html'">
</ol>
</div>
</div>
<!-- Nested node template -->
<script type="text/ng-template" id="nodes_renderer.html">
<div ui-tree-handle ng-if="!item.item.is_hidden">
{{ item.item.getListViewTitle() }}
</div>
<div ui-tree-handle ng-if="item.item.is_hidden && showInternalItems" class="internal">
<i class="fa fa-ban"></i> &nbsp;
{{ item.item.getListViewTitle() }}
</div>
<ol ui-tree-nodes="" ng-model="item.children">
<li ng-repeat="item in item.children" ui-tree-node ng-include="'nodes_renderer.html'">
</ol>
</script>

View File

@ -1,132 +0,0 @@
<div ng-if="item" class="details" ng-controller="ListOfSpeakersManagementCtrl">
<div class="speakers-toolbar">
<div class="pull-right">
<span os-perms="agenda.can_manage">
<button ng-if="item.speaker_list_closed" ng-click="closeList(false)"
class="btn btn-sm btn-default">
<translate>Open list of speakers</translate>
</button>
<button ng-if="!item.speaker_list_closed" ng-click="closeList(true)"
class="btn btn-sm btn-default">
<translate>Close list of speakers</translate>
</button>
</span>
<span os-perms="agenda.can_manage_list_of_speakers">
<button ng-if="isAllowed('removeAll')" class="btn btn-sm btn-danger"
ng-bootbox-confirm="{{ 'Are you sure you want to remove all speakers from this list?'| translate }}"
ng-bootbox-confirm-action="removeAllSpeakers()">
<i class="fa fa-trash fa-lg"></i>
<translate>Remove all speakers</translate>
</button>
</span>
</div>
</div>
<!-- text for empty list -->
<p ng-if="speakers.length == 0" translate>
The list of speakers is empty.
</p>
<template-hook hook-name="itemDetailListOfSpeakersButtons"></template-hook>
<!-- Last speakers -->
<div class="spacer-top-lg spacer-bottom">
<button ng-if="isAllowed('showLastSpeakers')" ng-click="$parent.showOldSpeakers = !$parent.showOldSpeakers"
class="btn btn-xs btn-default">
<translate ng-if="!$parent.showOldSpeakers">Last speakers</translate>
<translate ng-if="$parent.showOldSpeakers">Hide</translate>
</button>
<div uib-collapse="!showOldSpeakers">
<ol class="indentation">
<li ng-repeat="speaker in lastSpeakers">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
<small class="grey">
{{ getDuration(speaker) | osSecondsToTime }} <translate>minutes</translate>
(<translate>Start time</translate>:
{{ speaker.begin_time | date:'yyyy-MM-dd HH:mm:ss' }})
</small>
<button os-perms="agenda.can_manage_list_of_speakers" ng-click="removeSpeaker(speaker.id)"
class="btn btn-default btn-xs" title="{{ 'Remove' | translate }}">
<i class="fa fa-times"></i>
</button>
</ol>
</div>
</div>
<!-- Current speaker -->
<p ng-repeat="speaker in currentSpeaker" class="currentSpeaker spacer indentation">
<i class="fa fa-microphone fa-lg"></i>
{{ speaker.user.get_full_name() }}
<span os-perms="!agenda.can_manage_list_of_speakers">
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</span>
<button os-perms="agenda.can_manage_list_of_speakers" ng-click="endSpeech()"
class="btn btn-default btn-sm" title="{{ 'End speech' | translate }}">
<i class="fa fa-microphone-slash"></i> <translate>Stop</translate>
</button>
<button os-perms="agenda.can_manage_list_of_speakers" ng-click="toggleMarked(speaker)"
class="btn btn-default btn-sm" title="{{ 'Mark speaker' | translate }}">
<i class="fa" ng-class="speaker.marked ? 'fa-star' : 'fa-star-o'"></i>
</button>
<button os-perms="agenda.can_manage_list_of_speakers" ng-click="removeSpeaker(speaker.id)"
class="btn btn-default btn-sm" title="{{ 'Remove' | translate }}">
<i class="fa fa-times"></i>
</button>
</p>
<!-- Next speakers -->
<div ng-show="nextSpeakers.length > 0">
<div ui-tree="treeOptions" data-empty-placeholder-enabled="false">
<ol ui-tree-nodes="" ng-model="nextSpeakers">
<li ng-repeat="speaker in nextSpeakers | orderBy: 'weight'" ui-tree-node>
<i os-perms="agenda.can_manage_list_of_speakers" ui-tree-handle="" class="fa fa-arrows-v"></i>
{{ $index + 1 }}.
{{ speaker.user.get_full_name() }}
<span os-perms="!agenda.can_manage_list_of_speakers">
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</span>
&nbsp;
<button os-perms="agenda.can_manage_list_of_speakers" ng-click="beginSpeech(speaker.id)"
class="btn btn-default btn-sm" title="{{ 'Begin speech' | translate }}">
<i class="fa fa-microphone"></i> <translate>Start</translate>
</button>
<button os-perms="agenda.can_manage_list_of_speakers" ng-click="toggleMarked(speaker)"
class="btn btn-default btn-sm" title="{{ 'Mark speaker' | translate }}">
<i class="fa" ng-class="speaker.marked ? 'fa-star' : 'fa-star-o'"></i>
</button>
<button os-perms="agenda.can_manage_list_of_speakers" ng-click="removeSpeaker(speaker.id)"
class="btn btn-default btn-sm" title="{{ 'Remove' | translate }}">
<i class="fa fa-times"></i>
</button>
</ol>
</div>
</div>
<!-- Select speakers form -->
<div class="form-group spacer-top-lg">
<div uib-alert ng-show="alert.show" ng-class="'alert-' + (alert.type || 'warning')" ng-click="alert={}" close="alert={}">
{{ alert.msg }}
</div>
<div os-perms="agenda.can_manage_list_of_speakers">
<select chosen
ng-model="speakerSelectBox.selected"
ng-change="addSpeaker(speakerSelectBox.selected)"
ng-options="user.id as user.get_full_name() for user in users"
search-contains="true"
placeholder-text-single="'Select or search a participant ...' | translate"
no-results-text="'No results available ...' | translate"
class="form-control">
<select>
</div>
<p class="spacer">
<button ng-if="isAllowed('add')" ng-click="addSpeaker()" class="btn btn-default btn-sm">
<i class="fa fa-plus"></i>
<translate>Add me</translate>
</button>
<button ng-if="isAllowed('remove')" ng-click="removeSpeaker()" class="btn btn-default btn-sm">
<i class="fa fa-minus"></i>
<translate>Remove me</translate>
</button>
</div>
</div>

View File

@ -1,36 +0,0 @@
<div id="speakerbox">
<h3 translate>List of speakers</h3>
<!-- Last speakers -->
<p ng-repeat="speaker in lastSpeakers = (agendaItem.speakers
| filter: {end_time: '!!', begin_time: '!!'})
| limitTo: config('agenda_show_last_speakers') : (lastSpeakers.length - config('agenda_show_last_speakers'))"
class="lastSpeakers">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</p>
<!-- Current speaker -->
<div ng-repeat="speaker in agendaItem.speakers | filter: {end_time: null, begin_time: '!!'} "
class="currentSpeaker">
<i class="fa fa-microphone fa-lg"></i>
<span class="pull-right" style="width:calc(100% - 30px);">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</span>
</div>
<!-- Next speakers -->
<ol class="nextSpeakers">
<li ng-repeat="speaker in nextSpeakers = (agendaItem.speakers
| filter: {begin_time: null})
| orderBy:'weight'
| limitTo: 3">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</li>
</ol>
<p ng-if="nextSpeakers.length > 3" class="lastSpeakers">
<i>+ {{ nextSpeakers.length - 3 }}</i>
</p>
</div>

View File

@ -1,43 +0,0 @@
<div class="content scrollcontent">
<!-- Title -->
<div id="title">
<h1 translate>List of speakers</h1>
<h2>{{ agendaItem.getListOfSpeakersTitle() }}
<span ng-if="(agendaItem.speakers | filter: {begin_time: null}).length > 0">
&ndash; {{ (agendaItem.speakers | filter: {begin_time: null}).length }} <translate>speakers</translate>
</span>
<span ng-if="agendaItem.speaker_list_closed" class="slimlabel label label-danger" translate>Closed</span>
</h2>
</div>
<div class="zoomcontent">
<!-- Last speakers -->
<p ng-repeat="speaker in lastSpeakers = (agendaItem.speakers
| filter: {end_time: '!!', begin_time: '!!'})
| limitTo: config('agenda_show_last_speakers') : (lastSpeakers.length - config('agenda_show_last_speakers'))"
class="lastSpeakers">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</p>
<!-- Current speaker -->
<p ng-repeat="speaker in currentspeakers = (agendaItem.speakers
| filter: {end_time: null, begin_time: '!!'})"
class="currentSpeaker nobr">
<i class="fa fa-microphone fa-lg"></i>
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</p>
<!-- Next speakers -->
<ol class="nextSpeakers">
<li ng-repeat="speaker in agendaItem.speakers
| filter: {begin_time: null}
| orderBy:'weight'">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</li>
</ol>
</div>
</div>

View File

@ -1,8 +0,0 @@
<div ng-controller="SlideCurrentListOfSpeakersCtrl">
<ng-include src="'static/templates/agenda/partial-slide-current-list-of-speakers.html'"
ng-if="!overlay"></ng-include>
<ng-include src="'static/templates/agenda/partial-slide-current-list-of-speakers-overlay.html'"
ng-if="overlay"></ng-include>
</div>

View File

@ -1,27 +0,0 @@
<div ng-controller="SlideItemListCtrl" class="content scrollcontent">
<h1 ng-if="!element.id" translate>Agenda</h1>
<h1 ng-if="element.id">{{ rootItem.getTitle() }}</h1>
<div class="agendalist zoomcontent" ng-class="{'spacer-left-lg': element.id}">
<table class="agendalist-table">
<tr ng-repeat="node in tree" ng-include="'projector_agenda_renderer.html'"></tr>
</table>
</div>
</div>
<!-- Nested node template -->
<script type="text/ng-template" id="projector_agenda_renderer.html">
<td class="number" ng-if="!node.item.closed">
<p ng-class="{mainitem: node.item.parent_id === null, subitem: node.item.parent_id !== null}">
{{ node.item.item_number }}
</p>
</td>
<td ng-if="!node.item.closed">
<p ng-class="{mainitem: node.item.parent_id === null, subitem: node.item.parent_id !== null}">
{{ node.item.getProjectorTitle() }}
</p>
<table ng-if="node.children.length" class="agendalist-table">
<tr ng-repeat="node in node.children" ng-include="'projector_agenda_renderer.html'"></tr>
</table>
</td>
</script>

View File

@ -1,36 +0,0 @@
<div ng-controller="SlideListOfSpeakersCtrl" class="content scrollcontent">
<!-- Title -->
<div id="title">
<h1 translate>List of speakers</h1>
<h2>
{{ item.getListOfSpeakersTitle() }}
<span ng-if="(item.speakers | filter: {begin_time: null}).length > 0">
&ndash; {{ (item.speakers | filter: {begin_time: null}).length }} <translate>speakers</translate>
</span>
<span ng-if="item.speaker_list_closed" class="slimlabel label label-danger" translate>Closed</span>
</h2>
</div>
<div class="zoomcontent">
<!-- Last speakers -->
<p ng-repeat="speaker in lastSpeakers = (item.speakers | filter: {end_time: '!!', begin_time: '!!'}) |
limitTo: config('agenda_show_last_speakers') : (lastSpeakers.length - config('agenda_show_last_speakers'))" class="lastSpeakers">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
<!-- Current speaker -->
<p ng-repeat="speaker in item.speakers | filter: {end_time: null, begin_time: '!!'}"
class="currentSpeaker">
<i class="fa fa-microphone fa-lg"></i>
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
<!-- Next speakers -->
<ol class="nextSpeakers">
<li ng-repeat="speaker in item.speakers | filter: {begin_time: null} | orderBy:'weight'">
{{ speaker.user.get_full_name() }}
<i class="fa fa-star" ng-if="speaker.marked" title="{{ 'Marked' | translate }}"></i>
</ol>
</div>
</div>

View File

@ -1,10 +0,0 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^docxtemplate/$',
views.AgendaDocxTemplateView.as_view(),
name='agenda_docx_template'),
]

View File

@ -15,7 +15,6 @@ from openslides.utils.rest_api import (
detail_route,
list_route,
)
from openslides.utils.views import BinaryTemplateView
from ..utils.auth import has_perm
from .access_permissions import ItemAccessPermissions
@ -340,11 +339,3 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
inform_changed_data(items)
return Response({'detail': _('The agenda has been sorted.')})
# Special views
class AgendaDocxTemplateView(BinaryTemplateView):
"""
Returns the template for motions docx export
"""
template_name = 'templates/docx/agenda.docx'

View File

@ -1,19 +0,0 @@
.electionresults table {
width: calc(100% - 230px);
}
#speakerbox {
width: 40%;
float: right;
margin: 20px;
right: 0;
bottom: 0;
position: absolute;
background: #d3d3d3;
border-radius: 7px;
border: 1px solid #999;
padding: 0px 7px 10px 19px;
z-index: 99;
box-shadow: 3px 3px 10px 1px rgba(0,0,0,0.5);
overflow: hidden;
}

View File

@ -1,14 +0,0 @@
#content .col1 .ballot-tabs {
ul {
margin-left: 0px;
}
li.active a {
background-color: #f5f5f5 !important;
}
.tab-content {
background-color: #f5f5f5;
border: 1px solid #ddd;
border-width: 0px 1px 1px 1px;
padding: 15px;
}
}

View File

@ -1,500 +0,0 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.assignments', [])
.factory('AssignmentPollOption', [
'DS',
'jsDataModel',
'gettextCatalog',
'Config',
'MajorityMethods',
function (DS, jsDataModel, gettextCatalog, Config, MajorityMethods) {
return DS.defineResource({
name: 'assignments/polloption',
useClass: jsDataModel,
// Change the stringified numbers to floats.
beforeInject: function (resource, instance) {
_.forEach(instance.votes, function (vote) {
vote.weight = parseFloat(vote.weight);
});
},
methods: {
getVotes: function () {
if (!this.poll.has_votes) {
// Return undefined if this poll has no votes.
return;
}
// Initial values for the option
var votes = [],
config = Config.get('assignments_poll_100_percent_base').value;
var base = this.poll.getPercentBase(config);
if (typeof base === 'object' && base !== null) {
// this.poll.pollmethod === 'yna'
base = base[this.id];
}
_.forEach(this.votes, function (vote) {
// Initial values for the vote
var order = '',
value = '',
percentStr = '',
percentNumber;
// Check for special value
switch (vote.weight) {
case -1:
value = gettextCatalog.getString('majority');
break;
case -2:
value = gettextCatalog.getString('undocumented');
break;
default:
if (vote.weight >= 0) {
value = vote.weight;
} else {
value = 0; // Vote was not defined. Set value to 0.
}
}
switch (vote.value) {
case "Yes":
order = 1;
break;
case "No":
order = 2;
break;
case "Abstain":
order = 3;
break;
default:
order = 0;
}
// Special case where to skip percents
var skipPercents = config === 'YES_NO' && vote.value === 'Abstain';
if (base && !skipPercents) {
percentNumber = Math.round(vote.weight * 100 / base * 100) / 100;
if (percentNumber >= 0) {
percentStr = '(' + percentNumber + ' %)';
}
}
votes.push({
'order': order,
'label': gettextCatalog.getString(vote.value),
'value': value,
'percentStr': percentStr,
'percentNumber': percentNumber
});
});
return _.sortBy(votes, 'order');
},
// Returns 0 or positive integer if quorum is reached or surpassed.
// Returns negativ integer if quorum is not reached.
// Returns undefined if we can not calculate the quorum.
isReached: function (method) {
if (!this.poll.has_votes) {
// Return undefined if this poll has no votes.
return;
}
var isReached;
var config = Config.get('assignments_poll_100_percent_base').value;
var base = this.poll.getPercentBase(config);
if (typeof base === 'object' && base !== null) {
// this.poll.pollmethod === 'yna'
base = base[this.id];
}
if (base) {
// Provide result only if base is not undefined and not 0.
isReached = MajorityMethods[method](this.getVoteYes(), base);
}
return isReached;
},
// Returns the weight for the vote or the vote 'yes' in case of YNA poll method.
getVoteYes: function () {
var voteYes = 0;
if (this.poll.pollmethod === 'yna') {
var voteObj = _.find(this.votes, function (vote) {
return vote.value === 'Yes';
});
if (voteObj) {
voteYes = voteObj.weight;
}
} else {
// pollmethod === 'votes'
voteYes = this.votes[0].weight;
}
return voteYes;
}
},
relations: {
belongsTo: {
'assignments/poll': {
localField: 'poll',
localKey: 'poll_id',
},
'users/user': {
localField: 'candidate',
localKey: 'candidate_id',
}
}
},
});
}
])
.factory('AssignmentPoll', [
'$http',
'DS',
'jsDataModel',
'gettextCatalog',
'AssignmentPollOption',
'Config',
function ($http, DS, jsDataModel, gettextCatalog, AssignmentPollOption, Config) {
var name = 'assignments/poll';
return DS.defineResource({
name: name,
useClass: jsDataModel,
// Change the stringified numbers to floats.
beforeInject: function (resource, instance) {
var attrs = ['votescast', 'votesinvalid', 'votesvalid', 'votesabstain', 'votesno'];
_.forEach(attrs, function (attr) {
if (instance[attr] !== null) {
instance[attr] = parseFloat(instance[attr]);
}
});
},
methods: {
getResourceName: function () {
return name;
},
// Returns percent base. Returns undefined if calculation is not possible in general.
getPercentBase: function (config, type) {
var base;
switch (config) {
case 'CAST':
if (this.votescast <= 0 || this.votesinvalid < 0) {
// It would be OK to check only this.votescast < 0 because 0
// is checked again later but this is a little bit faster.
break;
}
base = this.votescast;
/* falls through */
case 'VALID':
if (this.votesvalid < 0) {
base = void 0;
break;
}
if (typeof base === 'undefined' && type !== 'votescast' && type !== 'votesinvalid') {
base = this.votesvalid;
}
/* falls through */
case 'YES_NO_ABSTAIN':
case 'YES_NO':
if (this.pollmethod === 'yna') {
if (typeof base === 'undefined' && type !== 'votescast' && type !== 'votesinvalid' && type !== 'votesvalid') {
base = {};
_.forEach(this.options, function (option) {
var allVotes = option.votes;
if (config === 'YES_NO') {
allVotes = _.filter(allVotes, function (vote) {
// Extract abstain votes in case of YES_NO percent base.
// Do not extract abstain vote if it is set to majority so the predicate later
// fails and therefor we get an undefined base. Reason: It should not be possible
// to set abstain to majority and nevertheless calculate percents of yes and no.
return vote.value !== 'Abstain' || vote.weight === -1;
});
}
var predicate = function (vote) {
return vote.weight < 0;
};
if (_.findIndex(allVotes, predicate) === -1) {
base[option.id] = _.reduce(allVotes, function (sum, vote) {
return sum + vote.weight;
}, 0);
}
});
}
} else {
// this.pollmethod === 'votes'
var predicate = function (option) {
return option.votes[0].weight < 0;
};
if (_.findIndex(this.options, predicate) !== -1) {
base = void 0;
} else {
if (typeof base === 'undefined' && type !== 'votesabstain' &&
type !== 'votesno' && type !== 'votescast' &&
type !== 'votesinvalid' && type !== 'votesvalid') {
base = _.reduce(this.options, function (sum, option) {
return sum + option.votes[0].weight;
}, 0);
}
}
}
}
return base;
},
// Returns object with value and percent for this poll (for votes valid/invalid/cast only).
getVote: function (type) {
if (!this.has_votes) {
// Return undefined if this poll has no votes.
return;
}
// Initial values
var value = '',
percentStr = '',
percentNumber,
vote,
config = Config.get('assignments_poll_100_percent_base').value;
switch (type) {
case 'votesabstain':
vote = this.votesabstain;
break;
case 'votesno':
vote = this.votesno;
break;
case 'votesinvalid':
vote = this.votesinvalid;
break;
case 'votesvalid':
vote = this.votesvalid;
break;
case 'votescast':
vote = this.votescast;
break;
}
// Check special values
switch (vote) {
case -1:
value = gettextCatalog.getString('majority');
break;
case -2:
value = gettextCatalog.getString('undocumented');
break;
default:
if (vote >= 0) {
value = vote;
} else {
value = 0; // value was not defined
}
}
// Calculate percent value
var base = this.getPercentBase(config, type);
if (base) {
percentNumber = Math.round(vote * 100 / (base) * 10) / 10;
percentStr = '(' + percentNumber + ' %)';
}
return {
'value': value,
'percentStr': percentStr,
'percentNumber': percentNumber,
'display': value + ' ' + percentStr
};
}
},
relations: {
belongsTo: {
'assignments/assignment': {
localField: 'assignment',
localKey: 'assignment_id',
}
},
hasMany: {
'assignments/polloption': {
localField: 'options',
foreignKey: 'poll_id',
}
}
},
});
}
])
.provider('AssignmentPollDecimalPlaces', [
function () {
this.$get = ['$q', function ($q) {
return {
getPlaces: function (poll, find) {
if (find) {
return $q(function (resolve) {
resolve(0);
});
} else {
return 0;
}
},
};
}];
}
])
.factory('AssignmentRelatedUser', [
'DS',
function (DS) {
return DS.defineResource({
name: 'assignments/relateduser',
relations: {
belongsTo: {
'users/user': {
localField: 'user',
localKey: 'user_id',
}
}
}
});
}
])
.factory('Assignment', [
'$http',
'DS',
'Projector',
'ProjectHelper',
'AssignmentRelatedUser',
'AssignmentPoll',
'jsDataModel',
'gettext',
function ($http, DS, Projector, ProjectHelper, AssignmentRelatedUser, AssignmentPoll,
jsDataModel, gettext) {
var name = 'assignments/assignment';
return DS.defineResource({
name: name,
useClass: jsDataModel,
verboseName: gettext('Election'),
verboseNamePlural: gettext('Elections'),
methods: {
getResourceName: function () {
return name;
},
getTitle: function () {
return this.title;
},
getAgendaTitle: function () {
return this.getTitle();
},
// link name which is shown in search result
getSearchResultName: function () {
return this.getAgendaTitle();
},
// return true if a specific relation matches for given searchquery
// (here: related_users/candidates)
hasSearchResult: function (results) {
var assignment = this;
// search for related users (check if any user.id from already found users matches)
return _.some(results, function(result) {
if (result.getResourceName() === "users/user") {
if (_.some(assignment.assignment_related_users, {'user_id': result.id})) {
return true;
}
}
});
},
// override project function of jsDataModel factory
project: function (projectorId, pollId) {
var isProjectedIds = this.isProjected(pollId);
var requestData = {
clear_ids: isProjectedIds,
};
if (_.indexOf(isProjectedIds, projectorId) == -1) {
requestData.prune = {
id: projectorId,
element: {
name: 'assignments/assignment',
id: this.id,
poll: pollId
},
};
}
return ProjectHelper.project(requestData);
},
// override isProjected function of jsDataModel factory
isProjected: function (poll_id, anyPoll) {
// Returns the ids of all projectors with an element
// with the name 'assignments/assignment'. Else returns an empty list.
// Provide a poll_id to query a specific poll or set anyPoll to true, to
// query whether any poll (but not the assignment itself) is projected.
var self = this;
var predicate = function (element) {
var value;
if (typeof poll_id === 'undefined') {
// Assignment detail slide without poll
value = element.name == 'assignments/assignment' &&
typeof element.id !== 'undefined' &&
element.id == self.id &&
typeof element.poll === 'undefined';
} else if (anyPoll) {
// Assignment detail slide with any poll
value = element.name == 'assignments/assignment' &&
typeof element.id !== 'undefined' &&
element.id == self.id &&
typeof element.poll !== 'undefined';
} else {
// Assignment detail slide with specific poll
value = element.name == 'assignments/assignment' &&
typeof element.id !== 'undefined' &&
element.id == self.id &&
typeof element.poll !== 'undefined' &&
element.poll == poll_id;
}
return value;
};
var isProjectedIds = [];
Projector.getAll().forEach(function (projector) {
if (typeof _.findKey(projector.elements, predicate) === 'string') {
isProjectedIds.push(projector.id);
}
});
return isProjectedIds;
},
isRelatedProjected: function () {
var listOfSpeakers = [];
if (this.agenda_item) {
listOfSpeakers = this.agenda_item.isListOfSpeakersProjected();
}
return listOfSpeakers.concat(this.isProjected(null, true));
},
},
relations: {
belongsTo: {
'agenda/item': {
localKey: 'agenda_item_id',
localField: 'agenda_item',
}
},
hasMany: {
'core/tag': {
localField: 'tags',
localKeys: 'tags_id',
},
'assignments/relateduser': {
localField: 'assignment_related_users',
foreignKey: 'assignment_id',
},
'assignments/poll': {
localField: 'polls',
foreignKey: 'assignment_id',
}
}
},
beforeInject: function (resource, instance) {
AssignmentRelatedUser.ejectAll({where: {assignment_id: {'==': instance.id}}});
}
});
}
])
.run(['Assignment', function(Assignment) {}]);
}());

View File

@ -1,666 +0,0 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
.factory('AssignmentContentProvider', [
'$filter',
'HTMLValidizer',
'gettextCatalog',
'PDFLayout',
'AssignmentPollDecimalPlaces',
function($filter, HTMLValidizer, gettextCatalog, PDFLayout, AssignmentPollDecimalPlaces) {
var createInstance = function(assignment) {
// page title
var title = PDFLayout.createTitle(assignment.title);
var isElectedSemaphore = false;
// open posts
var createPreamble = function() {
var preambleText = gettextCatalog.getString("Number of persons to be elected") + ": ";
var memberNumber = ""+assignment.open_posts;
var preamble = {
text: [
{
text: preambleText,
bold: true,
style: 'textItem'
},
{
text: memberNumber,
style: 'textItem'
}
]
};
return preamble;
};
// description
var createDescription = function() {
if (assignment.description) {
var html = HTMLValidizer.validize(assignment.description);
var descriptionText = gettextCatalog.getString("Description") + ":";
var description = [
{
text: descriptionText,
bold: true,
style: 'textItem'
},
{
text: $(html).text(),
style: 'textItem',
margin: [10, 0, 0, 0]
}
];
return description;
} else {
return "";
}
};
// show candidate list (if assignment phase is not 'finished')
var createCandidateList = function() {
if (assignment.phase != 2) {
var candidates = $filter('orderBy')(assignment.assignment_related_users, 'weight');
var candidatesText = gettextCatalog.getString("Candidates") + ": ";
var userList = [];
_.forEach(candidates, function(assignmentsRelatedUser) {
userList.push({
text: assignmentsRelatedUser.user.get_full_name(),
margin: [0, 0, 0, 10],
}
);
});
var cadidateList = {
columns: [
{
text: candidatesText,
bold: true,
width: "25%",
style: 'textItem'
},
{
ul: userList,
style: 'textItem'
}
]
};
return cadidateList;
} else {
return "";
}
};
// handles the case if a candidate is elected or not
var electedCandidateLine = function(candidateName, pollOption, pollTableBody) {
if (pollOption.is_elected) {
isElectedSemaphore = true;
return {
text: candidateName + "*",
bold: true,
style: PDFLayout.flipTableRowStyle(pollTableBody.length)
};
} else {
return {
text: candidateName,
style: PDFLayout.flipTableRowStyle(pollTableBody.length)
};
}
};
//creates the voting string for the result table and differentiates between special values
var parseVoteValue = function(voteObject, printLabel, precision) {
var voteVal = '';
if (voteObject) {
if (printLabel) {
voteVal += voteObject.label + ': ';
}
voteVal += $filter('number')(voteObject.value, precision);
if (voteObject.percentStr) {
voteVal += ' ' + voteObject.percentStr;
}
}
voteVal += '\n';
return voteVal;
};
// creates the election result table
var createPollResultTable = function() {
var resultBody = [];
_.forEach(assignment.polls, function(poll, pollIndex) {
if (poll.published) {
var pollTableBody = [];
var precision = AssignmentPollDecimalPlaces.getPlaces(poll);
resultBody.push({
text: gettextCatalog.getString('Ballot') + ' ' + (pollIndex+1),
bold: true,
style: 'textItem',
margin: [0, 15, 0, 0]
});
pollTableBody.push([
{
text: gettextCatalog.getString('Candidates'),
style: 'tableHeader',
},
{
text: gettextCatalog.getString('Votes'),
style: 'tableHeader',
}
]);
_.forEach(poll.options, function(pollOption, optionIndex) {
var candidateName = pollOption.candidate.get_full_name();
var votes = pollOption.getVotes(); // 0 = yes, 1 = no, 2 = abstain
var tableLine = [];
tableLine.push(electedCandidateLine(candidateName, pollOption, pollTableBody));
if (poll.pollmethod == 'votes') {
tableLine.push(
{
text: parseVoteValue(votes[0], false, precision),
style: PDFLayout.flipTableRowStyle(pollTableBody.length)
}
);
} else {
var resultBlock = [];
_.forEach(votes, function(vote) {
resultBlock.push(parseVoteValue(vote, true, precision));
});
tableLine.push({
text: resultBlock,
style: PDFLayout.flipTableRowStyle(pollTableBody.length)
}
);
}
pollTableBody.push(tableLine);
});
var pushConcludeRow = function (title, fieldName) {
if (poll[fieldName]) {
pollTableBody.push([
{
text: gettextCatalog.getString(title),
style: 'tableConclude'
},
{
text: parseVoteValue(poll.getVote(fieldName), false, precision),
style: 'tableConclude'
},
]);
}
};
pushConcludeRow('Abstain', 'votesabstain');
pushConcludeRow('No', 'votesno');
pushConcludeRow('Valid ballots', 'votesvalid');
pushConcludeRow('Invalid ballots', 'votesinvalid');
pushConcludeRow('Casted ballots', 'votescast');
var resultTableJsonSting = {
table: {
widths: ['64%','33%'],
headerRows: 1,
body: pollTableBody,
},
layout: 'headerLineOnly',
};
resultBody.push(resultTableJsonSting);
}
});
// add the legend to the result body
if (assignment.polls.length > 0 && isElectedSemaphore) {
resultBody.push({
text: '* = ' + gettextCatalog.getString('is elected'),
margin: [0, 5, 0, 0],
});
}
return resultBody;
};
var getContent = function() {
return [
title,
createPreamble(),
createDescription(),
createCandidateList(),
createPollResultTable()
];
};
return {
getContent: getContent,
title: assignment.title
};
};
return {
createInstance: createInstance
};
}
])
.factory('BallotContentProvider', [
'$q',
'$filter',
'gettextCatalog',
'PDFLayout',
'Config',
'User',
'ImageConverter',
function($q, $filter, gettextCatalog, PDFLayout, Config, User, ImageConverter) {
var createInstance = function(assignment, poll, pollNumber) {
var logoBallotPaperUrl = Config.get('logo_pdf_ballot_paper').value.path;
var imageMap = {};
// PDF header
var header = function() {
var columns = [];
// logo
if (logoBallotPaperUrl) {
columns.push({
image: logoBallotPaperUrl,
fit: [90,20],
width: '20%'
});
}
var text = Config.get('general_event_name').value;
columns.push({
text: text,
fontSize: 8,
alignment: 'right',
});
return {
color: '#555',
margin: [30, 10, 10, -10], // [left, top, right, bottom]
columns: columns,
columnGap: 10
};
};
// page title
var createTitle = function() {
return {
text: assignment.title,
style: 'title',
};
};
// poll description
var createPollHint = function() {
var description = poll.description ? ': ' + poll.description : '';
return {
text: gettextCatalog.getString("Ballot") + " " + pollNumber + description,
style: 'description',
};
};
// election entries
var createYNBallotEntry = function(decision) {
var YNColumn = [
{
width: "auto",
stack: [
PDFLayout.createBallotEntry(gettextCatalog.getString("Yes"))
]
},
{
width: "auto",
stack: [
PDFLayout.createBallotEntry(gettextCatalog.getString("No"))
]
},
];
if (poll.pollmethod == 'yna') {
YNColumn.push({
width: "auto",
stack: [
PDFLayout.createBallotEntry(gettextCatalog.getString("Abstain"))
]
});
}
return [
{
text: decision,
margin: [40, 10, 0, 0],
},
{
columns: YNColumn
}
];
};
var createSelectionField = function() {
var candidates = $filter('orderBy')(poll.options, 'weight');
var candidateBallotList = [];
if (poll.pollmethod == 'votes') {
_.forEach(candidates, function(option) {
var candidate = option.candidate.get_full_name();
candidateBallotList.push(PDFLayout.createBallotEntry(candidate));
});
// Add 'no' option
var no = gettextCatalog.getString('No');
var ballotEntry = PDFLayout.createBallotEntry(no);
ballotEntry.margin[1] = 25; // top margin
candidateBallotList.push(ballotEntry);
} else {
_.forEach(candidates, function(option) {
var candidate;
if (option.candidate) {
candidate = option.candidate.get_full_name();
}
candidateBallotList.push(createYNBallotEntry(candidate));
});
}
return candidateBallotList;
};
var createSection = function(marginTop) {
// since it is not possible to give a column a fixed height, we draw an "empty" column
// with a one px width and a fixed top-margin
return {
columns: [
{
width: 1,
margin: [0, marginTop],
text: '',
},
{
width: '*',
stack: [
header(),
createTitle(),
createPollHint(),
createSelectionField(),
],
},
]
};
};
var createTableBody = function(numberOfRows, sheetend, maxballots) {
var ballotstoprint = numberOfRows * 2;
if (Number.isInteger(maxballots) && maxballots > 0 && maxballots < ballotstoprint) {
ballotstoprint = maxballots;
}
var tableBody = [];
while (ballotstoprint > 1){
tableBody.push([createSection(sheetend), createSection(sheetend)]);
ballotstoprint -= 2;
}
if (ballotstoprint == 1) {
tableBody.push([createSection(sheetend), '']);
}
return tableBody;
};
var createContentTable = function() {
// first, determine how many ballots we need
var amount;
var amount_method = Config.get('assignments_pdf_ballot_papers_selection').value;
switch (amount_method) {
case 'NUMBER_OF_ALL_PARTICIPANTS':
amount = User.getAll().length;
break;
case 'NUMBER_OF_DELEGATES':
//TODO: assumption that DELEGATES is always group id 2. This may not be true
var group_id = 2;
amount = User.filter({where: {'groups_id': {contains:group_id} }}).length;
break;
case 'CUSTOM_NUMBER':
amount = Config.get('assignments_pdf_ballot_papers_number').value;
break;
default:
// should not happen.
amount = 0;
}
var tabledContent = [];
var rowsperpage;
var sheetend;
if (poll.pollmethod == 'votes') {
if (poll.options.length <= 4) {
sheetend = 105;
rowsperpage = 4;
} else if (poll.options.length <= 8) {
sheetend = 140;
rowsperpage = 3;
} else if (poll.options.length <= 12) {
sheetend = 210;
rowsperpage = 2;
}
else { //works untill ~30 people
sheetend = 418;
rowsperpage = 1;
}
} else {
if (poll.options.length <= 2) {
sheetend = 105;
rowsperpage = 4;
} else if (poll.options.length <= 4) {
sheetend = 140;
rowsperpage = 3;
} else if (poll.options.length <= 6) {
sheetend = 210;
rowsperpage = 2;
} else {
sheetend = 418;
rowsperpage = 1;
}
}
var page_entries = rowsperpage * 2;
var fullpages = Math.floor(amount / page_entries);
for (var i=0; i < fullpages; i++) {
tabledContent.push({
table: {
headerRows: 1,
widths: ['50%', '50%'],
body: createTableBody(rowsperpage, sheetend),
pageBreak: 'after'
},
layout: PDFLayout.getBallotLayoutLines(),
rowsperpage: rowsperpage
});
}
// fill the last page only partially
var lastpage_ballots = amount - (fullpages * page_entries);
if (lastpage_ballots < page_entries && lastpage_ballots > 0){
var partialpage = createTableBody(rowsperpage, sheetend, lastpage_ballots);
tabledContent.push({
table: {
headerRows: 1,
widths: ['50%', '50%'],
body: partialpage
},
layout: PDFLayout.getBallotLayoutLines(),
rowsperpage: rowsperpage
});
}
return tabledContent;
};
var getContent = function() {
return createContentTable();
};
var getImageMap = function () {
return imageMap;
};
return $q(function (resolve, reject) {
var imageSources = [
logoBallotPaperUrl,
];
ImageConverter.toBase64(imageSources).then(function (_imageMap) {
imageMap = _imageMap;
resolve({
getContent: getContent,
getImageMap: getImageMap,
});
}, reject);
});
};
return {
createInstance: createInstance
};
}
])
.factory('AssignmentCatalogContentProvider', [
'gettextCatalog',
'PDFLayout',
'Config',
function(gettextCatalog, PDFLayout, Config) {
var createInstance = function(allAssignments) {
var title = PDFLayout.createTitle(
Config.translate(Config.get('assignments_pdf_title').value)
);
var createPreamble = function() {
var preambleText = Config.get('assignments_pdf_preamble').value;
if (preambleText) {
return {
text: preambleText,
style: "preamble"
};
} else {
return "";
}
};
var createTOContent = function(assignmentTitles) {
var heading = {
text: gettextCatalog.getString("Table of contents"),
style: "heading2",
};
var toc = [];
_.forEach(assignmentTitles, function(title) {
toc.push({
text: title,
style: "tableofcontent"
});
});
return [
heading,
toc,
PDFLayout.addPageBreak()
];
};
var getContent = function() {
var content = [];
var assignmentContent = [];
var assignmentTitles = [];
_.forEach(allAssignments, function(assignment, key) {
assignmentTitles.push(assignment.title);
assignmentContent.push(assignment.getContent());
if (key < allAssignments.length - 1) {
assignmentContent.push(PDFLayout.addPageBreak());
}
});
content.push(title);
content.push(createPreamble());
content.push(createTOContent(assignmentTitles));
content.push(assignmentContent);
return content;
};
return {
getContent: getContent
};
};
return {
createInstance: createInstance
};
}
])
.factory('AssignmentPdfExport', [
'gettextCatalog',
'AssignmentContentProvider',
'AssignmentCatalogContentProvider',
'PdfMakeDocumentProvider',
'BallotContentProvider',
'PdfMakeBallotPaperProvider',
'PdfCreate',
'Messaging',
function (gettextCatalog, AssignmentContentProvider, AssignmentCatalogContentProvider,
PdfMakeDocumentProvider, BallotContentProvider, PdfMakeBallotPaperProvider, PdfCreate,
Messaging) {
return {
export: function (assignments, singleAssignment) {
var filename = singleAssignment ?
gettextCatalog.getString('Election') + '_' + assignments.title :
gettextCatalog.getString('Elections');
filename += '.pdf';
if (singleAssignment) {
assignments = [assignments];
}
// Convert the assignments to content providers
var assignmentContentProviderArray = _.map(assignments, function (assignment) {
return AssignmentContentProvider.createInstance(assignment);
});
var documentProviderPromise;
if (singleAssignment) {
documentProviderPromise =
PdfMakeDocumentProvider.createInstance(assignmentContentProviderArray[0]);
} else {
var assignmentCatalogContentProvider =
AssignmentCatalogContentProvider.createInstance(assignmentContentProviderArray);
documentProviderPromise =
PdfMakeDocumentProvider.createInstance(assignmentCatalogContentProvider);
}
documentProviderPromise.then(function (documentProvider) {
PdfCreate.download(documentProvider, filename);
}, function (error) {
Messaging.addMessage(error.msg, 'error');
});
},
createBallotPdf: function (assignment, pollId) {
var thePoll;
var pollNumber;
_.forEach(assignment.polls, function(poll, pollIndex) {
if (poll.id == pollId) {
thePoll = poll;
pollNumber = pollIndex+1;
}
});
var filename = gettextCatalog.getString('Ballot') + '_' + pollNumber + '_' + assignment.title + '.pdf';
BallotContentProvider.createInstance(assignment, thePoll, pollNumber).then(function (ballotContentProvider) {
var documentProvider = PdfMakeBallotPaperProvider.createInstance(ballotContentProvider);
PdfCreate.download(documentProvider, filename);
}, function (error) {
Messaging.addMessage(error.msg, 'error');
});
},
};
}
]);
}());

View File

@ -1,46 +0,0 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.assignments.projector', ['OpenSlidesApp.assignments'])
.config([
'slidesProvider',
function(slidesProvider) {
slidesProvider.registerSlide('assignments/assignment', {
template: 'static/templates/assignments/slide_assignment.html',
});
}
])
.controller('SlideAssignmentCtrl', [
'$scope',
'Assignment',
'AssignmentPoll',
'AssignmentPhases',
'AssignmentPollDecimalPlaces',
'User',
function($scope, Assignment, AssignmentPoll, AssignmentPhases, AssignmentPollDecimalPlaces, 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;
$scope.showResult = $scope.element.poll;
if ($scope.showResult) {
var poll = AssignmentPoll.get($scope.showResult);
$scope.votesPrecision = 0;
if (poll) {
AssignmentPollDecimalPlaces.getPlaces(poll, true).then(function (decimalPlaces) {
$scope.votesPrecision = decimalPlaces;
});
}
}
Assignment.bindOne(id, $scope, 'assignment');
$scope.phases = AssignmentPhases;
User.bindAll({}, $scope, 'users');
}
]);
}());

View File

@ -1,941 +0,0 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.assignments.site', [
'OpenSlidesApp.assignments',
'OpenSlidesApp.core.pdf',
'OpenSlidesApp.assignments.pdf',
'OpenSlidesApp.poll.majority'
])
.config([
'mainMenuProvider',
'gettext',
function (mainMenuProvider, gettext) {
mainMenuProvider.register({
'ui_sref': 'assignments.assignment.list',
'img_class': 'pie-chart',
'title': gettext('Elections'),
'weight': 400,
'perm': 'assignments.can_see'
});
}
])
.config([
'SearchProvider',
'gettext',
function (SearchProvider, gettext) {
SearchProvider.register({
'verboseName': gettext('Elections'),
'collectionName': 'assignments/assignment',
'urlDetailState': 'assignments.assignment.detail',
'weight': 400,
});
}
])
.config([
'$stateProvider',
'gettext',
function($stateProvider, gettext) {
$stateProvider
.state('assignments', {
url: '/assignments',
abstract: true,
template: "<ui-view/>",
data: {
title: gettext('Elections'),
basePerm: 'assignments.can_see',
},
})
.state('assignments.assignment', {
abstract: true,
template: "<ui-view/>",
})
.state('assignments.assignment.list', {})
.state('assignments.assignment.detail', {
controller: 'AssignmentDetailCtrl',
resolve: {
assignmentId: ['$stateParams', function($stateParams) {
return $stateParams.id;
}],
}
})
// redirects to assignment detail and opens assignment edit form dialog, uses edit url,
// used by ui-sref links from agenda only
// (from assignment controller use AssignmentForm factory instead to open dialog in front
// of current view without redirect)
.state('assignments.assignment.detail.update', {
onEnter: ['$stateParams', '$state', 'ngDialog',
function($stateParams, $state, ngDialog) {
ngDialog.open({
template: 'static/templates/assignments/assignment-form.html',
controller: 'AssignmentUpdateCtrl',
className: 'ngdialog-theme-default wide-form',
closeByEscape: false,
closeByDocument: false,
resolve: {
assignmentId: function() {
return $stateParams.id;
},
},
preCloseCallback: function() {
$state.go('assignments.assignment.detail', {assignment: $stateParams.id});
return true;
}
});
}
]
});
}
])
// Service for generic assignment form (create and update)
.factory('AssignmentForm', [
'gettextCatalog',
'operator',
'Editor',
'Mediafile',
'Tag',
'Assignment',
'Agenda',
'AgendaTree',
'ShowAsAgendaItemField',
function (gettextCatalog, operator, Editor, Mediafile, Tag, Assignment, Agenda, AgendaTree, ShowAsAgendaItemField) {
return {
// ngDialog for assignment form
getDialog: function (assignment) {
return {
template: 'static/templates/assignments/assignment-form.html',
controller: (assignment) ? 'AssignmentUpdateCtrl' : 'AssignmentCreateCtrl',
className: 'ngdialog-theme-default wide-form',
closeByEscape: false,
closeByDocument: false,
resolve: {
assignmentId: function () {return assignment ? assignment.id : void 0;}
},
};
},
// angular-formly fields for assignment form
getFormFields: function (isCreateForm) {
var images = Mediafile.getAllImages();
var formFields = [
{
key: 'title',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Title'),
required: true
}
},
{
key: 'description',
type: 'editor',
templateOptions: {
label: gettextCatalog.getString('Description')
},
data: {
ckeditorOptions: Editor.getOptions(images)
}
},
{
key: 'open_posts',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Number of persons to be elected'),
type: 'number',
min: 1,
required: true
}
},
{
key: 'poll_description_default',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Default comment on the ballot paper')
}
}];
// show as agenda item + parent item
if (isCreateForm) {
formFields.push(ShowAsAgendaItemField('assignments.can_manage'));
formFields.push({
key: 'agenda_parent_id',
type: 'select-single',
templateOptions: {
label: gettextCatalog.getString('Parent item'),
options: AgendaTree.getFlatTree(Agenda.getAll()),
ngOptions: 'item.id as item.getListViewTitle() for item in to.options | notself : model.agenda_item_id',
placeholder: gettextCatalog.getString('Select a parent item ...')
},
hide: !operator.hasPerms('agenda.can_manage')
});
}
// more (with tags field)
if (Tag.getAll().length > 0) {
formFields.push(
{
key: 'more',
type: 'checkbox',
templateOptions: {
label: gettextCatalog.getString('Show extended fields')
},
hide: !operator.hasPerms('assignments.can_manage')
},
{
template: '<hr class="smallhr">',
hideExpression: '!model.more'
},
{
key: 'tags_id',
type: 'select-multiple',
templateOptions: {
label: gettextCatalog.getString('Tags'),
options: Tag.getAll(),
ngOptions: 'option.id as option.name for option in to.options',
placeholder: gettextCatalog.getString('Select or search a tag ...')
},
hideExpression: '!model.more'
}
);
}
return formFields;
}
};
}
])
// Cache for AssignmentPollDetailCtrl so that users choices are keeped during user actions (e. g. save poll form).
.value('AssignmentPollDetailCtrlCache', {})
// Child controller of AssignmentDetailCtrl for each single poll.
.controller('AssignmentPollDetailCtrl', [
'$scope',
'MajorityMethodChoices',
'Config',
'AssignmentPollDetailCtrlCache',
'AssignmentPoll',
'AssignmentPollDecimalPlaces',
function ($scope, MajorityMethodChoices, Config, AssignmentPollDetailCtrlCache,
AssignmentPoll, AssignmentPollDecimalPlaces) {
// Define choices.
$scope.methodChoices = MajorityMethodChoices;
// TODO: Get $scope.baseChoices from config_variables.py without copying them.
$scope.votesPrecision = AssignmentPollDecimalPlaces.getPlaces($scope.poll);
// Setup empty cache with default values.
if (typeof AssignmentPollDetailCtrlCache[$scope.poll.id] === 'undefined') {
AssignmentPollDetailCtrlCache[$scope.poll.id] = {
method: $scope.config('assignments_poll_default_majority_method'),
};
}
// Fetch users choices from cache.
$scope.method = AssignmentPollDetailCtrlCache[$scope.poll.id].method;
$scope.recalculateMajorities = function (method) {
$scope.method = method;
_.forEach($scope.poll.options, function (option) {
option.majorityReached = option.isReached(method);
});
};
$scope.recalculateMajorities($scope.method);
$scope.saveDescriptionChange = function (poll) {
AssignmentPoll.save(poll);
};
// Save current values to cache on destroy of this controller.
$scope.$on('$destroy', function() {
AssignmentPollDetailCtrlCache[$scope.poll.id] = {
method: $scope.method,
};
});
}
])
.controller('AssignmentListCtrl', [
'$scope',
'ngDialog',
'AssignmentForm',
'Assignment',
'Tag',
'Agenda',
'Projector',
'ProjectionDefault',
'gettextCatalog',
'User',
'osTableFilter',
'osTableSort',
'osTablePagination',
'gettext',
'AssignmentPhases',
'AssignmentPdfExport',
function($scope, ngDialog, AssignmentForm, Assignment, Tag, Agenda, Projector,
ProjectionDefault, gettextCatalog, User, osTableFilter, osTableSort, osTablePagination,
gettext, AssignmentPhases, AssignmentPdfExport) {
$scope.$watch(function () {
return Assignment.lastModified();
}, function () {
$scope.assignments = _.orderBy(Assignment.getAll(), ['title']);
});
Tag.bindAll({}, $scope, 'tags');
$scope.$watch(function () {
return Projector.lastModified();
}, function () {
var projectiondefault = ProjectionDefault.filter({name: 'assignments'})[0];
if (projectiondefault) {
$scope.defaultProjectorId = projectiondefault.projector_id;
}
});
$scope.phases = AssignmentPhases;
$scope.alert = {};
// Filtering
$scope.filter = osTableFilter.createInstance('AssignmentTableFilter');
if (!$scope.filter.existsStorageEntry()) {
$scope.filter.multiselectFilters = {
tag: [],
phase: [],
};
}
$scope.filter.propertyList = ['title', 'description'];
$scope.filter.propertyFunctionList = [
function (assignment) {
return gettextCatalog.getString($scope.phases[assignment.phase].display_name);
},
];
$scope.filter.propertyDict = {
'assignment_related_users': function (candidate) {
return candidate.user.get_short_name();
},
'tags': function (tag) {
return tag.name;
},
};
$scope.getItemId = {
tag: function (assignment) {return assignment.tags_id;},
phase: function (assignment) {return assignment.phase;},
};
// Sorting
$scope.sort = osTableSort.createInstance('AssignmentTableSort');
if (!$scope.sort.column) {
$scope.sort.column = 'title';
}
$scope.sortOptions = [
{name: 'agenda_item.getItemNumberWithAncestors()',
display_name: gettext('Item')},
{name: 'title',
display_name: gettext('Title')},
{name: 'phase',
display_name: gettext('Phase')},
{name: 'assignment_related_users.length',
display_name: gettext('Number of candidates')},
];
$scope.hasTag = function (assignment, tag) {
return _.indexOf(assignment.tags_id, tag.id) > -1;
};
$scope.toggleTag = function (assignment, tag) {
if ($scope.hasTag(assignment, tag)) {
assignment.tags_id = _.filter(assignment.tags_id, function (tag_id){
return tag_id != tag.id;
});
} else {
assignment.tags_id.push(tag.id);
}
Assignment.save(assignment);
};
// Pagination
$scope.pagination = osTablePagination.createInstance('AssignmentTablePagination');
// update phase
$scope.updatePhase = function (assignment, phase_id) {
assignment.phase = phase_id;
Assignment.save(assignment);
};
// open new/edit dialog
$scope.openDialog = function (assignment) {
ngDialog.open(AssignmentForm.getDialog(assignment));
};
// *** select mode functions ***
$scope.isSelectMode = false;
// check all checkboxes
$scope.checkAll = function () {
$scope.selectedAll = !$scope.selectedAll;
angular.forEach($scope.assignments, function (assignment) {
assignment.selected = $scope.selectedAll;
});
};
// uncheck all checkboxes if isSelectMode is closed
$scope.uncheckAll = function () {
if (!$scope.isSelectMode) {
$scope.selectedAll = false;
angular.forEach($scope.assignments, function (assignment) {
assignment.selected = false;
});
}
};
// delete all selected assignments
$scope.deleteMultiple = function () {
angular.forEach($scope.assignments, function (assignment) {
if (assignment.selected)
Assignment.destroy(assignment.id);
});
$scope.isSelectMode = false;
$scope.uncheckAll();
};
// delete single assignment
$scope.delete = function (assignment) {
Assignment.destroy(assignment.id);
};
// create the PDF List
$scope.pdfExport = function () {
AssignmentPdfExport.export($scope.assignmentsFiltered);
};
}
])
.controller('AssignmentDetailCtrl', [
'$scope',
'$http',
'$filter',
'$timeout',
'filterFilter',
'gettext',
'ngDialog',
'AssignmentForm',
'operator',
'Assignment',
'User',
'assignmentId',
'Projector',
'ProjectionDefault',
'gettextCatalog',
'AssignmentPhases',
'AssignmentPdfExport',
'WebpageTitle',
'ErrorMessage',
function($scope, $http, $filter, $timeout, filterFilter, gettext, ngDialog, AssignmentForm, operator,
Assignment, User, assignmentId, Projector, ProjectionDefault, gettextCatalog, AssignmentPhases,
AssignmentPdfExport, WebpageTitle, ErrorMessage) {
User.bindAll({}, $scope, 'users');
var assignment = Assignment.get(assignmentId);
Assignment.loadRelations(assignment, 'agenda_item');
// This flag is for setting 'activeTab' to recently added (last) ballot tab.
// Set this flag, if ballots are added/removed. When the next autoupdate comes
// in, the tabset will be updated.
var updateBallotTabsFlag = true;
$scope.$watch(function () {
return Projector.lastModified();
}, function () {
var projectiondefault = ProjectionDefault.filter({name: 'assignments'})[0];
if (projectiondefault) {
$scope.defaultProjectorId = projectiondefault.projector_id;
}
});
$scope.$watch(function () {
return Assignment.lastModified(assignmentId);
}, function () {
// setup sorting of candidates
$scope.relatedUsersSorted = $filter('orderBy')(assignment.assignment_related_users, 'weight');
$scope.assignment = Assignment.get(assignment.id);
if (updateBallotTabsFlag) {
$scope.activeTab = $scope.assignment.polls.length - 1;
updateBallotTabsFlag = false;
}
WebpageTitle.updateTitle(gettextCatalog.getString('Election') + ' ' + $scope.assignment.title);
});
$scope.candidateSelectBox = {};
$scope.phases = AssignmentPhases;
$scope.alert = {};
// open edit dialog
$scope.openDialog = function () {
ngDialog.open(AssignmentForm.getDialog($scope.assignment));
};
// add (nominate) candidate
$scope.addCandidate = function (userId) {
$http.post('/rest/assignments/assignment/' + assignmentId + '/candidature_other/', {'user': userId})
.then(function (success){
$scope.alert.show = false;
$scope.candidateSelectBox = {};
}, function (error){
$scope.alert = ErrorMessage.forAlert(error);
$scope.candidateSelectBox = {};
});
};
// remove candidate
$scope.removeCandidate = function (userId) {
$http.delete('/rest/assignments/assignment/' + assignmentId + '/candidature_other/',
{headers: {'Content-Type': 'application/json'},
data: JSON.stringify({user: userId})})
.then(function (success) {},
function (error) {
$scope.alert = ErrorMessage.forAlert(error);
}
);
};
// add me (nominate self as candidate)
$scope.addMe = function () {
$http.post('/rest/assignments/assignment/' + assignmentId + '/candidature_self/', {}).then(
function (success) {
$scope.alert.show = false;
}, function (error) {
$scope.alert = ErrorMessage.forAlert(error);
}
);
};
// remove me (withdraw own candidature)
$scope.removeMe = function () {
$http.delete('/rest/assignments/assignment/' + assignmentId + '/candidature_self/').then(
function (success) {
$scope.alert.show = false;
}, function (error) {
$scope.alert = ErrorMessage.forAlert(error);
}
);
};
// check if current user is already a candidate (elected==false)
$scope.isCandidate = function () {
var check = $scope.assignment.assignment_related_users.map(function(candidate) {
if (!candidate.elected) {
return candidate.user_id;
}
}).indexOf(operator.user.id);
if (check > -1) {
return true;
} else {
return false;
}
};
// Sort all candidates
$scope.treeOptions = {
dropped: function () {
var sortedCandidates = [];
_.forEach($scope.relatedUsersSorted, function (user) {
sortedCandidates.push(user.id);
});
$http.post('/rest/assignments/assignment/' + assignmentId + '/sort_related_users/',
{related_users: sortedCandidates}
);
}
};
// update phase
$scope.updatePhase = function (phase_id) {
$scope.assignment.phase = phase_id;
Assignment.save($scope.assignment);
};
// create new ballot
$scope.createBallot = function () {
$http.post('/rest/assignments/assignment/' + assignmentId + '/create_poll/').then(
function (success) {
$scope.alert.show = false;
if (assignment.phase === 0) {
$scope.updatePhase(1);
}
updateBallotTabsFlag = true;
}, function (error) {
$scope.alert = ErrorMessage.forAlert(error);
}
);
};
// delete ballot
$scope.deleteBallot = function (poll) {
poll.DSDestroy().then(
function (success) {
$scope.activeTab = $scope.activeTab - 1;
updateBallotTabsFlag = true;
}
);
};
// edit poll dialog
$scope.editPollDialog = function (poll, ballot) {
ngDialog.open({
template: 'static/templates/assignments/assignmentpoll-form.html',
controller: 'AssignmentPollUpdateCtrl',
className: 'ngdialog-theme-default',
closeByEscape: false,
closeByDocument: false,
resolve: {
assignmentpollId: function () {return poll.id;},
ballot: function () {return ballot;},
}
});
};
// publish ballot
$scope.togglePublishBallot = function (poll) {
poll.DSUpdate({
assignment_id: assignmentId,
published: !poll.published,
})
.then(function (success) {
$scope.alert.show = false;
}, function (error) {
$scope.alert = ErrorMessage.forAlert(error);
});
};
// mark candidate as (not) elected
$scope.markElected = function (user, reverse) {
if (reverse) {
$http.delete(
'/rest/assignments/assignment/' + assignmentId + '/mark_elected/', {
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify({user: user})
});
} else {
$http.post('/rest/assignments/assignment/' + assignmentId + '/mark_elected/', {'user': user});
}
};
// Creates the document as pdf
$scope.pdfExport = function() {
AssignmentPdfExport.export($scope.assignment, true);
};
// Creates the ballotpaper as pdf
$scope.ballotpaperExport = function(pollId) {
AssignmentPdfExport.createBallotPdf($scope.assignment, pollId);
};
// Just mark some vote value strings for translation.
gettext('Yes');
gettext('No');
gettext('Abstain');
}
])
.controller('AssignmentCreateCtrl', [
'$scope',
'$state',
'Assignment',
'AssignmentForm',
'Agenda',
'Config',
'ErrorMessage',
function($scope, $state, Assignment, AssignmentForm, Agenda, Config, ErrorMessage) {
$scope.model = {
agenda_type: parseInt(Config.get('agenda_new_items_default_visibility').value),
};
// set default value for open posts form field
$scope.model.open_posts = 1;
// get all form fields
$scope.formFields = AssignmentForm.getFormFields(true);
// save assignment
$scope.save = function(assignment, gotoDetailView) {
Assignment.create(assignment).then(
function (success) {
if (gotoDetailView) {
$state.go('assignments.assignment.detail', {id: success.id});
}
$scope.closeThisDialog();
},
function (error) {
$scope.alert = ErrorMessage.forAlert(error);
}
);
};
}
])
.controller('AssignmentUpdateCtrl', [
'$scope',
'$state',
'Assignment',
'AssignmentForm',
'Agenda',
'assignmentId',
'ErrorMessage',
function($scope, $state, Assignment, AssignmentForm, Agenda, assignmentId, ErrorMessage) {
var assignment = Assignment.get(assignmentId);
$scope.alert = {};
// set initial values for form model by create deep copy of assignment object
// so list/detail view is not updated while editing
$scope.model = angular.copy(assignment);
// get all form fields
$scope.formFields = AssignmentForm.getFormFields();
// save assignment
$scope.save = function (assignment, gotoDetailView) {
// inject the changed assignment (copy) object back into DS store
Assignment.inject(assignment);
// save changed assignment object on server
Assignment.save(assignment).then(
function(success) {
if (gotoDetailView) {
$state.go('assignments.assignment.detail', {id: success.id});
}
$scope.closeThisDialog();
},
function (error) {
// save error: revert all changes by restore
// (refresh) original assignment object from server
Assignment.refresh(assignment);
$scope.alert = ErrorMessage.forAlert(error);
}
);
};
}
])
.controller('AssignmentPollUpdateCtrl', [
'$scope',
'$filter',
'gettextCatalog',
'AssignmentPoll',
'assignmentpollId',
'AssignmentPollDecimalPlaces',
'ballot',
'ErrorMessage',
function($scope, $filter, gettextCatalog, AssignmentPoll, assignmentpollId,
AssignmentPollDecimalPlaces, ballot, ErrorMessage) {
// set initial values for form model by create deep copy of assignmentpoll object
// so detail view is not updated while editing poll
var assignmentpoll = angular.copy(AssignmentPoll.get(assignmentpollId));
$scope.model = assignmentpoll;
$scope.ballot = ballot;
$scope.formFields = [];
$scope.alert = {};
// For number inputs
var step = Math.pow(10, -AssignmentPollDecimalPlaces.getPlaces(assignmentpoll));
// add dynamic form fields
var options = $filter('orderBy')(assignmentpoll.options, 'weight');
_.forEach(options, function(option) {
var defaultValue;
if (assignmentpoll.pollmethod == 'yna' || assignmentpoll.pollmethod == 'yn') {
defaultValue = {};
_.forEach(option.votes, function (vote) {
defaultValue[vote.value.toLowerCase()] = vote.weight;
});
var columnClass = (assignmentpoll.pollmethod === 'yna') ? 'col-xs-4' : 'col-xs-6';
var columns = [
{
key: 'yes_' + option.candidate_id,
type: 'input',
className: columnClass + ' no-padding-left',
templateOptions: {
label: gettextCatalog.getString('Yes'),
type: 'number',
min: -2,
step: step,
required: true
},
defaultValue: defaultValue.yes
},
{
key: 'no_' + option.candidate_id,
type: 'input',
className: columnClass + ' no-padding-left' +
(assignmentpoll.pollmethod === 'yn' ? ' no-padding-right' : ''),
templateOptions: {
label: gettextCatalog.getString('No'),
type: 'number',
min: -2,
step: step,
required: true
},
defaultValue: defaultValue.no
}
];
if (assignmentpoll.pollmethod == 'yna'){
columns.push({
key:'abstain_' + option.candidate_id,
type: 'input',
className: columnClass + ' no-padding-left no-padding-right',
templateOptions: {
label: gettextCatalog.getString('Abstain'),
type: 'number',
min: -2,
step: step,
required: true
},
defaultValue: defaultValue.abstain
});
}
$scope.formFields.push({
noFormControl: true,
template: '<strong>' + option.candidate.get_full_name() + '</strong>'
},
{
className: 'row',
fieldGroup: columns,
});
} else { // votes method
if (option.votes.length) {
defaultValue = option.votes[0].weight;
}
$scope.formFields.push({
key: 'vote_' + option.candidate_id,
type: 'input',
templateOptions: {
label: option.candidate.get_full_name(),
type: 'number',
min: -2,
step: step,
required: true,
},
defaultValue: defaultValue
});
}
});
if (assignmentpoll.pollmethod == 'votes'){
$scope.formFields.push(
{
key: 'votesabstain',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Abstain'),
type: 'number',
step: step,
min: -2,
}
},
{
key: 'votesno',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('No'),
type: 'number',
step: step,
min: -2,
}
}
);
}
// add general form fields
$scope.formFields.push(
{
template: '<hr class="smallhr">',
},
{
key: 'votesvalid',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Valid ballots'),
type: 'number',
step: step,
min: -2,
}
},
{
key: 'votesinvalid',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Invalid ballots'),
type: 'number',
step: step,
min: -2,
}
},
{
key: 'votescast',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Casted ballots'),
type: 'number',
step: step,
min: -2,
}
}
);
// save assignmentpoll
$scope.save = function (poll) {
var votes = [];
if (assignmentpoll.pollmethod == 'yna') {
assignmentpoll.options.forEach(function(option) {
votes.push({
'Yes': poll['yes_' + option.candidate_id],
'No': poll['no_' + option.candidate_id],
'Abstain': poll['abstain_' + option.candidate_id]
});
});
} else if (assignmentpoll.pollmethod == 'yn') {
assignmentpoll.options.forEach(function(option) {
votes.push({
'Yes': poll['yes_' + option.candidate_id],
'No': poll['no_' + option.candidate_id]
});
});
} else {
assignmentpoll.options.forEach(function(option) {
votes.push({
'Votes': poll['vote_' + option.candidate_id],
});
});
}
// save change poll object on server
poll.DSUpdate({
assignment_id: poll.assignment_id,
votes: votes,
votesabstain: poll.votesabstain,
votesno: poll.votesno,
votesvalid: poll.votesvalid,
votesinvalid: poll.votesinvalid,
votescast: poll.votescast,
})
.then(function(success) {
$scope.alert.show = false;
$scope.closeThisDialog();
}, function (error) {
$scope.alert = ErrorMessage.forAlert(error);
});
};
}
])
//mark all assignment config strings for translation with Javascript
.config([
'gettext',
function (gettext) {
gettext('Election method');
gettext('Automatic assign of method');
gettext('Always one option per candidate');
gettext('Always Yes-No-Abstain per candidate');
gettext('Always Yes/No per candidate');
gettext('Elections');
gettext('Ballot and ballot papers');
gettext('The 100-%-base of an election result consists of');
gettext('For Yes/No/Abstain per candidate and Yes/No per candidate the 100-%-base ' +
'depends on the election method: If there is only one option per candidate, ' +
'the sum of all votes of all candidates is 100 %. Otherwise for each ' +
'candidate the sum of all votes is 100 %.');
gettext('Yes/No/Abstain per candidate');
gettext('Yes/No per candidate');
gettext('All valid ballots');
gettext('All casted ballots');
gettext('Disabled (no percents)');
gettext('Number of ballot papers (selection)');
gettext('Number of all delegates');
gettext('Number of all participants');
gettext('Use the following custom number');
gettext('Custom number of ballot papers');
gettext('Required majority');
gettext('Default method to check whether a candidate has reached the required majority.');
gettext('Simple majority');
gettext('Two-thirds majority');
gettext('Three-quarters majority');
gettext('Disabled');
gettext('Put all candidates on the list of speakers');
gettext('Title for PDF document (all elections)');
gettext('Preamble text for PDF document (all elections)');
//other translations
gettext('Searching for candidates');
gettext('Voting');
gettext('Finished');
}
]);
}());

View File

@ -1,299 +0,0 @@
<div class="header">
<div class="title">
<div class="submenu">
<a ui-sref="assignments.assignment.list" class="btn btn-sm btn-default">
<i class="fa fa-angle-double-left fa-lg"></i>
<translate>Back to overview</translate>
</a>
<!-- List of speakers -->
<a ui-sref="agenda.item.detail({id: assignment.agenda_item_id})"
os-perms="agenda.can_see" class="btn btn-sm btn-default">
<i class="fa fa-microphone fa-lg"></i>
<translate>List of speakers</translate>
</a>
<!-- project -->
<projector-button model="assignment", default-projector-id="defaultProjectorId"
title="{{ 'Project' | translate }}">
</projector-button>
<!-- edit -->
<a os-perms="assignments.can_manage" ng-click="openDialog()"
class="btn btn-default btn-sm"
title="{{ 'Edit' | translate}}">
<i class="fa fa-pencil"></i>
</a>
<!-- pdf -->
<a ng-click="pdfExport()" target="_blank" class="btn btn-default btn-sm"
title="{{ 'Export as PDF' | translate}}">
<i class="fa fa-file-pdf-o fa-lg"></i>
<translate>PDF</translate>
</a>
</div>
<h1>{{ assignment.agenda_item.getTitle() || assignment.title }}</h1>
<h2>
<translate>Election</translate>
</h2>
</div>
</div>
<div class="meta">
<div class="title" ng-click="isMeta = !isMeta">
<div class="name">
<i class="fa fa-info-circle"></i>
<translate>Meta information</translate>
</div>
<div class="icon">
<i class="fa fa-lg" ng-class="isMeta ? 'fa-angle-down' : 'fa-angle-up'"></i>
</div>
</div>
<div class="detail" uib-collapse="isMeta">
<div class="row">
<div class="col-md-6">
<!-- posts -->
<h3 translate>Number of persons to be elected</h3>
{{ assignment.open_posts }}<br>
<!-- Tags -->
<h3 ng-if="assignment.tags.length > 0" translate>Tags</h3>
<span ng-repeat="tag in assignment.tags">
{{ tag.name }}{{$last ? '' : ', '}}
</span>
</div>
<div class="col-md-6">
<!-- phase -->
<h3 translate>Phase</h3>
<span class="label" ng-class="{'label-primary': assignment.phase == 0,
'label-warning': assignment.phase == 1,
'label-success': assignment.phase == 2 }">
{{ phases[assignment.phase].display_name | translate }}
</span>
<div os-perms="assignments.can_manage" class="spacer">
<select ng-model="phaseSelect" class="form-control" ng-change="updatePhase(phaseSelect)">
<option value="" translate>--- Set phase ---</option>
<option ng-repeat="phase in phases" value="{{ phase.value }}">{{ phase.display_name | translate }}</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="details">
<div ng-if="assignment.description" class="clearfix">
<h3 translate>Description</h3>
<div ng-bind-html="assignment.description | trusted"></div>
</div>
<div ng-if="assignment.phase !== 2">
<h3 translate>Candidates</h3>
<div ui-tree="treeOptions" ng-if="assignment.assignment_related_users.length"
data-empty-placeholder-enabled="false">
<ol ui-tree-nodes="" ng-model="relatedUsersSorted">
<li ui-tree-node ng-repeat="related_user in assignment.assignment_related_users | orderBy:'weight'">
<i ui-tree-handle="" class="fa fa-arrows-v spacer-right" os-perms="assignments.can_manage"></i>
<a class="spacer-right" ui-sref="users.user.detail({id: related_user.user_id})">
{{ related_user.user.get_full_name() }}
</a>
<i ng-if="related_user.elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
<a href os-perms="assignments.can_manage" ng-click="removeCandidate(related_user.user_id)" class="btn btn-default btn-xs">
<i class="fa fa-times"></i>
</a>
</ol>
</div>
<div class="form-group spacer-top-lg">
<div uib-alert ng-show="alert.show" ng-class="'alert-' + (alert.type || 'warning')" ng-click="alert={}" close="alert={}">
{{ alert.msg }}
</div>
<div os-perms="assignments.can_nominate_other">
<select chosen ng-model="candidateSelectBox.selected" ng-change="addCandidate(candidateSelectBox.selected)"
ng-options="user.id as user.get_full_name() for user in users"
search-contains="true"
placeholder-text-single="'Select or search a participant ...' | translate"
no-results-text="'No results available ...' | translate"
class="form-control">
<select>
</div>
<p os-perms="assignments.can_nominate_self" class="spacer">
<button ng-if="!isCandidate()" ng-click="addMe()" class="btn btn-default btn-sm">
<i class="fa fa-plus"></i>
<translate>Add me</translate>
</button>
<button ng-if="isCandidate()" ng-click="removeMe()" class="btn btn-default btn-sm">
<i class="fa fa-minus"></i>
<translate>Remove me</translate>
</button>
</div>
</div>
<h3 translate>Election result</h3>
<template-hook hook-name="assignmentPollVotingHeader"></template-hook>
<template-hook hook-name="assignmentPollNewBallotButton">
<button os-perms="assignments.can_manage" ng-show="assignment.phase !== 2" ng-click="createBallot()"
class="btn btn-default btn-sm">
<i class="fa fa-bar-chart fa-lg"></i>
<translate>New ballot</translate>
</button>
</template-hook>
<uib-tabset ng-if="assignment.polls.length > 0" class="spacer ballot-tabs" active="$parent.activeTab">
<uib-tab ng-repeat="poll in assignment.polls | orderBy:'id'"
index="$index" heading="{{ 'Ballot' | translate }} {{ $index + 1 }}">
<div ng-controller="AssignmentPollDetailCtrl">
<!-- action buttons -->
<div class="pull-right" os-perms="assignments.can_manage" ng-show="assignment.phase !== 2">
<span class="spacer-right" editable-text="poll.description" onaftersave="saveDescriptionChange(poll)"
uib-tooltip="{{ 'Hint on the ballot paper.' | translate }}">
<span ng-if="!poll.description" translate>Set hint for ballot paper ...</span>
<span ng-if="poll.description">{{ poll.description }}</span>
<i class="fa fa-pencil"></i>
</span>
<!-- delete -->
<a class="btn btn-danger btn-xs"
ng-bootbox-confirm="{{ 'Are you sure you want to delete this ballot?' | translate }}"
ng-bootbox-confirm-action="deleteBallot(poll)">
<i class="fa fa-trash"></i>
<translate>Delete</translate>
</a>
</div>
<div os-perms="assignments.can_manage" class="spacer" role="group">
<!-- angular requires to open the link in new tab with "target='_blank'".
Otherwise the pdf url can't be open in same window; angular redirects to "/". -->
<!-- PDF -->
<a ng-click="ballotpaperExport(poll.id)" target="_blank" class="btn btn-default btn-sm">
<i class="fa fa-file-pdf-o"></i>
<translate>Print ballot paper</translate>
</a>
<!-- Edit -->
<button ng-click="assignment.phase !== 2 && editPollDialog(poll, $index+1)"
ng-class="{ 'disabled': assignment.phase === 2 }"
class="btn btn-default btn-sm">
<i class="fa fa-pencil"></i>
<translate>Enter votes</translate>
</button>
<!-- Publish -->
<button ng-click="togglePublishBallot(poll)"
ng-class="poll.published ? 'btn-primary' : 'btn-default'"
class="btn btn-sm">
<i class="fa fa-eye"></i>
<translate>Publish</translate>
</button>
<!-- Project -->
<projector-button model="assignment" default-projector-id="defaultProjectorId"
arg="poll.id" content="{{ 'Project' | translate }}">
</projector-button>
</div>
<!-- template hook for assignment poll small buttons -->
<template-hook hook-name="assignmentPollSmallButtons"></template-hook>
<div class="results spacer-top-lg">
<!-- list of candidates of selected poll (without election result) -->
<div ng-if="!poll.has_votes">
<strong translate>Candidates</strong>
<ul class="list-unstyled">
<li ng-repeat="option in poll.options | orderBy:'weight'">
<a ui-sref="users.user.detail({id: option.candidate.id})">
{{ option.candidate.get_full_name() }}
</a>
</ul>
</div>
<!-- Settings for majority calculations -->
<div os-perms="assignments.can_manage" ng-show="poll.has_votes" ng-cloak>
<div class="input-group">
<span><translate>Required majority</translate>: </span>
<select ng-model="method"
ng-change="recalculateMajorities(method)"
ng-options="option.value as option.display_name | translate for option in methodChoices" />
</div>
</div>
<!-- election result of poll -->
<table ng-if="poll.has_votes" class="table table-bordered table-striped minimumTable spacer">
<tr>
<th translate>Candidates
<th translate>Votes
<th translate ng-hide="method === 'disabled'">Quorum
</th>
<!-- candidates (poll options) -->
<tr ng-repeat="option in poll.options | orderBy:'weight'">
<!-- candidate name -->
<td>
<span os-perms="assignments.can_manage">
<i ng-if="option.is_elected" class="fa fa-check-square-o"
ng-click="markElected(option.candidate_id, option.is_elected)"
title="{{ 'is elected' | translate }}"></i>
<i ng-if="!option.is_elected" class="fa fa-square-o"
ng-click="markElected(option.candidate_id, option.is_elected)"
title="{{ 'is not elected' | translate }}"></i>
&nbsp;
</span>
<a ui-sref="users.user.detail({id: option.candidate.id})">{{ option.candidate.get_full_name() }}</a>
<!-- votes -->
<td>
<div ng-init="votes = option.getVotes()">
<div ng-repeat="vote in votes">
<span ng-if="poll.pollmethod == 'yna' || poll.pollmethod == 'yn'">{{ vote.label }}:</span>
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
<div ng-if="vote.percentNumber >= 0">
<uib-progressbar ng-if="$index == 0" value="vote.percentNumber" type="success"></uib-progressbar>
<uib-progressbar ng-if="$index == 1" value="vote.percentNumber" type="danger"></uib-progressbar>
<uib-progressbar ng-if="$index == 2" value="vote.percentNumber" type="warning"></uib-progressbar>
</div>
</div>
</div>
<td ng-hide="method === 'disabled'">
<span ng-if="option.majorityReached >= 0" class="text-success" translate>
Quorum ({{ (option.getVoteYes() - option.majorityReached) | number:votesPrecision }}) reached.
</span>
<span ng-if="option.majorityReached < 0" class="text-danger" translate>
Quorum ({{ (option.getVoteYes() - option.majorityReached) | number:votesPrecision }}) not reached.
</span>
<!-- total votes (valid/invalid/casts) -->
<tr ng-if="poll.pollmethod === 'votes'">
<td>
<translate>Abstain</translate>
<td>
{{ poll.getVote('votesabstain').value | number:votesPrecision }}
{{ poll.getVote('votesabstain').percentStr }}
<tr ng-if="poll.pollmethod === 'votes'">
<td>
<translate>No</translate>
<td>
{{ poll.getVote('votesno').value | number:votesPrecision }}
{{ poll.getVote('votesno').percentStr }}
<tr>
<td>
<translate>Valid ballots</translate>
<td>
{{ poll.getVote('votesvalid').value | number:votesPrecision }}
{{ poll.getVote('votesvalid').percentStr }}
<tr>
<td>
<translate>Invalid ballots</translate>
<td>
{{ poll.getVote('votesinvalid').value | number:votesPrecision }}
{{ poll.getVote('votesinvalid').percentStr }}
<tr class="total bg-info">
<td>
<translate>Casted ballots</translate>
<td>
{{ poll.getVote('votescast').value | number:votesPrecision }}
{{ poll.getVote('votescast').percentStr }}
</table>
<strong translate>Election method</strong><br>
<span ng-if="poll.pollmethod == 'votes'" translate>One vote per candidate</span>
<span ng-if="poll.pollmethod == 'yna'" translate>Yes/No/Abstain per candidate</span>
<span ng-if="poll.pollmethod == 'yn'" translate>Yes/No per candidate</span>
</div>
</div>
</uib-tab>
</uib-tabset>
<!-- Workaround to prevent page scrolling up after autoupdate. -->
<div style="height: 700px"></div>
</div>
<template-hook hook-name="assignmentDetailViewDetailContainer"></template-hook>

View File

@ -1,22 +0,0 @@
<h1 ng-if="model.id" translate>Edit election</h1>
<h1 ng-if="!model.id" translate>New election</h1>
<div uib-alert ng-show="alert.show" ng-class="'alert-' + (alert.type || 'warning')" ng-click="alert={}" close="alert={}">
{{ alert.msg }}
</div>
<form name="assignmentForm" ng-submit="save(model, gotoDetailView)">
<formly-form model="model" fields="formFields">
<hr class="smallhr">
<div class="checkbox pointer" ng-click="$parent.$parent.gotoDetailView = !$parent.$parent.gotoDetailView">
<i class="fa" ng-class="$parent.$parent.gotoDetailView ? 'fa-check-square-o' : 'fa-square-o'"></i>
<translate>Open election detail view after save.</translate>
</div>
<button type="submit" ng-disabled="assignmentForm.$invalid" class="btn btn-primary" translate>
Save
</button>
<button type="button" ng-click="closeThisDialog()" class="btn btn-default" translate>
Cancel
</button>
</formly-form>
</form>

View File

@ -1,348 +0,0 @@
<div class="header">
<div class="title">
<div class="submenu">
<a ng-click="openDialog()" os-perms="assignments.can_manage" class="btn btn-primary btn-sm">
<i class="fa fa-plus fa-lg"></i>
<translate>New</translate>
</a>
<a ui-sref="core.tag.list" os-perms="core.can_manage_tags" class="btn btn-default btn-sm">
<i class="fa fa-tags fa-lg"></i>
<translate>Tags</translate>
</a>
<template-hook hook-name="assignmentListMenuButton"></template-hook>
</div>
<h1 translate>Elections</h1>
</div>
</div>
<div class="details">
<div class="row form-group">
<div class="col-sm-12">
<!-- select mode -->
<button os-perms="assignments.can_manage" class="btn btn-sm"
ng-class="$parent.isSelectMode ? 'btn-primary' : 'btn-default'"
ng-click="$parent.isSelectMode = !$parent.isSelectMode; uncheckAll()">
<i class="fa fa-check-square-o"></i>
<translate>Select ...</translate>
</button>
<!-- export button -->
<button type="button" class="btn btn-default btn-sm pull-right" ng-click="pdfExport()">
<i class="fa fa-file-pdf-o"></i>
<span ng-if="assignmentsFiltered.length == assignments.length" translate>
Export all
</span>
<span ng-if="assignmentsFiltered.length != assignments.length" translate>
Export filtered
</span>
</button>
</div>
</div>
<div uib-collapse="!isFilterOpen" class="row">
<div class="col-sm-6 text-right"></div>
<div class="col-sm-6 text-right">
<!-- phase filter -->
<select ng-model="phaseFilter" class="form-control" id="phaseFilter">
<option value="" translate>--- Select phase ---</option>
<option ng-repeat="phase in phases" value="{{ phase.value }}">{{ phase.display_name | translate }}</option>
</select>
</div>
</div>
<div uib-collapse="!isSelectMode" class="row spacer">
<div class="col-sm-12 text-left">
<!-- delete button -->
<a ng-show="isSelectMode" os-perms="assignments.can_manage"
ng-bootbox-confirm="{{ 'Are you sure you want to delete all selected elections?' | translate }}"
ng-bootbox-confirm-action="deleteMultiple()"
class="btn btn-default btn-sm btn-danger">
<i class="fa fa-trash fa-lg"></i>
<translate>Delete selected elections</translate>
</a>
</div>
</div>
<div class="spacer-top-lg italic row">
<div class="col-md-6">
{{ assignmentsFiltered.length }} /
{{ assignments.length }} {{ "elections" | translate }}<span ng-if="(assignments|filter:{selected:true}).length > 0">,
{{(assignments|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
</div>
<div class="col-md-6" ng-show="assignmentsFiltered.length > pagination.itemsPerPage">
<span class="pull-right">
<a href="" class="pagination-arrow" ng-click="pagination.prevPage()"
ng-if="pagination.showPrevPageArrow()">
&laquo;
</a>
<translate>Page</translate> {{ pagination.currentPage }} /
{{ pagination.getPageCount(assignmentsFiltered) }}
<a href="" class="pagination-arrow" ng-click="pagination.nextPage(assignmentsFiltered)"
ng-if="pagination.showNextPageArrow(assignmentsFiltered)">
&raquo;
</a>
</span>
</div>
</div>
<div class="os-table container-fluid">
<div class="row header-row">
<div class="col-xs-1 centered" ng-show="isSelectMode">
<i class="fa text-danger pointer" ng-class="selectedAll ? 'fa-check-square-o' : 'fa-square-o'"
ng-click="checkAll()"></i>
</div>
<div class="col-xs-11 main-header">
<span class="form-inline text-right pull-right">
<!-- clear all filters -->
<span class="sort-spacer pointer" ng-click="filter.reset(isSelectMode)"
ng-if="filter.areFiltersSet()" ng-disabled="isSelectMode"
ng-class="{'disabled': isSelectMode}">
<i class="fa fa-window-close"></i>
<translate>Filter</translate>
</span>
<!-- Tag filter -->
<span uib-dropdown ng-if="tags.length > 0">
<span class="pointer" id="dropdownTag" uib-dropdown-toggle
ng-class="{'bold': filter.multiselectFilters.tag.length > 0, 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Tag</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right"
aria-labelledby="dropdownTag">
<li ng-repeat="tag in tags">
<a href ng-click="filter.operateMultiselectFilter('tag', tag.id, isSelectMode)">
<i class="fa fa-check" ng-if="filter.MultiselectFilters.tag.indexOf(tag.id) > -1"></i>
{{ tag.name }}
</a>
</li>
<li class="divider"></li>
<li>
<a href ng-click="filter.operateMultiselectFilter('tag', -1, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.tag.indexOf(-1) > -1"></i>
<translate>No tag set</translate>
</a>
</li>
</ul>
</span>
<!-- Phase filter -->
<span uib-dropdown>
<span class="pointer" id="dropdownPhase" uib-dropdown-toggle
ng-class="{'bold': filter.multiselectFilters.phase.length > 0, 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Phase</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right"
aria-labelledby="dropdownPhase">
<li ng-repeat="phase in phases">
<a href ng-click="filter.operateMultiselectFilter('phase', phase.value, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.phase.indexOf(phase.value) > -1"></i>
{{ phase.display_name | translate }}
</a>
</li>
</ul>
</span>
<!-- dropdown sort -->
<span uib-dropdown>
<span class="pointer" id="dropdownSort" uib-dropdown-toggle
ng-class="{'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Sort</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownSort">
<!-- all sortOptions -->
<li ng-repeat="option in sortOptions">
<a ng-click="sort.toggle(option.name)">
<span ng-style="{'font-weight': sort.column === option.name ? 'bold' : 'normal'}">
{{ option.display_name | translate }}
</span>
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sort.column === option.name ? 'visible' : 'hidden'}"
ng-class="sort.reverse ? 'fa-sort-amount-desc' : 'fa-sort-amount-asc'">
</i>
</a>
</li>
</ul>
</span>
<!-- search field -->
<span class="form-group">
<span class="input-group">
<span class="input-group-addon"><i class="fa fa-search"></i></span>
<input type="text" ng-model="filter.filterString" class="form-control"
placeholder="{{ 'Search' | translate}}" ng-disable="isSelectMode"
ng-change="filter.save()">
</span>
</span>
</span>
<!-- show all selected multiselectoptions -->
<span ng-repeat="tag in tags" class="pointer spacer-left-lg"
ng-if="filter.multiselectFilters.tag.indexOf(tag.id) > -1"
ng-click="filter.operateMultiselectFilter('tag', tag.id, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ tag.name }}
</span>
</span>
<span ng-if="filter.multiselectFilters.tag.indexOf(-1) > -1" class="pointer spacer-left-lg"
ng-click="filter.operateMultiselectFilter('tag', -1, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<i class="fa fa-times-circle"></i>
<translate>No tag set</translate>
</span>
<span ng-repeat="phase in phases" class="pointer spacer-left-lg"
ng-if="filter.multiselectFilters.phase.indexOf(phase.value) > -1"
ng-click="filter.operateMultiselectFilter('phase', phase.value, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ phase.display_name | translate }}
</span>
</span>
<span>
</span>
</div>
</div>
<!-- main table -->
<div class="row data-row" ng-mouseover="assignment.hover=true"
ng-mouseleave="assignment.hover=false"
ng-class="{'projected': assignment.isProjected().length,
'related-projected': assignment.isRelatedProjected().length}"
ng-repeat="assignment in assignmentsFiltered = (assignments
| osFilter: filter.filterString : filter.getObjectQueryString
| MultiselectFilter: filter.multiselectFilters.tag : getItemId.tag
| MultiselectFilter: filter.multiselectFilters.phase : getItemId.phase
| orderByEmptyLast: sort.column : sort.reverse)
| limitTo : pagination.itemsPerPage : pagination.limitBegin">
<!-- select column -->
<div ng-show="isSelectMode" os-perms="assignments.can_manage" class="col-xs-1 centered">
<i class="fa text-danger pointer" ng-click="assignment.selected=!assignment.selected"
ng-class="assignment.selected ? 'fa-check-square-o' : 'fa-square-o'"></i>
</div>
<!-- projector column -->
<div class="col-xs-1 centered projector" os-perms="core.can_manage_projector">
<projector-button model="assignment", default-projector-id="defaultProjectorId">
</projector-button>
</div>
<div class="no-projector-spacer" os-perms="!core.can_manage_projector"></div>
<!-- main content column -->
<div class="col-xs-6 content">
<div class="title-col">
<!-- title and phase -->
<div>
<a class="title" ui-sref="assignments.assignment.detail({id: assignment.id})">
{{ assignment.title }}
</a>
<span style="padding: 5px;" ng-mouseover="assignment.phaseHover=true" ng-mouseleave="assignment.phaseHover=false">
<span class="label" ng-class="{'label-primary': assignment.phase == 0,
'label-warning': assignment.phase == 1,
'label-success': assignment.phase == 2 }">
{{ phases[assignment.phase].display_name | translate }}
</span>
<span os-perms="assignments.can_manage" ng-class="{'hiddenDiv': !assignment.phaseHover}" uib-dropdown>
<i class="fa fa-cog pointer" uib-dropdown-toggle id="phaseDropdown{{ assignment.id }}"></i>
<ul uib-dropdown-menu aria-labelledby="phaseDropdown{{ assignment.id }}"
class="dropdown-menu">
<li ng-repeat="phase in phases">
<a href>
<i class="fa fa-check" ng-if="assignment.phase == phase.value"></i>
<span class="pointer" ng-click="updatePhase(assignment, phase.value)">{{ phase.display_name | translate }}</a>
</a>
</li>
</ul>
</span>
</span>
</div>
<!-- hover menu -->
<div os-perms="assignments.can_manage" ng-class="{'hiddenDiv': !assignment.hover}">
<small>
<a href="" ng-click="openDialog(assignment)" translate>Edit</a> &middot;
<a href="" class="text-danger"
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
<b>{{ assignment.title }}</b>"
ng-bootbox-confirm-action="delete(assignment)" translate>Delete</a>
</small>
</div>
</div>
</div>
<!-- additional content column -->
<div class="col-xs-4 content" ng-style="{'width': isSelectMode ? 'calc(50% - 120px)' : 'calc(50% - 70px)'}">
<div style="width: 60%;" class="optional">
<small>
<!-- Tag dropdown for manage user -->
<div os-perms="assignments.can_manage" ng-show="tags.length > 0"
ng-mouseover="assignment.tagHover=true"
ng-mouseleave="assignment.tagHover=false">
<span uib-dropdown>
<span id="dropdownTags{{ assignment.id }}" class="pointer"
uib-dropdown-toggle uib-tooltip="{{ 'Add a tag' | translate }}"
tooltip-class="nobr">
<span ng-if="assignment.tags.length == 0" ng-show="assignment.hover">
<i class="fa fa-tags"></i>
<i class="fa fa-plus"></i>
</span>
<span ng-if="assignment.tags.length > 0">
<i class="fa fa-tags"></i>
<span ng-repeat="tag in assignment.tags">
{{ tag.name }}<span ng-if="!$last">,</span>
</span>
<i class="fa fa-cog fa-lg spacer-left" ng-show="assignment.tagHover"></i>
</span>
</span>
<ul class="dropdown-menu" aria-labelledby="dropdownTags{{ assignment.id }}">
<li ng-repeat="tag in tags">
<a href ng-click="toggleTag(assignment, tag)">
<i class="fa fa-check" ng-if="hasTag(assignment, tag)"></i>
{{ tag.name }}
</a>
</li>
</ul>
</span>
</div>
<!-- Tag string for normal user -->
<div os-perms="!assignments.can_manage" ng-show="assignment.tags.length > 0">
<i class="fa fa-tags spacer-right"></i>
<span ng-repeat="tag in assignment.tags">
{{ tag.name }}<span ng-if="!$last">,</span>
</span>
</div>
<template-hook hook-name="assignmentListAdditionalContentColumn"></template-hook>
</small>
</div>
<div style="width: 20%;" class="pull-right nobr optional centered">
<span class="badge"
uib-tooltip="{{ assignment.assignment_related_users.length }} {{ 'Candidates' | translate }}"
tooltip-class="nobr"
ng-class="{'badge-info': assignment.assignment_related_users.length < assignment.open_posts}">
{{ assignment.assignment_related_users.length }}
</span>
</div>
<div style="width: 20%;" class="pull-right nobr">
<div class="centered">{{ assignment.agenda_item.getItemNumberWithAncestors() }}</div>
</div>
</div>
</div> <!-- main table -->
<ul uib-pagination
ng-show="assignmentsFiltered.length > pagination.itemsPerPage"
total-items="assignmentsFiltered.length"
items-per-page="pagination.itemsPerPage"
ng-model="pagination.currentPage"
ng-change="pagination.pageChanged()"
class="pagination-sm"
direction-links="false"
boundary-links="true"
first-text="&laquo;"
last-text="&raquo;">
</ul>
</div> <!-- end container -->
</div>

View File

@ -1,22 +0,0 @@
<h1><translate>Ballot</translate> {{ ballot }}</h1>
<div uib-alert ng-show="alert.show" ng-class="'alert-' + (alert.type || 'warning')" ng-click="alert={}" close="alert={}">
{{ alert.msg }}
</div>
<p>
<translate>Special values</translate>:
<span class="badge badge-success">-1</span> = <translate>majority</translate>
<span class="badge">-2</span> = <translate>undocumented</translate>
<form name="assignmentPollForm" ng-submit="save(model)">
<formly-form model="model" fields="formFields">
<template-hook hook-name="assignmentPollFormButtons"></template-hook>
<button type="submit" ng-disabled="assignmentPollForm.$invalid" class="btn btn-primary" translate>
Save
</button>
<button ng-click="closeThisDialog()" class="btn btn-default" translate>
Cancel
</button>
</formly-form>
</form>

View File

@ -1,101 +0,0 @@
<div ng-controller="SlideAssignmentCtrl" class="content scrollcontent">
<!-- Title -->
<div id="title">
<h1>{{ assignment.agenda_item.getTitle() || assignment.title }}</h1>
<h2>
<translate>Election</translate>
</h2>
</div>
<!-- Description -->
<div ng-if="!showResult && assignment.description !== ''"
ng-bind-html="assignment.description | trusted"
class="zoomcontent clearfix">
</div>
<!-- Candidates -->
<div class="zoomcontent" ng-if="!showResult">
<h3 translate>Candidates</h3>
<ul>
<li ng-repeat="related_user in assignment.assignment_related_users | orderBy:'weight'">
{{ related_user.user.get_full_name() }}
<i ng-if="related_user.elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
</ul>
</div>
<!-- vote results -->
<div class="zoomcontent" ng-show="showResult">
<div ng-if="(assignment.polls | filter: {id: element.poll}).length == 0" translate>
Waiting for results ...
</div>
<div ng-repeat="poll in assignment.polls | filter: {id: element.poll}" class="electionresults spacer">
<table class="table table-bordered table-striped minimumTable">
<tr>
<th translate>Candidates
<th ng-if="poll.has_votes" class="col-sm-5" translate>Votes</th>
<!-- candidates (poll options) -->
<tr ng-repeat="option in poll.options | orderBy:'weight'">
<!-- candidate name -->
<td class="bold">
<i ng-if="option.is_elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
{{ option.candidate.get_full_name() }}
<!-- votes -->
<td ng-if="poll.has_votes" class="bold">
<div ng-init="votes = option.getVotes()">
<div ng-show="poll.pollmethod == 'yna' || poll.pollmethod == 'yn'">
<span ng-show="poll.pollmethod == 'yna'">
{{ votes[0].label | translate }}: {{ votes[0].value | number:votesPrecision }} {{ votes[0].percentStr }}<br>
{{ votes[1].label | translate }}: {{ votes[1].value | number:votesPrecision }} {{ votes[1].percentStr }}<br>
{{ votes[2].label | translate }}: {{ votes[2].value | number:votesPrecision }} {{ votes[2].percentStr }}</span>
<span ng-show="poll.pollmethod == 'yn'">
{{ votes[0].label | translate }}: {{ votes[0].value | number:votesPrecision }} {{ votes[0].percentStr }}<br>
{{ votes[1].label | translate }}: {{ votes[1].value | number:votesPrecision }} {{ votes[1].percentStr }}</span>
</div>
<div ng-show="poll.pollmethod == 'votes'">
<div ng-repeat="vote in votes">
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
<div style="float:right; width:200px;" ng-if="vote.percentNumber >= 0">
<uib-progressbar value="vote.percentNumber" type="success"></uib-progressbar>
</div>
</div>
</div>
</div>
<!-- total votes (abstain/no/valid/invalid/casts) -->
<tr class="total" ng-if="poll.has_votes && poll.pollmethod === 'votes' && poll.getVote('votesabstain').value !== null">
<td>
<translate>Abstain</translate>
<td ng-init="vote = poll.getVote('votesabstain')">
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
<tr class="total" ng-if="poll.has_votes && poll.pollmethod === 'votes' && poll.getVote('votesno').value !== null">
<td>
<translate>No</translate>
<td ng-init="vote = poll.getVote('votesno')">
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
<tr class="total" ng-if="poll.has_votes && poll.getVote('votesvalid').value !== null">
<td>
<translate>Valid ballots</translate>
<td ng-init="vote = poll.getVote('votesvalid')">
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
<tr class="total" ng-if="poll.has_votes && poll.getVote('votesinvalid').value !== null">
<td>
<translate>Invalid ballots</translate>
<td ng-init="vote = poll.getVote('votesinvalid')">
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
<tr class="total bg-info" ng-if="poll.has_votes && poll.getVote('votescast').value !== null">
<td>
<translate>Casted ballots</translate>
<td ng-init="vote = poll.getVote('votescast')">
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
</table>
</div>
</div>
</div>

View File

@ -1,66 +0,0 @@
import os
from typing import Any, Dict
from django.conf import settings
from django.contrib.staticfiles.management.commands.collectstatic import (
Command as CollectStatic,
)
from django.contrib.staticfiles.utils import matches_patterns
from django.core.management.base import CommandError
from django.db.utils import OperationalError
from ...views import WebclientJavaScriptView
class Command(CollectStatic):
"""
Custom collectstatic command.
"""
realms = ['site', 'projector']
js_filename = 'webclient-{}.js'
def handle(self, **options: Any) -> str:
if options['link']:
raise CommandError("Option 'link' is not supported.")
try:
self.view = WebclientJavaScriptView()
except OperationalError:
raise CommandError('You have to run OpenSlides first to create a ' +
'database before collecting staticfiles.')
return super().handle(**options)
def collect(self) -> Dict[str, Any]:
result = super().collect()
try:
destination_dir = os.path.join(settings.STATIC_ROOT, 'js')
except IndexError:
# If the user does not want do have staticfiles, he should not get
# the webclient files either.
pass
else:
if self.dry_run:
self.log('Pretending to write WebclientJavaScriptView for all realms.', level=1)
else:
if not os.path.exists(destination_dir):
os.makedirs(destination_dir)
for realm in self.realms:
filename = self.js_filename.format(realm)
# Matches only the basename.
if matches_patterns(filename, self.ignore_patterns):
continue
path = os.path.join(destination_dir, filename)
if matches_patterns(path, self.ignore_patterns):
continue
content = self.view.get(realm=realm).content
with open(path, 'wb+') as f:
f.write(content)
message = "Written WebclientJavaScriptView for realm {} to '{}'".format(
realm,
path)
self.log(message, level=1)
result['modified'].append(path)
return result

View File

@ -1,28 +0,0 @@
/*
* Font definitions for OpenSlides site
*/
@font-face {
font-family: $font;
src: $font-src;
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: $font-medium;
src: $font-medium-src;
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: $font-condensed;
src: $font-condensed-src;
font-weight: 100;
font-style: normal;
}
@font-face {
font-family: $font-condensed-light;
src: $font-condensed-light-src;
font-weight: 100;
font-style: normal;
}

View File

@ -1,255 +0,0 @@
/** General helper classes **/
.disabled {
color: #555;
cursor: not-allowed !important;
}
.bold {
font-weight: bold;
}
.dropdown-menu {
margin-left: 0px !important;
max-height: 300px;
overflow-y: auto;
}
.slimDropDown {
padding-left: 4px !important;
padding-right: 4px !important;
}
.btn-primary {
background-color: #317796;
}
.btn-slim {
padding-left: 6px;
padding-right: 6px;
}
.spacer, .spacer-top {
margin-top: 7px;
}
.spacer-top-lg {
margin-top: 25px;
}
.spacer-bottom {
margin-bottom: 7px;
}
.spacer-right {
margin-right: 5px;
}
.spacer-left {
margin-left: 5px;
}
.spacer-left-lg {
margin-left: 10px;
}
.no-padding {
padding: 0px;
}
.lead-div {
margin-bottom: 20px;
.lead {
margin-bottom: 0;
}
}
.italic {
font-style: italic;
}
.hoverActions {
font-size: 85%;
}
.hiddenDiv {
visibility: hidden;
}
.vcenter {
vertical-align: middle;
}
.white-space-pre-line {
white-space: pre-line;
}
.slimlabel.label {
padding: 0px 10px;
}
.indentation, .indentation20 {
margin-left: 20px !important;
}
.indentation-lg {
margin-left: 35px !important;
}
.halfWidth {
width: 50%;
}
.badge-info {
background-color: #f0ad4e;
}
.badge-success {
background-color: #5bb85b;
}
.smallhr {
margin-top: 5px;
margin-bottom: 5px;
border-color: #cccccc;
}
.nobr {
white-space: nowrap;
}
.rotate-45-deg-right {
filter: "progid: DXImageTransform.Microsoft.BasicImage(rotation=0.5)";
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.optional { /* show optional column */
display: auto;
}
.inline {
display: inline;
}
.login-logo {
width: 250px;
}
.loginForm .input-group-addon i {
width: 15px;
}
.modal-header {
padding: 5px;
}
.no-padding {
padding: 0 !important;
}
.no-padding-left {
padding-left: 0 !important;
}
.no-padding-right {
padding-right: 0 !important;
}
.scroll-x-container {
overflow-x: auto;
}
.btn-group > label > input[type=radio] {
display: none;
}
/* tables */
.topictext table {
border-spacing: 10px 0;
border-collapse: separate;
}
.minimum {
width: 1px;
}
.minimumTable {
width: auto;
}
.deleteColumn {
text-align: center;
background-color: #ff9999 !important;
}
th.sortable:hover, tr.pointer:hover, .pointer {
cursor: pointer;
}
/** background colors for table rows **/
tr.hiddenrow td {
background-color: #F5DCDC;
}
tr.activeline td, li.activeline, .projected {
background-color: #bed4de !important;
}
.related-projected {
background-color: #e1ecfe !important;
}
tr.selected td {
background-color: #ff9999;
}
.help-block {
font-size: 85%;
margin-top: 2px;
margin-left: 2px;
}
.pagination-arrow {
font-size: 150%;
padding: 3px;
text-decoration: none;
&:hover, &:focus {
text-decoration: none;
}
}
.grey {
color: gray;
}
.info {
color: #222;
background-color: #bed4de;
border-color: #46b8da;
}
.error {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}
.success {
color: #3c763d;
background-color: #dff0d8;
border-color: #d6e9c6;
}
.warning {
color: #725523;
background-color: #fcf8e3;
border-color: #faebcc;
}
.close {
opacity: 0.3 !important;
}
.close:hover {
opacity: 0.6 !important;
}

View File

@ -1,115 +0,0 @@
/*////////////////////////////////////////
=MEDIA QUERIES (RESPONSIVE DESIGN)
////////////////////////////////////////*/
/* display for 900 to 1200px resolutions */
@media only screen and (max-width: 1200px) {
body { font-size: 12px; }
h1 { font-size: 30px; padding-bottom: 6px; }
h2 { font-size: 22px; padding-bottom: 10px; }
h3 { font-size: 18px; padding-bottom: 10px; }
.col-md-4 {
float: left;
width: 50%;
}
.motion-text.line-numbers-outside .os-line-number:after {
font-size: 10px;
top: 9px;
}
}
/* display for resolutions smaller that 900px */
@media only screen and (max-width: 900px) {
#nav .navbar li a { padding: 24px 5px; }
#multi-table .info-head {
width: 200px;
&.small {
width: 250px;
}
}
/* hide text in multi-table earlier */
#multi-table .optional { display: none; }
/* show replacement elements, if any */
#multi-table .optional-show { display: block !important; }
/* hide searchbar input */
#nav .searchbar input { display: none !important; }
#nav .searchbar .btn {
border-top-left-radius: 4px !important;
border-bottom-left-radius: 4px !important;
}
}
/* display for resolutions smaller that 760px */
@media only screen and (max-width: 760px) {
h1 { font-size: 22px; padding-bottom: 4px; }
h2 { font-size: 16px; padding-bottom: 4px; }
h3 { font-size: 14px; padding-bottom: 4px; }
.user i { font-size: 16px; padding: 3px; }
#nav .navbar { box-shadow: none; padding-right: 60px !important; }
#nav .navbar ul li a { padding: 10px 15px; }
#nav .searchbar { margin: 15px -53px 0 0 !important; }
#chatbox { width: 100%; top: 40px; }
.badge { font-size: 10px; }
/* show replacement elements, if any */
.optional-show { display: block !important; }
/* hide marked element / column */
.optional, .hide-sm { display: none !important; }
}
/* display for resolutions smaller that 560px */
@media only screen and (max-width: 560px) {
#content .containerOS { padding: 0; }
#content .col2 { width: 100%; }
#content .col2.sidebar-max, #content .col2.sidebar-min,
#content .col1.sidebar-min, #content .col1.sidebar-max {
width: 100%;
}
#sidebar {
display: none !important;
}
#sidebar-xs {
display: block !important;
}
#sidebar-xs .projector_full {
margin-left: 0 !important;
width: 100%;
}
#multi-table .info-head {
width: 150px;
&.small {
width: 100px;
}
}
.personalNoteFixed {
width: 100%;
}
}

View File

@ -1,34 +0,0 @@
#wrapper {
float: left;
width: 100%;
margin: 0 auto 0 auto;
}
#startup-overlay {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: #fff;
z-index: 900;
}
#spinner-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
max-height: 340px;
z-index: 1000;
div {
width: 100%;
position: absolute;
bottom: 0;
font-size: 56px;
text-align: center;
color: #317796;
}
}

View File

@ -1,169 +0,0 @@
/* override booststrap's label class to fix linebreak and add spacing */
.label {
display: inline-block;
padding: .4em .6em;
margin-right: .2em;
white-space: normal;
}
/* Custom OpenSlides slider classes */
.os-slider {
margin-top: 40px;
margin-bottom: 20px;
&.rzslider {
.rz-bar {
background: #317796;
opacity: 0.3;
height: 2px;
}
.rz-selection {
background: #317796;
}
.rz-pointer {
width: 14px;
height: 14px;
top: -7px;
bottom: 0;
background-color: #317796;
border-radius: 7px;
&:after {
display: none;
}
}
}
}
/* ngDialog: override ngdialog-theme-default */
.ngdialog.ngdialog-theme-default {
padding-top: 40px;
.ngdialog-content {
font-family: $font, sans-serif !important;
}
h2 {
margin-top: 5px;
}
label {
font-family: $font-medium;
font-family: $font-medium, sans-serif;
font-weight: normal;
}
.row:after, .row:before {
display: table-cell !important;
}
}
.ngdialog.ngdialog-theme-default.wide-form .ngdialog-content {
width: 750px;
line-height: 1em;
}
/* Bootbox: override z-index, that bootboxes are higher than ngDialogs */
.bootbox {
z-index: 100000;
}
/* angular-chosen: override default width of select fields in quickmode */
.chosen-container {
width: 100% !important;
font-size: 14px;
ul.chosen-results {
margin: 0 !important;
li.active-result {
margin: 6px 5px 0 0 !important;
}
}
input {
margin: 4px 0 !important;
}
}
.chosen-container-active .chosen-choices {
border-color: rgba(82,168,236,.8);
box-shadow: 0 0 8px rgba(82,168,236,.6);
}
/* angular-ui-tree style */
.angular-ui-tree-handle {
background: none repeat scroll 0 0 #f8faff;
border: 1px solid #dae2ea;
color: #7c9eb2;
padding: 10px;
&:hover {
background: none repeat scroll 0 0 #f4f6f7;
border-color: #dce2e8;
color: #438eb9;
}
}
.angular-ui-tree-placeholder {
background: none repeat scroll 0 0 #f0f9ff;
border: 2px dashed #bed2db;
box-sizing: border-box;
}
/* ngAnimate classes */
.animate-item.ng-enter {
-webkit-animation: fade-in 0.5s linear;
animation: fade-in 0.5s linear;
}
.animate-item.ng-leave {
-webkit-animation: fade-out 0.25s linear;
animation: fade-out 0.25s linear;
}
/* xeditable */
.editable-click {
border: none;
cursor: pointer;
color: #555;
&:hover {
color: #555;
}
}
.popover-wrapper {
.editable-hide {
display: inline !important;
}
.small-form {
input{
width: 100px;
height: 30px;
}
button{
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
}
}
}
@keyframes fade-out {
0% { opacity: 1; background: none; }
100% { opacity: 0; background: none; }
}
@keyframes fade-in {
0% { opacity: 0; background: none; }
100% { opacity: 1; background: none; }
}
/* Angular formly */
.checkbox label {
padding-left: 0;
}

View File

@ -1,10 +0,0 @@
/** Fonts **/
/* Note: The font naming has to be consistent to the projector.html */
$font: 'OSFont';
$font-src: url('../fonts/Roboto-Regular.woff') format('woff');
$font-medium: 'OSFont Medium';
$font-medium-src: url('../fonts/Roboto-Medium.woff') format('woff');
$font-condensed: 'OSFont Condensed';
$font-condensed-src: url('../fonts/Roboto-Condensed-Regular.woff') format('woff');
$font-condensed-light: 'OSFont Condensed Light';
$font-condensed-light-src: url('../fonts/Roboto-Condensed-Light.woff') format('woff');

View File

@ -1,19 +0,0 @@
/* Chatbox */
#chatbox {
position: fixed;
top: 40px;
right: 0;
width: 40%;
border-color: #dddddd;
border-width: 1px;
box-shadow: -5px 5px 5px rgba(0, 0, 0, 0.2);
height: 234px;
padding: 0 10px 10px 10px;
z-index: 5;
}
#chatbox-text {
overflow-y: scroll;
height: 190px;
color: #222;
}

View File

@ -1,28 +0,0 @@
/** Config **/
#content .col1 #config {
.comments > div {
margin-bottom: 5px;
}
.config-checkbox {
padding: 6px 12px;
}
.custom-translations {
> div {
margin-bottom: 5px;
padding-right: 15px;
width: 100%;
}
.inputs {
input {
width: 47%;
}
.arrow {
width: 6%;
float: left;
text-align: center;
}
}
}
}

View File

@ -1,32 +0,0 @@
/** Countdown fullscreen mode **/
#countdownWrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
background-color: white;
}
#countdownWrapper > div {
text-align: center;
margin-top: 50px;
}
#countdown {
display: inline-block;
padding: 20px 50px;
border-radius: 8px;
font-size: 30vw;
line-height: 1;
font-weight: bold;
}
#countdown .description {
font-weight: normal;
font-size: 5vw;
}
.warning_time {
color: #ed940d;
}
.negative {
color: #CC0000;
}

View File

@ -1,28 +0,0 @@
/* for csv import form */
#content .col1 #csv-import {
margin-left: 15px;
width: 35%;
.file-select {
input {
display: none;
}
label {
font-weight: normal;
cursor: pointer;
}
}
.clear-file {
color: #555;
}
.help-block {
padding-bottom: 0;
}
.help-block-big {
font-size: 100%;
}
}

View File

@ -1,20 +0,0 @@
/** Goto top link **/
#goto-top {
position: fixed;
bottom: 15px;
right: 30px;
padding: 10px 30px;
background: white;
opacity: 0.8;
transition: opacity 250ms ease-out;
z-index: 100;
&:hover {
opacity: 1;
transition: opacity 250ms ease-in;
}
a:hover {
text-decoration: none;
}
}

View File

@ -1,31 +0,0 @@
/** manage-projectors **/
#manage-projectors {
> div {
display: inline-table;
width: 256px;
margin: 10px 20px 35px 10px;
> div {
margin-bottom: 10px;
}
}
.middle {
width: 100%;
> div {
margin-left: auto;
margin-right: auto;
display: table;
}
}
.dropdown button {
width: 100%;
}
.resolution {
display: inline-block;
width: 120px;
}
}

View File

@ -1,20 +0,0 @@
/** Messaging status bar **/
#messaging {
position: fixed;
bottom: 0;
width: 100%;
z-index: 100000;
}
#messaging-container {
margin: 0 auto 0 auto;
padding: 0px 20px;
max-width: 1400px;
> div {
margin-bottom: 10px;
padding: 10px 20px;
border-radius: 6px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
}

View File

@ -1,47 +0,0 @@
/* multi list */
#multi-table {
table-layout: fixed;
text-align: center;
thead tr th {
vertical-align: top;
text-align: center;
min-width: 40px;
overflow: hidden;
}
.info-head {
width: 300px;
&.small {
width: 200px;
}
}
tbody tr:hover {
background-color: #f5f5f5 !important;
}
tbody tr.bg-grey {
background-color: #f9f9f9;
}
tbody tr td .no-overflow{
overflow: hidden;
}
tbody tr td:first-child {
text-align: left;
}
tbody tr td div {
text-align: center;
}
.optional-show { /* hide optional-show column */
display: none;
}
.editable-click {
color: #000;
}
}

View File

@ -1,107 +0,0 @@
/** OS-Table **/
#content .col1 .os-table {
font-weight: normal;
small {
color: #555;
}
.row {
border: 1px solid #ddd;
border-top: 0px;
.col-xs-1 {
width: 50px;
}
.col-xs-4 {
padding-right: 10px;
}
.col-space {
padding: 5px 7px 5px 7px;
}
// TODO: Isn't this defined in the _helper.scss?
.centered {
text-align: center;
}
}
.data-row:hover {
background-color: #f5f5f5;
}
.data-row > div {
padding: 10px 0px 10px 0px;
}
.id-col {
width: 50px;
min-height: 1px;
word-wrap: break-word;
}
.id-col-space {
width: calc(100% - 50px);
}
.no-projector-spacer {
margin-right: 20px;
float: left;
}
.header-row {
border-top: 1px solid #ddd;
background-color: #f5f5f5;
}
.header-row > div {
padding: 10px;
}
.main-header {
width: calc(100% - 50px);
float: right;
.form-inline {
margin-left: 15px;
}
}
.content > div { /* horizontal blocks */
display: inline-block;
float: left;
}
.content > div > div { /* vertival blocks */
margin-bottom: 3px;
}
.content > div > div:last-child {
margin-bottom: 0px;
}
.projector {
width: 70px !important;
}
.header-row .dropdown > span, .sort-spacer {
padding: 5px 10px 5px 10px;
}
.title {
font-family: $font-medium;
font-family: $font-medium, sans-serif;
font-weight: normal;
font-size: 120%;
margin-right: 3px;
padding: 0;
background-color: transparent;
}
.dropdown-hover-space {
padding: 5px 5px 5px 0;
}
}

View File

@ -1,43 +0,0 @@
/* ProjectorContainer */
.pContainer {
background-color: #222;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
display: table;
& > div {
display: table-cell;
vertical-align: middle;
}
#iframe {
-moz-transform-origin: 0 0;
-webkit-transform-origin: 0 0;
-o-transform-origin: 0 0;
transform-origin: 0 0 0;
}
#iframewrapper, .error {
position: relative;
overflow: hidden;
margin-left: auto;
margin-right: auto;
}
.error > p {
color: #f00;
font-size: 150%;
text-align: center;
}
#iframeoverlay {
position: absolute;
top: 0px;
left: 0px;
display: block;
z-index: 1;
}
}

View File

@ -1,31 +0,0 @@
/* Pojector sidebar */
.col2 .projectorSelector {
margin-top: 10px;
margin-bottom: 10px;
& > div {
margin-bottom: 10px;
& > div {
width: 65%;
padding-right: 5px;
float: left;
margin-bottom: 10px;
& > button {
width: 100%;
overflow: hidden;
}
}
}
.manageBtn {
width: 35%;
}
.btn-group {
margin-left: auto;
margin-right: auto;
display: table;
}
}

View File

@ -1,269 +0,0 @@
@import "projector-container";
/* Template styles for OpenSlides projector */
html, body {
height: 100%;
}
body {
font-family: $font, sans-serif;
font-size: 20px !important;
line-height: 24px !important;
overflow: hidden;
color: #222;
}
h1, h2, h3, h4, h5, h6 {
font-size: inherit;
font-family: $font-condensed, sans-serif;
font-weight: normal;
}
h1 {
font-size: 2.25em;
line-height: 1.1em;
margin-bottom: 30px;
}
h2 {
font-size: 28px;
margin-top: 15px;
display: block;
color: #9a9898;
}
h3 {
color: #222;
margin-bottom: 10px
}
ul, ol {
margin: 0 0 10px 0;
}
hr {
margin: 10px 0;
}
#header {
font-family: $font-condensed-light;
background-repeat: no-repeat;
background-size: 100% 100%;
box-shadow: 0 0 7px rgba(0,0,0,0.6);
height: 70px;
margin-bottom: 20px;
}
#logo {
position: relative;
left: 50px;
top: 10px;
height: 50px;
margin-right: 25px;
float: left;
}
#eventdata {
position: relative;
left: 50px;
top: 12px;
height: 50px;
overflow: hidden;
.title {
font-size: 26px;
font-weight: bold;
&.titleonly {
position: relative;
top: 12px;
}
}
.description {
font-size: 18px;
opacity: 0.8;
}
}
#currentTime {
font-family: $font-condensed-light;
border: 0 solid #000000;
font-size: 24px;
position: absolute;
text-align: right;
top: 23px;
right: 50px;
padding-left: 30px;
}
#footer {
font-family: $font-condensed-light;
position: fixed;
bottom: 0;
height: 35px;
width: 100%;
font-size: 16px;
padding-left: 50px;
padding-right: 50px;
padding-top: 5px;
overflow: hidden;
text-align: right;
span {
opacity: 0.8;
}
}
/* Content */
.content {
position: absolute;
left: 50px;
right: 50px;
z-index: -1;
line-height: 1.5em;
img {
max-width: 65%;
height: auto;
}
}
.scrollcontent {
transition-property: margin;
transition-duration: 1s;
}
.zoomcontent {
transition-property: font-size;
transition-duration: 1s;
}
.fullscreen {
position: fixed;
top: 0 !important;
left: 0;
width: 100%;
height: 100%;
z-index: 100;
background-color: black;
canvas {
margin: auto;
display: block !important;
}
}
#title {
border-bottom: 5px solid #d3d3d3;
margin-bottom: 40px;
h1 {
margin-bottom: 0;
padding-bottom: 0;
}
h2 {
margin-top: 10px;
margin-bottom: 5px;
}
}
/* Overlays: countdown and message */
.countdown {
min-width: 260px;
position: relative;
margin: 0 0 10px 10px;
top: 0px;
right: 0px;
padding: 26px 45px 7px 19px;
min-height: 72px;
font-family: $font-condensed, sans-serif;
font-size: 3.7em;
font-weight: bold;
text-align: right;
border-radius: 7px 7px 7px 7px;
z-index: 320;
.description {
font-weight: normal;
font-size: 18px;
margin-top: 20px;
padding-right: 6px;
}
}
.message_background {
background-color: #777777;
opacity: 0.8;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 300;
}
.message {
font-family: $font-condensed, sans-serif;
font-weight: normal;
position: fixed;
top: 35%;
left: 10%;
width: 80%;
text-align: center;
border-radius: 0.2em;
background: #FFFFFF;
font-size: 2.75em;
padding: 0.2em 0;
line-height: normal !important;
z-index: 350;
}
.identify {
background-color: #D9F8C4;
z-index: 400;
}
/* Table style */
.table {
border-collapse:collapse;
border-color:#CCCCCC -moz-use-text-color #CCCCCC #CCCCCC;
border-style:solid none solid solid;
border-width:1px medium 1px 1px;
margin:0;
border-spacing:0px;
th {
border-right:1px solid #CCCCCC;
color:#333333;
font-weight:normal;
padding:10px 10px 10px 10px;
text-align:left;
text-transform:uppercase;
}
tr.odd td {
background:none repeat scroll 0 0 #F1F1F1;
}
td {
background:none repeat scroll 0 0 #F7F7F7;
border-right:1px solid #CCCCCC;
line-height:120%;
padding: 10px 10px;
vertical-align:middle;
}
tr.total td {
border-top: 1px solid #333333;
background-color: #e3e3e3;
}
tr.elected td {
background-color: #BED4DE !important;
}
}
.transparentTable td {
background-color: transparent;
padding-right: 10px;
}

View File

@ -1,13 +0,0 @@
/* search results */
.searchresults li {
margin-bottom: 12px;
}
.searchresults h3 {
margin-bottom: 3px;
padding-bottom: 0;
}
.searchresults .hits {
margin-bottom: 10px;
color: #999999;
font-size: 85%;
}

View File

@ -1,612 +0,0 @@
/** Template styles for OpenSlides site **/
@import "config";
@import "search";
@import "os-table";
@import "multi-table";
@import "csv-import";
@import "chatbox";
@import "countdown";
@import "goto-top";
@import "messaging";
@import "projector-sidebar";
@import "manage-projectors";
* { /* set margin/padding for all (block) elements to null */
margin: 0;
padding: 0;
}
body {
font-size: 14px;
line-height: 1.5;
color: #222;
text-align: center;
background: #e8eaed;
font-family: $font, sans-serif !important;
}
div {
text-align: left;
}
img {
border: none;
}
a {
text-decoration: none;
color: #2b6883;
&:hover {
text-decoration: underline;
}
}
blockquote {
font-size: inherit;
}
h1, h2, h3, h4, h5, h6 {
line-height: 1.1;
font-family: $font-condensed, sans-serif;
font-weight: 400;
}
h1 {
font-size: 36px;
color: #317796;
padding-bottom: 8px;
}
h2 {
font-size: 22px;
color: #9a9898;
padding-bottom: 14px;
}
h3 {
font-size: 22px;
color: #222;
padding-bottom: 14px;
}
h4 {
font-size: 18px;
color: #222;
}
strong, b, th {
font-family: $font-medium;
font-family: $font-medium, sans-serif;
font-weight: normal;
}
.heading {
a {
color: #444;
&:hover {
color: #2b6883;
text-decoration: none;
}
}
}
/** Header **/
#header {
float: left;
width: 100%;
height: 40px;
background: #002c42;
color: #999;
a.headerlink {
text-decoration: none;
color: #999;
}
a.headerlink:hover, a.headerlink:active, a.headerlink.active {
color: #e8eaed;
}
.title {
float: left;
margin: 2px 0 0 -5px;
color: #fff;
font-family: $font-condensed, sans-serif;
font-weight: 400;
}
.user {
float: right;
margin: 5px 0 0 0;
height: 100%;
.inline {
display: inline;
}
}
}
#nav {
float: left;
width: 100%;
height: auto;
background: #317796;
color: #fff;
overflow-y: hidden;
.navbar {
width: 100%;
border: none;
margin: 0;
}
.navbar-toggle {
padding: 5px 0;
z-index: 2;
i {
font-size: 28px;
color: #fff;
opacity: 0.5;
}
&:hover i {
opacity: 1;
}
}
.navbar-collapse {
padding: 0;
}
.navbar ul {
list-style: none;
margin: 0;
li {
float: left;
text-align: center;
a {
display: block;
color: #fff;
padding: 22px 15px;
text-decoration: none;
&:hover {
background: url('/static/img/nav_dark-bg.png');
text-decoration: none;
}
}
i {
font-size: 28px;
display: block;
margin-bottom: 6px;
margin-right: 0;
opacity: 0.5;
}
}
li.active {
background: url('/static/img/nav_dark-bg.png');
a {
background: url('/static/img/nav_active.png') no-repeat bottom;
}
i {
opacity: 1;
}
}
}
.searchbar {
float: right;
margin-top: 33px;
margin-bottom: 32px;
display: inline-table;
input {
width: 150px;
}
.btn {
background: #e8eaed;
color: #555;
}
}
}
#header .containerOS, #nav .containerOS, #content .containerOS {
max-width: 1300px;
height: 100%;
margin: 0 auto 0 auto;
padding: 0 30px;
}
#content .containerOSExpanded {
height: 100%;
margin: 0 auto 0 auto;
padding: 0 20px;
}
/** Content **/
#content {
float: left;
width: 100%;
margin-top: 20px;
.containerOS {
height: 30px;
}
}
/** Main column **/
#content .col1 {
float: left;
&.sidebar-max { /*with maximized sidebar*/
width: calc(100% - 325px);
}
&.sidebar-min { /*with minimized sidebar*/
width: calc(100% - 70px);
}
&.sidebar-none { /*without sidebar*/
width: 100%;
}
.header {
background: #fff;
border: 1px solid #d3d3d3;
.submenu {
float: right;
}
}
.title {
padding: 0 20px;
width: auto;
}
.meta {
.heading, h3 {
font-family: $font-condensed-light;
font-size: 22px;
line-height: 24px;
font-weight: 300;
color: #444;
padding-bottom: 0;
margin-top: 20px;
margin-bottom: 5px;
}
.title {
width: 100%;
cursor: pointer;
height: 30px;
color: #fff;
background: #317796;
padding: 5px 20px 0 20px;
&:hover {
color: #d3d3d3;
}
.name {
float: left;
font-size: 14px;
}
.icon {
float: right;
}
}
.detail {
padding: 0 20px 10px 20px;
width: 100%;
min-height: 30px;
background: #d3d3d3;
color: #444;
}
.heading .drop-down-name {
font-family: $font-condensed-light;
}
}
.details {
padding: 20px;
width: auto;
margin-top: 20px;
background: #fff;
border: 1px solid #d3d3d3;
img {
max-width: 100%;
height: auto;
}
}
ol, ul {
margin-left: 15px;
}
}
/** Projector sidebar column **/
#sidebar-xs {
display: none !important;
}
#content .col2 {
float: right;
position: relative;
display: inline-flex;
z-index: 3;
margin-bottom: 20px;
&.sidebar-max {
width: 325px;
}
&.sidebar-min {
width: 70px;
}
&.sidebar-none {
width: 0px;
}
h4 {
font-size: 20px;
line-height: 24px;
color: #444;
font-family: $font-condensed-light;
font-weight: 300;
}
a:hover h4 {
text-decoration: none;
}
.projector_min {
background: url('/static/img/nav_projector_sidebar_min.png') no-repeat left 17px;
width: 50px;
margin-left: 20px;
padding-left: 8px;
float: right;
.icon {
float: left;
color: #fff;
font-size: 24px;
width: 50px;
text-align: center;
padding: 7px 0;
background: #317796;
a {
color: #fff;
display: block;
i {
opacity: 0.5;
}
&:hover i {
opacity: 1;
}
}
}
}
.projector_full {
margin-left: 30px;
width: auto;
.title {
width: 100%;
color: #fff;
height: 50px;
background: #317796;
cursor: pointer;
&:hover {
color: #d3d3d3;
}
a, a:hover {
color: #fff;
text-decoration: none;
display: block;
}
i {
margin-right: 10px;
}
.name {
float: left;
padding: 8px 0 0 20px;
font-size: 22px;
font-weight: 400;
}
.icon {
float: right;
width: 50px;
text-align: center;
padding-top: 7px;
font-size: 24px;
}
}
.details {
clear: both;
width: 100%;
background: #d3d3d3;
.section {
padding: 1px 20px;
width: auto;
border-bottom: 1px solid #c2c2c2;
a:hover {
text-decoration: none;
}
div.in.collapse {
padding-bottom: 15px;
}
}
}
.toggle-icon {
font-size: 20px;
float: right;
margin-top: 10px;
}
}
/* countdown and message controls */
.countdown {
&.panel {
margin-bottom: 7px;
}
.panel-heading {
padding: 3px 15px;
}
.panel-body {
padding: 5px 15px;
}
.icons {
padding-right: 10px;
}
}
.message {
&.panel {
margin-bottom: 7px;
}
.panel-heading {
padding: 3px 15px;
}
.panel-body {
padding: 10px 15px;
}
projector-button {
float: left;
width: auto;
margin: 5px 10px 5px 0px;
}
.innermessage {
float: left;
width: 180px;
max-width: 170px;
overflow: hidden;
}
.panel-input {
width: 228px;
float: left;
margin-top: 10px;
}
.editicon {
padding-right: 10px;
}
}
.countdown_timer {
font-size: 2.2em;
font-weight: bold;
&.warning {
color: #ed940d;
}
&.negative {
color: #CC0000;
}
}
.notNull {
color: red;
font-weight: bold;
}
}
/* iframe for live view */
.iframe {
-moz-transform-origin: 0 0;
-webkit-transform-origin: 0 0;
-o-transform-origin: 0 0;
transform-origin: 0 0 0;
}
.iframewrapper {
width: 258px;
position: relative;
overflow: hidden;
border: 1px solid #D5D5D5;
margin-bottom: 10px;
}
.iframeoverlay {
width: 256px;
position: absolute;
top: 0px;
left: 0px;
display: block;
z-index: 1;
}
/** Footer **/
#footer {
float: left;
height: 50px;
padding-top: 15px;
font-size: 90%;
}
.details h1 {
font-size: 20px;
color: #000;
}
.details h2 {
font-size: 18px;
color: #000;
}
.details h3 {
font-size: 16px;
color: #000;
}
/** Config **/
#config .panel-body {
padding-top: 0;
}
#config.details h3 {
font-size: 22px;
padding-top: 30px;
margin-top: 0;
}

View File

@ -1,14 +0,0 @@
/* Main projector CSS file. Just import all files needed for the site here. */
/* General */
@import "variables";
@import "helper";
@import "ui-override";
@import "core/countdown";
/* Apps */
@import "core/projector";
@import "../../../agenda/static/css/agenda/projector";
@import "../../../assignments/static/css/assignments/projector";
@import "../../../motions/static/css/motions/projector";
@import "../../../mediafiles/static/css/mediafiles/projector";

View File

@ -1,17 +0,0 @@
/* Main site CSS file. Just import all files needed for the site here. */
/* General */
@import "variables"; // TODO: Add colors, ...
@import "fonts";
@import "helper";
@import "mediaqueries";
@import "ui-override";
@import "startup-spinner";
/* Apps */
@import "core/site";
@import "../../../agenda/static/css/agenda/site";
@import "../../../assignments/static/css/assignments/site";
@import "../../../motions/static/css/motions/site";
@import "../../../users/static/css/users/site";
@import "../../../mediafiles/static/css/mediafiles/site";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 925 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -0,0 +1,3 @@
<h1>Hello user!</h1>
Use <a href="http://localhost:4200">This url</a> to get to the client. For further information see the DEVELOPMENT.rst

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.core.csv', [])
.factory('CsvDownload', [
'Config',
'FileSaver',
function (Config, FileSaver) {
var utf8_BOM = decodeURIComponent('%EF%BB%BF');
return function (contentRows, filename) {
var separator = Config.get('general_csv_separator').value;
var rows = _.map(contentRows, function (row) {
return row.join(separator);
});
var blob = new Blob([utf8_BOM + rows.join('\n')]);
FileSaver.saveAs(blob, filename);
};
}
]);
}());

View File

@ -1,356 +0,0 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.core.docx', [])
.factory('Html2DocxConverter', [
'$q',
'ImageConverter',
function ($q, ImageConverter) {
var PAGEBREAK = '<w:p><w:r><w:br w:type="page" /></w:r></w:p>';
var createInstance = function () {
var converter = {
imageMap: {},
documentImages: [],
relationships: [],
contentTypes: [],
};
var html2docx = function (html) {
var docx = '';
var tagStack = [];
// With this variable, we keep track, if we are currently inside or outside of a paragraph.
var inParagraph = true;
// the text may not begin with a paragraph. If so, append one because word needs it.
var skipFirstParagraphClosing = true;
var handleTag = function (tag) {
if (tag.charAt(0) == "/") { // A closing tag
// remove from stack
tagStack.pop();
// Special: end paragraphs
if (tag.indexOf('/p') === 0) {
docx += '</w:p>';
inParagraph = false;
}
} else { // now all other tags
var tagname = tag.split(' ')[0];
handleNamedTag(tagname, tag);
}
return docx;
};
var handleNamedTag = function (tagname, fullTag) {
var tag = {
tag: tagname,
attrs: {},
};
switch (tagname) {
case 'p':
if (inParagraph && !skipFirstParagraphClosing) {
// End the paragrapth, if there is one
docx += '</w:p>';
}
skipFirstParagraphClosing = false;
docx += '<w:p>';
inParagraph = true;
break;
case 'span':
var styleRegex = /(?:\"|\;\s?)([a-zA-z\-]+)\:\s?([a-zA-Z0-9\-\#]+)/g, matchSpan;
while ((matchSpan = styleRegex.exec(fullTag)) !== null) {
switch (matchSpan[1]) {
case 'color':
tag.attrs.color = matchSpan[2].slice(1); // cut off the #
break;
case 'background-color':
tag.attrs.backgroundColor = matchSpan[2].slice(1); // cut off the #
break;
case 'text-decoration':
if (matchSpan[2] === 'underline') {
tag.attrs.underline = true;
} else if (matchSpan[2] === 'line-through') {
tag.attrs.strike = true;
}
break;
}
}
break;
case 'a':
var hrefRegex = /href="([^"]+)"/g;
var href = hrefRegex.exec(fullTag)[1];
tag.href = href;
break;
case 'img':
imageTag(tag, fullTag);
break;
}
if (tagname !== 'img' && tagname !== 'p') {
tagStack.push(tag);
}
};
var imageTag = function (tag, fullTag) {
// images has to be placed instantly, so there is no use of 'tag'.
var image = {};
var attributeRegex = /(\w+)=\"([^\"]*)\"/g, attributeMatch;
while ((attributeMatch = attributeRegex.exec(fullTag)) !== null) {
image[attributeMatch[1]] = attributeMatch[2];
}
if (image.src && converter.imageMap[image.src]) {
image.width = converter.imageMap[image.src].width;
image.height = converter.imageMap[image.src].height;
var rrId = converter.relationships.length + 1;
var imageId = converter.documentImages.length + 1;
// set name ('pic.jpg'), title, ext ('jpg'), mime ('image/jpeg')
image.name = _.last(image.src.split('/'));
var tmp = image.name.split('.');
image.ext = tmp.splice(-1);
// set name without extension as title if there isn't a title
if (!image.title) {
image.title = tmp.join('.');
}
image.mime = 'image/' + image.ext;
if (image.ext == 'jpe' || image.ext == 'jpg') {
image.mime = 'image/jpeg';
}
// x and y for the container and picture size in EMU (assuming 96dpi)!
var x = image.width * 914400 / 96;
var y = image.height * 914400 / 96;
// the image does not belong into a paragraph in ooxml
if (inParagraph) {
docx += '</w:p>';
}
docx += '<w:p><w:r><w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extend cx="' + x +'" cy="' + y + '"/><wp:effectExtent l="0" t="0" r="0" b="0"/>' +
'<wp:docPr id="' + imageId + '" name="' + image.name + '" title="' + image.title + '" descr="' + image.title + '"/><wp:cNvGraphicFramePr>' +
'<a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1"/></wp:cNvGraphicFramePr>' +
'<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">' +
'<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:nvPicPr><pic:cNvPr id="' + imageId + '" name="' +
image.name + '" title="' + image.title + '" descr="' + image.title + '"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="rrId' + rrId + '"/><a:stretch>' +
'<a:fillRect/></a:stretch></pic:blipFill><pic:spPr bwMode="auto"><a:xfrm><a:off x="0" y="0"/><a:ext cx="' + x + '" cy="' + y + '"/></a:xfrm>' +
'<a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r></w:p>';
// inParagraph stays untouched, the documents paragraph state is restored here
if (inParagraph) {
docx += '<w:p>';
}
// entries in documentImages, relationships and contentTypes
converter.documentImages.push({
src: image.src,
zipPath: 'word/media/' + image.name
});
converter.relationships.push({
Id: 'rrId' + rrId,
Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
Target: 'media/' + image.name
});
converter.contentTypes.push({
PartName: '/word/media/' + image.name,
ContentType: image.mime
});
}
};
var handleText = function (text) {
// Start a new paragraph, if only loose text is there
if (!inParagraph) {
docx += '<w:p>';
inParagraph = true;
}
var docxPart = '<w:r><w:rPr>';
var hyperlink = false;
tagStack.forEach(function (tag) {
switch (tag.tag) {
case 'b':
case 'strong':
docxPart += '<w:b/><w:bCs/>';
break;
case 'em':
case 'i':
docxPart += '<w:i/><w:iCs/>';
break;
case 'span':
for (var key in tag.attrs) {
switch (key) {
case 'color':
docxPart += '<w:color w:val="' + tag.attrs[key] + '"/>';
break;
case 'backgroundColor':
docxPart += '<w:shd w:fill="' + tag.attrs[key] + '"/>';
break;
case 'underline':
docxPart += '<w:u w:val="single"/>';
break;
case 'strike':
docxPart += '<w:strike/>';
break;
}
}
break;
case 'u':
docxPart += '<w:u w:val="single"/>';
break;
case 'strike':
docxPart += '<w:strike/>';
break;
case 'a':
var id = converter.relationships.length + 1;
docxPart = '<w:hyperlink r:id="rrId' + id + '">' + docxPart;
docxPart += '<w:rStyle w:val="Internetlink"/>';
converter.relationships.push({
Id: 'rrId' + id,
Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
Target: tag.href,
TargetMode: 'External'
});
hyperlink = true;
break;
}
});
docxPart += '</w:rPr><w:t>' + text + '</w:t></w:r>';
if (hyperlink) {
docxPart += '</w:hyperlink>';
}
// append to docx
docx += docxPart;
return docx;
};
var replaceEntities = function () {
// replacing of special symbols:
docx = docx.replace(new RegExp('\&auml\;', 'g'), 'ä');
docx = docx.replace(new RegExp('\&uuml\;', 'g'), 'ü');
docx = docx.replace(new RegExp('\&ouml\;', 'g'), 'ö');
docx = docx.replace(new RegExp('\&Auml\;', 'g'), 'Ä');
docx = docx.replace(new RegExp('\&Uuml\;', 'g'), 'Ü');
docx = docx.replace(new RegExp('\&Ouml\;', 'g'), 'Ö');
docx = docx.replace(new RegExp('\&szlig\;', 'g'), 'ß');
docx = docx.replace(new RegExp('\&nbsp\;', 'g'), ' ');
docx = docx.replace(new RegExp('\&sect\;', 'g'), '§');
// remove all entities except gt, lt and amp
var entityRegex = /\&(?!gt|lt|amp)\w+\;/g, matchEntry, indexes = [];
while ((matchEntry = entityRegex.exec(docx)) !== null) {
indexes.push({
startId: matchEntry.index,
stopId: matchEntry.index + matchEntry[0].length
});
}
for (var i = indexes.length - 1; i>=0; i--) {
docx = docx.substring(0, indexes[i].startId) + docx.substring(indexes[i].stopId, docx.length);
}
};
var parse = function () {
if (html.substring(0,3) != '<p>') {
docx += '<w:p>';
skipFirstParagraphClosing = false;
}
html = html.split(/(<|>)/g);
// remove whitespaces and > brackets. Leave < brackets in there to check, whether
// the following string is a tag or text.
html = _.filter(html, function (part) {
var skippedCharsRegex = new RegExp('^([\s\n\r]|>)*$', 'g');
return !skippedCharsRegex.test(part);
});
for (var i = 0; i < html.length; i++) {
if (html[i] === '<') {
i++;
handleTag(html[i]);
} else {
handleText(html[i]);
}
}
// for finishing close the last paragraph (if open)
if (inParagraph) {
docx += '</w:p>';
}
replaceEntities();
return docx;
};
return parse();
};
// return a wrapper function for html2docx, that fetches all the images.
converter.html2docx = function (html) {
var imageSources = _.map($(html).find('img'), function (element) {
return element.getAttribute('src');
});
// Don't get images multiple times; just if the converter has not seen them befor.
imageSources = _.filter(imageSources, function (src) {
return !converter.imageMap[src];
});
return $q(function (resolve) {
ImageConverter.toBase64(imageSources).then(function (_imageMap) {
_.forEach(_imageMap, function (value, key) {
converter.imageMap[key] = value;
});
var docx = html2docx(html);
resolve(docx);
});
});
};
converter.updateZipFile = function (zip) {
var updateRelationships = function (oldContent) {
var content = oldContent.split('\n');
_.forEach(converter.relationships, function (relationship) {
content[1] += '<Relationship';
_.forEach(relationship, function (value, key) {
content[1] += ' ' + key + '="' + value + '"';
});
content[1] += '/>';
});
return content.join('\n');
};
var updateContentTypes = function (oldContent) {
var content = oldContent.split('\n');
_.forEach(converter.contentTypes, function (type) {
content[1] += '<Override';
_.forEach(type, function (value, key) {
content[1] += ' ' + key + '="' + value + '"';
});
content[1] += '/>';
});
return content.join('\n');
};
// update relationships from 'relationships'
var relationships = updateRelationships(zip.file('word/_rels/document.xml.rels').asText());
zip.file('word/_rels/document.xml.rels', relationships);
// update content type from 'contentTypes'
var contentTypes = updateContentTypes(zip.file('[Content_Types].xml').asText());
zip.file('[Content_Types].xml', contentTypes);
converter.documentImages = _.uniqBy(converter.documentImages, 'src');
_.forEach(converter.documentImages, function (image) {
var dataUrl = converter.imageMap[image.src].data;
var base64 = dataUrl.split(',')[1];
zip.file(image.zipPath, base64, {base64: true});
});
return zip;
};
return converter;
};
return {
createInstance: createInstance,
};
}
]);
})();

View File

@ -1,124 +0,0 @@
/* Worker for creating PDFs in a separate thread. The creation of larger PDFs
* needs (currently) a lot of time and we don't want to block the UI.
*/
// Setup fake environment for pdfMake
var document = {
'createElementNS': function () {
return {};
},
};
var window = this;
// PdfMake
importScripts('/static/js/workers/pdf-worker-libs.js');
// Set default font family.
// "PdfFont" and "OSFont-*" are generic names used here and in core/pdf.js. The
// suffix after "OSFont-" has to be the same as the config value.
pdfMake.fonts = {
PdfFont: {
normal: 'OSFont-regular.ttf',
bold: 'OSFont-bold.ttf',
italics: 'OSFont-italic.ttf',
bolditalics: 'OSFont-bold_italic.ttf'
}
};
// Function to replace layout placeholder
//
// Workaround for using table layout functions.
// TODO: Needs improvement of pdfmake's web worker support.
// Currently only functions are allowed for 'layout'.
// But functions cannot be passed to workers (via JSON).
var replacePlaceholder = function (content) {
for (var i = 0; i < content.length; i++) {
if (typeof content[i] === 'object') {
// motion meta table border lines
if (content[i].layout === "{{motion-placeholder-to-insert-functions-here}}") {
content[i].layout = {
hLineWidth: function(i, node) {
return (i === 0 || i === node.table.body.length) ? 0 : 0.5;
},
vLineWidth: function() {
return 0;
},
hLineColor: function() {
return 'white';
}
};
return true;
}
// ballot paper crop marks
if (content[i].layout === "{{ballot-placeholder-to-insert-functions-here}}") {
content[i].layout = {
hLineWidth: function(i, node) {
if (i === 0){
return 0;
} else {
return 0.5;
}
},
vLineWidth: function(i, node) {
return (i === 0 || i === node.table.widths.length) ? 0 : 0.5;
},
hLineColor: function() {
return 'gray';
},
vLineColor: function() {
return 'gray';
}
};
return true;
}
replacePlaceholder(content[i]);
}
}
};
// Workaround for using dynamic footer with page number.
// TODO: Needs improvement of pdfmake's web worker support.
// see https://github.com/bpampuch/pdfmake/issues/38
var replaceFooter = function (doc) {
if (doc.footerTpl) {
doc.footer = function (currentPage, pageCount) {
// One way to clone arrays/objects in js, that are serilizeable.
var columns = JSON.parse(JSON.stringify(doc.footerTpl.columns));
for (var i = 0; i < columns.length; i++) {
if (columns[i].text) {
columns[i].text = columns[i].text
.replace('{{currentPage}}', currentPage)
.replace('{{pageCount}}', pageCount);
}
}
return {
columns: columns,
margin: doc.footerTpl.margin,
};
};
}
};
// Create PDF on message and return the base64 decoded document
self.addEventListener('message', function(e) {
var data = JSON.parse(e.data);
pdfMake.vfs = data.vfs; // Set custom fonts.
var doc = data.pdfDocument;
replaceFooter(doc);
replacePlaceholder(doc.content);
var pdf = pdfMake.createPdf(doc);
pdf.getBase64(function (base64) {
if (data.filename) {
self.postMessage(JSON.stringify({
filename: data.filename,
base64: base64
}));
} else {
self.postMessage(base64);
}
});
}, false);

File diff suppressed because it is too large Load Diff

View File

@ -1,409 +0,0 @@
(function () {
'use strict';
// The core module for the OpenSlides projector
angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
// Can be used to find out if the projector or the side is used
.constant('REALM', 'projector')
.run([
'$http',
'autoupdate',
'DS',
function ($http, autoupdate, DS) {
autoupdate.newConnect();
// If the connection aborts, we try to ping the server with whoami requests. If
// the server is flushed, we clear the datastore, so the message 'this projector
// cannot be shown' will be displayed. Otherwise establish the websocket connection.
autoupdate.registerRetryConnectCallback(function () {
return $http.get('/users/whoami').then(function (success) {
if (success.data.user_id === null && !success.data.guest_enabled) {
DS.clear();
} else {
autoupdate.newConnect();
}
});
});
}
])
// 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.error("Unknown slide: " + element.name);
}
});
return elements;
}
};
};
}
])
.config([
'slidesProvider',
function(slidesProvider) {
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/projector-message', {
template: 'static/templates/core/slide_message.html',
});
}
])
.controller('LanguageAndFontCtrl', [
'$scope',
'Languages',
'Config',
'Projector',
'ProjectorID',
'Fonts',
function ($scope, Languages, Config, Projector, ProjectorID, Fonts) {
// for the dynamic title
$scope.projectorId = ProjectorID();
$scope.$watch(function () {
return Projector.lastModified($scope.projectorId);
}, function () {
var projector = Projector.get($scope.projectorId);
if (projector) {
$scope.projectorName = projector.name;
}
});
$scope.$watch(function () {
return Config.lastModified('projector_language');
}, function () {
var lang = Config.get('projector_language');
if (!lang || lang.value == 'browser') {
$scope.selectedLanguage = Languages.getBrowserLanguage();
} else {
$scope.selectedLanguage = lang.value;
}
Languages.setCurrentLanguage($scope.selectedLanguage);
});
$scope.$watch(function () {
return Config.lastModified('font_regular') +
Config.lastModified('font_italic') +
Config.lastModified('font_bold') +
Config.lastModified('font_bold_italic');
}, function () {
$scope.font = Fonts.getForCss('font_regular');
$scope.font_medium = Fonts.getForCss('font_italic');
$scope.font_condensed = Fonts.getForCss('font_bold');
$scope.font_condensed_light = Fonts.getForCss('font_bold_italic');
});
}
])
// Projector Container Controller
.controller('ProjectorContainerCtrl', [
'$scope',
'$timeout',
'$location',
'gettext',
'Projector',
function($scope, $timeout, $location, gettext, Projector) {
$scope.showError = true;
// watch for changes in Projector
$scope.$watch(function () {
return Projector.lastModified($scope.projectorId);
}, function () {
var projector = Projector.get($scope.projectorId);
if (projector) {
$scope.showError = false;
$scope.projectorWidth = projector.width;
$scope.projectorHeight = projector.height;
$scope.recalculateIframe();
} else {
$scope.showError = true;
// delay displaying the error message, because with a slow internet
// connection, the autoupdate with the projector may be delayed. We
// de not want to irritate the user by showing this error to early.
$scope.error = '';
$timeout(function () {
if ($scope.showError) {
$scope.error = gettext('Can not open the projector.');
}
}, 3000);
}
});
// recalculate the actual Iframesize and scale
$scope.recalculateIframe = function () {
var scale_width = window.innerWidth / $scope.projectorWidth;
var scale_height = window.innerHeight / $scope.projectorHeight;
// Iframe has to be scaled down or saceUp is activated
if (scale_width <= scale_height) {
// width is the reference
$scope.iframeWidth = window.innerWidth;
$scope.scale = scale_width;
$scope.iframeHeight = $scope.projectorHeight * scale_width;
} else {
// height is the reference
$scope.iframeHeight = window.innerHeight;
$scope.scale = scale_height;
$scope.iframeWidth = $scope.projectorWidth * scale_height;
}
};
// watch for changes in the windowsize
$(window).on("resize.doResize", function () {
$scope.$apply(function() {
$scope.recalculateIframe();
});
});
$scope.$on("$destroy",function (){
$(window).off("resize.doResize");
});
}
])
.controller('ProjectorCtrl', [
'$scope',
'$location',
'$timeout',
'Projector',
'slides',
'Config',
'ProjectorID',
'Logos',
function($scope, $location, $timeout, Projector, slides, Config, ProjectorID, Logos) {
var projectorId = ProjectorID();
$scope.broadcast = 0;
var setElements = function (projector) {
// Get all elements, that should be projected.
var newElements = [];
var enable_clock = Config.get('projector_enable_clock');
enable_clock = enable_clock ? enable_clock.value : true;
_.forEach(slides.getElements(projector), function (element) {
if (!element.error) {
// Exclude the clock if it should be disabled.
if (enable_clock || element.name !== 'core/clock') {
newElements.push(element);
}
} else {
console.error("Error for slide " + element.name + ": " + element.error);
}
});
// Now we have to align $scope.elements to newElements:
// We cannot just assign them, because the ng-repeat would reload every
// element. This should be prevented (see #3259). To change $scope.elements:
// 1) remove all elements from scope, that are not in newElements (compared by the uuid)
// 2) Every new element in newElements, that is not in $scope.elements, get inserted there.
// 3) If there is the same element in newElements and $scope.elements every changed property
// is copied from the new element to the scope element.
$scope.elements = _.filter($scope.elements, function (element) {
return _.some(newElements, function (newElement) {
return element.uuid === newElement.uuid;
});
});
_.forEach(newElements, function (newElement) {
var matchingElement = _.find($scope.elements, function (element) {
return element.uuid === newElement.uuid;
});
if (matchingElement) {
// copy all changed properties.
_.forEach(newElement, function (value, key) {
// key has own property and does not start with a '$'.
if (newElement.hasOwnProperty(key) && key.indexOf('$') != 0) {
if (typeof matchingElement[key] === 'undefined' || matchingElement[key] !== value) {
matchingElement[key] = value;
}
}
});
} else {
$scope.elements.push(newElement);
}
});
};
$scope.scroll = 0;
var setScroll = function (scroll) {
$scope.scroll = -250 * scroll;
};
$scope.$watch(function () {
return Projector.lastModified(projectorId);
}, function () {
$scope.projector = Projector.get(projectorId);
if ($scope.projector) {
if ($scope.broadcast === 0) {
setElements($scope.projector);
$scope.blank = $scope.projector.blank;
}
setScroll($scope.projector.scroll);
} else {
// Blank projector on error
$scope.elements = [];
$scope.projector = {
scale: 0,
blank: true
};
setScroll(0);
}
});
$scope.$watch(function () {
return Config.lastModified('projector_broadcast');
}, function () {
var bc = Config.get('projector_broadcast');
if (bc) {
if ($scope.broadcast != bc.value) {
$scope.broadcast = bc.value;
if ($scope.broadcastDeregister) {
// revert to original $scope.projector
$scope.broadcastDeregister();
$scope.broadcastDeregister = null;
setElements($scope.projector);
$scope.blank = $scope.projector.blank;
}
}
if ($scope.broadcast > 0) {
// get elements and blank from broadcast projector
$scope.broadcastDeregister = $scope.$watch(function () {
return Projector.lastModified($scope.broadcast);
}, function () {
if ($scope.broadcast > 0) {
var broadcast_projector = Projector.get($scope.broadcast);
if (broadcast_projector) {
setElements(broadcast_projector);
$scope.blank = broadcast_projector.blank;
}
}
});
}
}
});
$scope.$watch(function () {
return Config.lastModified('projector_enable_clock');
}, function () {
setElements($scope.projector);
});
$scope.$on('$destroy', function() {
if ($scope.broadcastDeregister) {
$scope.broadcastDeregister();
$scope.broadcastDeregister = null;
}
});
}
])
.controller('SlideClockCtrl', [
'$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.servertime = ( Date.now() / 1000 - $scope.serverOffset ) * 1000;
var interval = $interval(function () {
$scope.servertime = ( Date.now() / 1000 - $scope.serverOffset ) * 1000;
}, 30000); // Update the clock every 30 seconds
$scope.$on('$destroy', function() {
if (interval) {
$interval.cancel(interval);
}
});
}
])
.controller('SlideCountdownCtrl', [
'$scope',
'$interval',
'Countdown',
function($scope, $interval, Countdown) {
// 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;
var interval;
var calculateCountdownTime = function (countdown) {
countdown.seconds = Math.floor( $scope.countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset );
};
$scope.$watch(function () {
return Countdown.lastModified(id);
}, function () {
$scope.countdown = Countdown.get(id);
if (interval) {
$interval.cancel(interval);
}
if ($scope.countdown) {
if ($scope.countdown.running) {
calculateCountdownTime($scope.countdown);
interval = $interval(function () { calculateCountdownTime($scope.countdown); }, 1000);
} else {
$scope.countdown.seconds = $scope.countdown.countdown_time;
}
}
});
$scope.$on('$destroy', function() {
// Cancel the interval if the controller is destroyed
if (interval) {
$interval.cancel(interval);
}
});
}
])
.controller('SlideMessageCtrl', [
'$scope',
'ProjectorMessage',
'Projector',
'ProjectorID',
'gettextCatalog',
function($scope, ProjectorMessage, Projector, ProjectorID, gettextCatalog) {
// 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;
if ($scope.element.identify) {
var projector = Projector.get(ProjectorID());
$scope.identifyMessage = gettextCatalog.getString('Projector') + ' ' + projector.id + ': ' + gettextCatalog.getString(projector.name);
} else {
$scope.message = ProjectorMessage.get(id);
ProjectorMessage.bindOne(id, $scope, 'message');
}
}
]);
}());

View File

@ -1,50 +0,0 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.core.remove-format-plugin', [
'OpenSlidesApp.core',
])
/*
* Plugin for the CKEditor that hooks into the removeformat plugin
* which is a default plugin enabled by 'cleanup' in the config
* toolbar.
* We change the behavior of the removeformat command here:
* It should not remove any tags and styles, but only the
* 'DISALLOWED_STYLES'. Removeformat traverses through the DOM
* and calles for every element the custom filter down below.
* We change the element and return false, so the removeformat
* plugin does not clean it up.
*/
.factory('OSRemoveFormatPlugin', [
'Editor',
'gettextCatalog',
function (Editor, gettextCatalog) {
var DISALLOWED_STYLES = ['color', 'background-color'];
return {
getPlugin: function () {
return {
init: function (editor) {
editor.addRemoveFormatFilter(function (element) {
_.forEach(DISALLOWED_STYLES, function (style) {
element.removeStyle(style);
});
return false;
});
},
};
},
};
}
])
.run([
'Editor',
'OSRemoveFormatPlugin',
function (Editor, OSRemoveFormatPlugin, gettext) {
Editor.registerPlugin('OSRemoveFormat', OSRemoveFormatPlugin.getPlugin());
}
]);
}());

File diff suppressed because it is too large Load Diff

View File

@ -1,117 +0,0 @@
(function () {
'use strict';
angular.module('OpenSlidesApp.core.start', [])
.factory('OpenSlides', [
'$http',
'$rootScope',
'$state',
'$q',
'DS',
'autoupdate',
'operator',
'Group',
'mainMenu',
'ngDialog',
'LoginDialog',
function($http, $rootScope, $state, $q, DS, autoupdate, operator, Group, mainMenu, ngDialog, LoginDialog) {
var OpenSlides = {
bootup: function () {
$rootScope.openslidesBootstrapDone = false;
$http.get('/users/whoami/').then(function (success) {
$rootScope.guest_enabled = success.data.guest_enabled;
if (success.data.user_id === null && !success.data.guest_enabled) {
// Redirect to login dialog if user is not logged in.
$state.go('login', {guest_enabled: success.data.guest_enabled});
} else {
autoupdate.newConnect();
autoupdate.firstMessageDeferred.promise.then(function () {
operator.setUser(success.data.user_id, success.data.user);
$rootScope.operator = operator;
mainMenu.updateMainMenu();
$rootScope.openslidesBootstrapDone = true;
});
}
});
},
shutdown: function () {
// Close connection, clear the store and show the OS overlay.
autoupdate.closeConnection();
DS.clear();
operator.setUser(null);
$rootScope.openslidesBootstrapDone = false;
$rootScope.operator = operator;
// close all open dialogs (except the login dialog)
_.forEach(ngDialog.getOpenDialogs(), function (id) {
if (id !== LoginDialog.id) {
ngDialog.close(id);
}
});
},
reboot: function () {
this.shutdown();
this.bootup();
},
};
// We need to 'ping' the server with a get request to whoami, because then we can decide,
// if the server is down or respond with a 403 (this cannot be differentiated with websockets)
autoupdate.registerRetryConnectCallback(function () {
return $http.get('/users/whoami').then(function (success) {
if (success.data.user_id === null && !success.data.guest_enabled) {
OpenSlides.shutdown();
// Redirect to login dialog if user is not logged in.
$state.go('login', {guest_enabled: success.data.guest_enabled});
} else {
autoupdate.newConnect();
}
});
});
return OpenSlides;
}
])
.run([
'OpenSlides',
function (OpenSlides) {
OpenSlides.bootup();
}
])
.run([
'$rootScope',
'$state',
'operator',
'User',
'Group',
'mainMenu',
function ($rootScope, $state, operator, User, Group, mainMenu) {
var permissionChangeCallback = function () {
operator.reloadPerms();
mainMenu.updateMainMenu();
var stateData = $state.current.data;
var basePerm = stateData ? stateData.basePerm : '';
$rootScope.baseViewPermissionsGranted = basePerm ?
operator.hasPerms(basePerm) : true;
};
$rootScope.$watch(function () {
return Group.lastModified();
}, function () {
if (Group.getAll().length) {
permissionChangeCallback();
}
});
$rootScope.$watch(function () {
return operator.user ? User.lastModified(operator.user.id) : true;
}, function () {
permissionChangeCallback();
});
}
]);
}());

View File

@ -1,152 +0,0 @@
<div class="form-group">
<label>{{ label | translate }}</label>
<div class="input-group">
<!-- text/number input -->
<input ng-if="type === 'text' || type === 'number'"
ng-model="$parent.value"
ng-model-options="{debounce: 1000}"
ng-change="save(configOption, $parent.value)"
ng-class="{ 'form-control': type != 'checkbox' }"
id="{{ key }}"
type="{{ type }}">
<!-- checkbox -->
<div ng-if="type === 'checkbox'" class="config-checkbox">
<i class="fa pointer" id="{{ key }}"
ng-click="$parent.value = !$parent.value; save(configOption, $parent.value)"
ng-class="$parent.value ? 'fa-check-square-o' : 'fa-square-o'"></i>
</div>
<!-- comments -->
<div class="comments" ng-if="type === 'comments'">
<div ng-repeat="(id, field) in ($parent.value
| excludeSpecialCommentsFields
| excludeDeletedAndForbiddenCommentsFields)"
class="input-group">
<input ng-model="field.name"
ng-model-options="{debounce: 1000}"
ng-change="save(configOption, $parent.value)"
class="form-control"
id="{{ key }}"
type="text">
<span class="input-group-btn">
<button type=button" class="btn btn-default"
ng-click="field.public = !field.public; save(configOption, $parent.value);">
<i class="fa" ng-class="field.public ? 'fa-unlock' : 'fa-lock'"></i>
{{ (field.public ? 'Public' : 'Private') | translate }}
</button>
<button type="button" class="btn btn-default"
ng-click="removeComment(configOption, $parent, id)">
<i class="fa fa-minus"></i>
<translate>Remove</translate>
</button>
</span>
</div>
<div>
<button type="button" ng-click="addComment(configOption, $parent)"
class="btn btn-default btn-sm">
<i class="fa fa-plus"></i>
<translate>Add new comment field</translate>
</button>
</div>
</div>
<!-- colorpicker -->
<input ng-if="type === 'colorpicker'"
colorpicker
class="form-control"
ng-model="$parent.value"
ng-model-options="{debounce: 1000}"
ng-change="save(configOption, $parent.value)"
type="text">
<!-- datetimepicker -->
<input ng-if="type === 'datetimepicker'"
class="form-control"
datetime-picker="dd.MM.yyyy HH:mm"
ng-model="$parent.value"
ng-change="save(configOption, $parent.value)"
is-open="datetimeOpen[key]"
ng-focus="datetimeOpen[key]=true;"
save-as="'number'"
button-bar="dateTimePickerTranslatedButtons"
default-time="10:00:00">
<!-- textarea -->
<textarea ng-if="type === 'textarea'"
ng-model="$parent.value"
ng-model-options="{debounce: 1000}"
ng-change="save(configOption, $parent.value)"
id="{{ key }}" class="form-control">
</textarea>
<!-- editor -->
<textarea ng-if="type === 'editor'"
id="{{ configOption.key }}"
ckeditor="ckeditorOptions"
ng-model="$parent.value" class="form-control"
ng-model-options="{debounce: 1000}">
</textarea>
<!-- select -->
<select ng-if="type === 'choice'"
ng-model="$parent.value"
ng-model-options="{debounce: 500}"
ng-change="save(configOption, $parent.value)"
id="{{ key }}" class="form-control"
ng-options="option.value as option.display_name | translate for option in choices">
</select>
<!-- custom trnaslations -->
<div class="custom-translations" ng-if="type === 'translations'">
<div ng-repeat="entry in $parent.value" class="input-group">
<div class="inputs">
<input ng-model="entry.original"
ng-model-options="{debounce: 1000}"
ng-change="save(configOption, $parent.value)"
class="form-control"
id="{{ key }}_original"
type="text">
<span class="arrow form-control"><i class="fa fa-arrow-right"></i></span>
<input ng-model="entry.translation"
ng-model-options="{debounce: 1000}"
ng-change="save(configOption, $parent.value)"
class="form-control"
id="{{ key }}_translated"
type="text">
</div>
<span class="input-group-btn">
<button type="button" class="btn btn-default"
ng-click="removeTranslation(configOption, $parent, $index)">
<i class="fa fa-minus"></i>
<translate>Remove</translate>
</button>
</span>
</div>
<div>
<button type="button" ng-click="addTranslation(configOption, $parent)"
class="btn btn-default btn-sm">
<i class="fa fa-plus"></i>
<translate>Add new custom translation</translate>
</button>
</div>
</div>
<span id="success-{{ key }}" class="input-group-addon" ng-if="configOption.success !== undefined">
<i class="fa fa-lg fa-check-circle text-success"
ng-if="configOption.success === true"></i>
<i class="fa fa-lg fa-exclamation-triangle text-danger"
ng-if="configOption.success === false"
uib-tooltip="{{ configOption.errorMessage | translate }}"></i>
</span>
<span class="input-group-btn">
<button ng-click="reset()" class="btn btn-default" title="{{ default_value | translate }}">
<i class="fa fa-undo"></i>
<translate>Reset</translate>
</button>
</span>
</div>
<p ng-if="help_text" class="help-block">{{ help_text | translate }}</p>
</div>

View File

@ -1,35 +0,0 @@
<div class="header">
<div class="title">
<h1 translate>Settings</h1>
</div>
</div>
<div id="config" class="details">
<div os-perms="core.can_manage_config" class="panel-group" id="accordion"
role="tablist" aria-multiselectable="true">
<!-- generate config groups -->
<div ng-repeat="group in configGroups">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="heading-group{{ $index }}">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#group{{ $index }}"
aria-expanded="false" aria-controls="group{{ $index }}">
{{ group.name | translate }}
</a>
</h4>
</div> <!-- heading -->
<div id="group{{ $index }}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading-group{{ $index }}">
<div class="panel-body">
<div ng-repeat="subgroup in group.subgroups">
<h3>{{ subgroup.name | translate }}</h3>
<div ng-repeat="configOption in subgroup.items">
<os-form-field field="configOption"></os-form-field>
</div>
</div>
</div>
</div> <!-- group.name -->
</div>
</div>
</div>
</div>

View File

@ -1,14 +0,0 @@
<div class="form-group">
<div>
<label class="control-label" ng-if="to.label">
{{ to.label }}
</label>
</div>
<div class="btn-group">
<label ng-repeat="option in to.options" class="btn btn-default btn-sm" uib-btn-checkbox
ng-model="model[options.key][option.id]"
ng-disabled="option.disabled">
{{ option.name | translate }}
</label>
</div>
</div>

View File

@ -1,7 +0,0 @@
<div class="checkbox">
<label ng-click="model[options.key] = !model[options.key]">
<i class="fa" ng-class="model[options.key] ? 'fa-check-square-o' : 'fa-square-o'"></i>
{{to.label}}
{{to.required ? '*' : ''}}
</label>
</div>

View File

@ -1,16 +0,0 @@
<div id="countdownWrapper">
<div>
<div id="countdown" ng-class="{
'negative': countdown.seconds <= 0,
'warning_time': countdown.seconds <= config('agenda_countdown_warning_time') && countdown.seconds > 0 }">
<div class="row">
<div class="col-d-12">{{ countdown.seconds | osSecondsToTime}}</div>
</div>
<div class="row">
<div class="col-md-12 description">
<span class="pull-right">{{ countdown.description }}</span>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,2 +0,0 @@
<!-- custom angular formly template for ckeditor textarea field -->
<textarea ckeditor="options.data.ckeditorOptions" ng-model="model[options.key]" class="form-control"></textarea>

View File

@ -1,2 +0,0 @@
<!-- custom angular formly template for uploading a file -->
<input type="file" ngf-select ngf-change="to.change(model, $files, $event, $rejectedFiles)" class="form-control">

View File

@ -1,30 +0,0 @@
<form ng-submit="login()">
<div class="modal-header">
<img src="/static/img/openslides-logo.png" alt="OpenSlides" class="login-logo center-block">
</div>
<div class="modal-body loginForm">
<div uib-alert ng-repeat="alert in alerts" ng-class="'alert-' + (alert.type || 'warning')" close="closeAlert($index)">
<span ng-bind-html="alert.msg | translate"><span>
</div>
<div class="input-group form-group">
<div class="input-group-addon"><i class="fa fa-user"></i></div>
<input os-focus-me type="text" ng-model="username" class="form-control input-lg"
placeholder="{{ 'Username' | translate }}">
</div>
<div class="input-group form-group">
<div class="input-group-addon"><i class="fa fa-key"></i></div>
<input type="password" ng-model="password" class="form-control input-lg"
placeholder="{{ 'Password' | translate }}">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary" translate>
Login
</button>
<button ng-if="guestAllowed" ng-click="guestLogin()" class="btn btn-default" translate>
Continue as guest
</button>
<template-hook hook-name="loginFormButtons"></template-hook>
</div>
<a href="" ng-click="openPrivacyPolicyDialog()" translate>Privacy policy</a>
</div>
</form>

View File

@ -1,219 +0,0 @@
<div class="header">
<div class="title">
<div class="submenu" os-perms="core.can_manage_projector">
<!-- New -->
<button class="btn btn-primary btn-sm" ng-bootbox-prompt="{{ 'Please enter a name for the new projector' | translate }}"
ng-bootbox-prompt-action="createProjector(result)">
<i class="fa fa-plus"></i>
<translate>New</translate>
</button>
<!-- Reference for current list of speakers -->
<div class="btn-group" uib-dropdown>
<button class="btn btn-default btn-sm" id="menuListofSpeakers" uib-dropdown-toggle
uib-tooltip="{{ 'Select the projector to which the current list of speakers refers to.' | translate }}"
aria-haspopup="true" aria-expanded="true">
<i class="fa fa-microphone"></i>
<translate>Reference for current list of speakers</translate>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" uib-dropdown-menu aria-labelledby="menuListOfSpeakers">
<li ng-repeat="projector in projectors | orderBy:'id'">
<a href ng-click="setListOfSpeakers(projector)">
<i class="fa fa-check" ng-if="projector.id == currentListOfSpeakers"></i>
{{ projector.name | translate }}
</a>
</li>
</ul>
</div>
<!-- Identity -->
<button class="btn btn-sm" ng-click="identifyProjectors()" ng-class="identifyPromise ? 'btn-primary' : 'btn-default'"
uib-tooltip="{{ 'Display an identifier message on all projectors with the id and the name.' | translate }}">
<i class="fa fa-binoculars"></i>
<translate>Identify</translate>
</button>
</div>
<h1 translate>Manage projectors</h1>
</div>
</div>
<div class="details" os-perms="core.can_manage_projector">
<div id="manage-projectors">
<div ng-repeat="projector in projectors | orderBy: 'id'">
<div>
<a ui-sref="projector({id: projector.id})">
{{ projector.id }}:
<strong>{{ projector.name | translate }}</strong>
</a>
<span class="pull-right">
<a href="" ng-click="toggleEditMenu(projector.id)"><i class="fa" ng-class="edit[projector.id] ? 'fa-times' : 'fa-pencil'"></i></a>
<a href="" class="text-danger" style="padding-left: 5px;"
ng-hide="projector.id==1 || edit[projector.id]"
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
<b>{{ projector.name | translate }}</b>"
ng-bootbox-confirm-action="deleteProjector(projector)">
<i class="fa fa-trash"></i>
</a>
</span>
</div>
<div ng-show="edit[projector.id]" style="margin-bottom: -20px;">
<div class="form-group">
<label for="name{{ projector.id }}" class="control-label"><translate>Name</translate>:</label>
<input type="text" class="form-control" id="name{{ projector.id }}"
ng-model="projector.name" ng-change="editName(projector)"
ng-model-options="{debounce: 2000}"></input>
</div>
<div class="form-group">
<label for="menuProjector{{ projector.id }}" class="control-label"><translate>Projection defaults</translate>:</label>
<div class="dropdown" uib-dropdown>
<button class="btn btn-default btn-sm" id="menuProjector{{ projector.id }}" uib-dropdown-toggle>
--- <translate>Please select</translate> ---
<span class="caret"></span>
</button>
<ul class="dropdown-menu" uib-dropdown-menu aria-labelledby="menuProjector{{ projector.id }}">
<li ng-repeat="projectiondefault in projectiondefaults | orderBy:'id'">
<a href ng-click="setProjectionDefault(projector, projectiondefault)">
<i class="fa fa-check" ng-if="projectiondefault.projector_id === projector.id"></i>
{{ projectiondefault.display_name | translate }}
</a>
</li>
</ul>
</div>
</div>
<div class="form-group">
<label for="aspectRatio{{ projector.id }}" class="control-label"><translate>Aspect ratio</translate>:</label>
<div id="aspectRatio{{ projector.id }}">
<div class="dropdown" uib-dropdown>
<button class="btn btn-default btn-sm" uib-dropdown-toggle>
{{ resolutions[projector.id].aspectRatio }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" uib-dropdown-menu aria-labelledby="aspectRatio{{ projector.id }}">
<li ng-repeat="(aspectRatio, value) in aspectRatios track by $index">
<a href ng-click="setAspectRatio(projector, aspectRatio); saveResolution(projector);">
<i class="fa fa-check" ng-if="aspectRatio === resolutions[projector.id].aspectRatio"></i>
{{ aspectRatio }}
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="form-group">
<label for="resolution{{ projector.id }}" class="control-label"><translate>Scaling</translate>:</label>
<div id="resolution{{ projector.id }}">
<rzslider class="os-slider"
rz-slider-model="sliders[projector.id].value"
rz-slider-options="sliders[projector.id].options"></rzslider>
</div>
<p class="help-block">
{{ resolutions[projector.id].error }}
</p>
</div>
</div>
<style>
#iframe_{{ projector.id }} {
width: {{ projector.width }}px;
height: {{ projector.height }}px;
-moz-transform: scale({{ projector.iframeScale }});
-webkit-transform: scale({{ projector.iframeScale }});
-o-transform: scale({{ projector.iframeScale }});
transform: scale({{ projector.iframeScale }});
/* IE8+ - must be on one line, unfortunately */
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11={{ projector.iframeScale }}, M12=0, M21=0, M22={{ projector.iframeScale }}, SizingMethod='auto expand')";
}
#iframewrapper_{{ projector.id }} {
height: {{ projector.iframeHeight + 2 }}px;
}
#iframeoverlay_{{ projector.id }} {
height: {{ projector.iframeHeight }}px;
}
</style>
<a ui-sref="projector({id: projector.id})" target="_blank">
<div class="iframewrapper" id="iframewrapper_{{ projector.id }}">
<iframe class="iframe" id="iframe_{{ projector.id }}" ng-src="{{ '/real-projector/' + projector.id + '/' }}" frameborder="0"></iframe>
<div class="iframeoverlay" id="iframeoverlay_{{ projector.id }}"></div>
</div>
</a>
<!-- projector control buttons -->
<div class="nobr">
<!-- edit -->
<a ng-click="editCurrentSlide(projector)"
ng-disabled="!projector.getFormOrStateForCurrentSlide()"
class="btn btn-default btn-sm"
title="{{ 'Edit current slide' | translate}}">
<i class="fa fa-pencil"></i>
</a>
<!-- scale -->
<div class="btn-group">
<a ng-click="projector.controlProjector('scale', 'down')"
class="btn btn-default btn-sm"
title="{{ 'Smaller' | translate}}">
<i class="fa fa-search-minus"></i>
</a>
<a ng-click="projector.controlProjector('scale', 'up')"
class="btn btn-default btn-sm"
title="{{ 'Bigger' | translate}}">
<i class="fa fa-search-plus"></i>
</a>
<a ng-click="projector.controlProjector('scale', 'reset')"
class="btn btn-default btn-sm"
title="{{ 'Reset scaling' | translate}}">
<i class="fa fa-undo"></i>
</a>
</div>
<span ng-class="{'notNull': projector.scale != 0}">{{ projector.scale }}</span>
<!-- scroll -->
<div class="btn-group">
<a ng-click="projector.controlProjector('scroll', 'down')"
class="btn btn-default btn-sm"
title="{{ 'Scroll up' | translate}}">
<i class="fa fa-arrow-up"></i>
</a>
<a ng-click="projector.controlProjector('scroll', 'up')"
class="btn btn-default btn-sm"
title="{{ 'Scroll down' | translate}}">
<i class="fa fa-arrow-down"></i>
</a>
<a ng-click="projector.controlProjector('scroll', 'reset')"
class="btn btn-default btn-sm"
title="{{ 'Reset scrolling' | translate}}">
<i class="fa fa-undo"></i>
</a>
</div>
<span ng-class="{'notNull': projector.scroll != 0}">{{ projector.scroll }}</span>
</div>
<!-- Default, BC, Blank -->
<div class="middle">
<div class="btn-group">
<button class="btn btn-sm" ng-class="broadcast == projector.id ? 'btn-danger' : 'btn-default'"
ng-click="projector.toggleBroadcast(projector)"
ng-disabled="broadcast > 0 && broadcast != projector.id"
uib-tooltip="{{ 'Broadcast the content of this projector to all other projectors.' | translate }}"
tooltip-placement="bottom">
<i class="fa fa-bullhorn"></i>
<translate>Broadcast</translate>
</button>
<button class="btn btn-sm" ng-class="projector.blank ? 'btn-danger' : 'btn-default'"
ng-click="projector.toggleBlank(projector)"
ng-disabled="broadcast > 0 && broadcast != projector.id">
<i class="fa" ng-class="projector.blank ? 'fa-square' : 'fa-square-o'"></i>
<translate>Blank</translate>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -1 +0,0 @@
<input type="password" ng-model="model[options.key]" class="form-control">

View File

@ -1,312 +0,0 @@
<div class="details">
<div ng-controller="ProjectorControlCtrl">
<!-- live view -->
<div class="section" os-perms="core.can_see_projector">
<a href="#" ng-click="isLiveViewClosed = !isLiveViewClosed">
<i class="fa toggle-icon" ng-class="isLiveViewClosed ? 'fa-angle-down' : 'fa-angle-up'"></i>
<h4 translate>Live view</h4>
</a>
<div uib-collapse="isLiveViewClosed" ng-cloak>
<style>
.col2 #iframe_sidebar {
width: {{ active_projector.width }}px;
height: {{ active_projector.height }}px;
-moz-transform: scale({{ scale }});
-webkit-transform: scale({{ scale }});
-o-transform: scale({{ scale }});
transform: scale({{ scale }});
/* IE8+ - must be on one line, unfortunately */
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11={{ scale }}, M12=0, M21=0, M22={{ scale }}, SizingMethod='auto expand')";
}
.col2 #iframewrapper_sidebar {
height: {{ iframeHeight }}px;
}
.col2 #iframeoverlay_sidebar {
height: {{ iframeHeight }}px;
}
</style>
<div class="projectorSelector">
<div>
<div ng-show="projectors.length > 1" uib-dropdown>
<button class="btn btn-default btn-sm dropdown-toggle" id="menuProjector" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true">
{{ active_projector.name | translate }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-entries" aria-labelledby="menuProjector">
<li ng-repeat="projector in projectors | orderBy:'id'">
<a href ng-class="{'projected': projector === active_projector}"
ng-click="changeProjector(projector)">
<i ng-show="projector === active_projector" class="fa fa-check"></i>
{{ projector.name | translate }}
<i ng-show="projector.id == broadcast" class="fa fa-star-o spacer-left"></i>
</a>
</li>
</ul>
</div>
<div os-perms="core.can_manage_projector">
<button class="btn btn-sm" ng-click="active_projector.toggleBlank()" ng-hide="projectors.length > 1"
ng-class="active_projector.blank ? 'btn-danger' : 'btn-default'">
<i class="fa" ng-class="active_projector.blank ? 'fa-square' : 'fa-square-o'"></i>
<translate>Blank</translate>
</button>
</div>
<a os-perms="core.can_manage_projector" class="btn btn-primary btn-sm manageBtn" ui-sref="manage-projectors">
<i class="fa fa-cog fa-lg"></i>
<translate>Manage</translate>
</a>
</div>
</div>
<a ui-sref="projector({id: active_projector.id })" target="_blank">
<div class="iframewrapper" id="iframewrapper_sidebar">
<iframe class="iframe" id="iframe_sidebar" ng-src="{{ '/real-projector/' + active_projector.id + '/'}}" frameborder="0"></iframe>
<div class="iframeoverlay" id="iframeoverlay_sidebar"></div>
</div>
</a>
<!-- projector control buttons -->
<div os-perms="core.can_manage_projector" class="nobr">
<!-- edit -->
<a ng-click="editCurrentSlide(active_projector)"
ng-disabled="!active_projector.getFormOrStateForCurrentSlide()"
class="btn btn-default btn-sm"
title="{{ 'Edit current slide' | translate}}">
<i class="fa fa-pencil"></i>
</a>
<!-- scale -->
<div class="btn-group">
<a ng-click="active_projector.controlProjector('scale', 'down')"
class="btn btn-default btn-sm"
title="{{ 'Smaller' | translate}}">
<i class="fa fa-search-minus"></i>
</a>
<a ng-click="active_projector.controlProjector('scale', 'up')"
class="btn btn-default btn-sm"
title="{{ 'Bigger' | translate}}">
<i class="fa fa-search-plus"></i>
</a>
<a ng-click="active_projector.controlProjector('scale', 'reset')"
class="btn btn-default btn-sm"
title="{{ 'Reset scaling' | translate}}">
<i class="fa fa-undo"></i>
</a>
</div>
<span ng-class="{ 'notNull': active_projector.scale != 0 }">{{ active_projector.scale }}</span>
<!-- scroll -->
<div class="btn-group">
<a ng-click="active_projector.controlProjector('scroll', 'down')"
class="btn btn-default btn-sm"
title="{{ 'Scroll up' | translate}}">
<i class="fa fa-arrow-up"></i>
</a>
<a ng-click="active_projector.controlProjector('scroll', 'up')"
class="btn btn-default btn-sm"
title="{{ 'Scroll down' | translate}}">
<i class="fa fa-arrow-down"></i>
</a>
<a ng-click="active_projector.controlProjector('scroll', 'reset')"
class="btn btn-default btn-sm"
title="{{ 'Reset scrolling' | translate}}">
<i class="fa fa-undo"></i>
</a>
</div>
<span ng-class="{ 'notNull': active_projector.scroll != 0 }">{{ active_projector.scroll }}</span>
</div>
</div>
</div>
<!-- countdowns -->
<div class="section" os-perms="core.can_manage_projector">
<a href="#" ng-click="isCountdowns = !isCountdowns">
<i class="fa toggle-icon" ng-class="isCountdowns ? 'fa-angle-up' : 'fa-angle-down'"></i>
<h4 translate>Countdowns</h4>
</a>
<div uib-collapse="!isCountdowns" ng-cloak>
<div ng-repeat="countdown in countdowns | orderBy: 'index'" id="countdown{{countdown.id}}" class="countdown panel panel-default">
<div class="panel-heading">
<span ng-if="countdown.description">{{ countdown.description }}</span>
<span ng-if="!countdown.description">Countdown {{ $index +1 }}</span>
<!-- remove countdown button -->
<button type="button" class="close"
ng-click="removeCountdown(countdown)"
title="{{ 'Remove countdown' | translate}}">
<i class="fa fa-times"></i>
</button>
<!-- edit countdown button -->
<button type="button" class="close icons"
ng-click="countdown.editFlag=true; countdown.new_description = countdown.description;"
title="{{ 'Edit countdown' | translate}}">
<i class="fa fa-pencil"></i>
</button>
<a ui-sref="core.countdown.detail({id: countdown.id})" class="close icons" target="_blank"
uib-tooltip="{{ 'Open countdown in fullscreen' | translate }}">
<i class="fa fa-arrows-alt"></i>
</a>
</div>
<div class="panel-body"
ng-class="{ 'projected': isProjected(countdown).length }">
<projector-button model="countdown" default-projector-id="countdownDefaultProjectorId"></projector-button>
&nbsp;&nbsp;
<!-- countdown controls -->
<a class="btn btn-default vcenter"
ng-click="countdown.reset()"
ng-class="{ 'disabled': !countdown.running && countdown.default_time == countdown.countdown_time }"
title="{{ 'Reset countdown' | translate}}">
<i class="fa fa-stop"></i>
</a>
<a ng-if="!countdown.running" class="btn btn-default vcenter"
ng-click="countdown.start()"
title="{{ 'Start' | translate}}">
<i class="fa fa-play"></i>
<i ng-if="countdown.running" class="fa fa-pause"></i>
</a>
<a ng-if="countdown.running" class="btn btn-default vcenter"
ng-click="countdown.stop()"
title="{{ 'Pause' | translate}}">
<i class="fa fa-pause"></i>
</a>
<span ng-if="!countdown.editTime" class="countdown_timer vcenter"
ng-class="{
'negative': countdown.seconds <= 0,
'warning_time': countdown.seconds <= config('agenda_countdown_warning_time') && countdown.seconds > 0 }">
{{ countdown.seconds | osSecondsToTime }}
</span>
<!-- edit countdown form -->
<form ng-show="countdown.editFlag"
ng-submit="editCountdown(countdown)">
<div class="form-group">
<label translate>Description</label>
<input ng-model="countdown.new_description" type="text" class="form-control input-sm">
</div>
<div class="form-group">
<label translate>Start time</label>
<div class="input-group">
<input data-ng-model="countdown.default_time" min-sec-format
type="text" placeholder="mm:ss" class="form-control input-sm">
</div>
</div>
<button type="submit" title="{{ 'Save' | translate}}"
class="btn btn-sm btn-primary">
<i class="fa fa-check"></i>
</button>
<button ng-click="countdown.editFlag=false;"
title="{{ 'Cancel' | translate}}"
class="btn btn-default btn-sm">
<i class="fa fa-times"></i>
</button>
</form>
</div>
</div>
<!-- Add countdown button -->
<a ng-click="addCountdown()"
class="btn btn-default btn-sm"
title="{{ 'Add countdown' | translate}}">
<i class="fa fa-plus"></i> <translate>Add new countdown</translate>
</a>
</div>
</div>
<!-- messages -->
<div class="section" os-perms="core.can_manage_projector">
<a href="#" ng-click="isMessages = !isMessages">
<i class="fa toggle-icon" ng-class="isMessages ? 'fa-angle-up' : 'fa-angle-down'"></i>
<h4 translate>Messages</h4>
</a>
<div uib-collapse="!isMessages" ng-cloak>
<div ng-repeat="message in messages" id="message{{message.id}}" class="message panel panel-default">
<div class="panel-heading">
<span>{{ 'Message' | translate }} {{ $index + 1 }}</span>
<!-- remove message button -->
<button type="button" class="close"
ng-click="removeMessage(message)"
title="{{ 'Remove message' | translate}}">
<i class="fa fa-times"></i>
</button>
<button type="button" class="close editicon"
ng-click="editMessage(message)"
title="{{ 'Edit message' | translate}}">
<i class="fa fa-pencil"></i>
</button>
</div>
<div class="panel-body"
ng-class="{ 'projected': isProjected(message).length }">
<projector-button model="message" default-projector-id="messageDefaultProjectorId"></projector-button>
&nbsp;&nbsp;
<div class="innermessage" ng-bind-html="message.message | trusted"></div>
</div>
</div>
<!-- Add message button -->
<a ng-click="addMessage()"
class="btn btn-default btn-sm"
title="{{ 'Add message' | translate}}">
<i class="fa fa-plus"></i> <translate>Add new message</translate>
</a>
</div>
</div>
<!-- list of speakers overlay -->
<div class="section" os-perms="core.can_manage_projector">
<a href="#" ng-click="isSpeakerList = !isSpeakerList">
<i class="fa toggle-icon" ng-class="isSpeakerList ? 'fa-angle-up' : 'fa-angle-down'"></i>
<h4 translate>List of speakers</h4>
</a>
<div uib-collapse="!isSpeakerList" ng-cloak>
<!-- Current list of speakers projector button -->
<div class="btn-group button" uib-dropdown
uib-tooltip="{{ 'Project the current list of speakers' | translate }}"
os-perms="core.can_manage_projector">
<button type="button" class="btn btn-default btn-sm"
ng-click="currentListOfSpeakers.project(listOfSpeakersDefaultProjectorId, currentListOfSpeakersAsOverlay)"
ng-class="{ 'btn-primary': currentListOfSpeakers.isProjected().length && inArray(currentListOfSpeakers.isProjected(), listOfSpeakersDefaultProjectorId)}">
<i class="fa fa-video-camera"></i>
<translate>Current list of speakers</translate>
</button>
<button type="button" class="btn btn-default btn-sm slimDropDown"
ng-if="projectors.length > 1"
uib-dropdown-toggle
ng-class="{ 'btn-primary': currentListOfSpeakers.isProjected().length && !inArray(currentListOfSpeakers.isProjected(), listOfSpeakersDefaultProjectorId)}">
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="split-button" ng-if="projectors.length > 1">
<li role="menuitem">
<a href="" ng-click="setOverlay(false); $event.stopPropagation();">
<i class="fa" ng-class="currentListOfSpeakersAsOverlay ? 'fa-circle-o' : 'fa-check-circle-o'"></i>
<translate>Project as slide</translate>
</a>
</li>
<li role="menuitem">
<a href="" ng-click="setOverlay(true); $event.stopPropagation();">
<i class="fa" ng-class="currentListOfSpeakersAsOverlay ? 'fa-check-circle-o' : 'fa-circle-o'"></i>
<translate>Project as overlay</translate>
</a>
</li>
<li class="divider"></li>
<li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'">
<a href="" ng-click="currentListOfSpeakers.project(projector.id, currentListOfSpeakersAsOverlay)"
ng-class="{ 'projected': inArray(currentListOfSpeakers.isProjected(), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(currentListOfSpeakers.isProjected(), projector.id)"></i>
{{ projector.name | translate }}
<span ng-if="projector.id == listOfSpeakersDefaultProjectorId">(<translate>Default</translate>)</span>
</a>
</li>
</ul>
</div>
<button os-perms="agenda.can_manage"
ng-disabled="!currentListOfSpeakersItem()"
ng-click="goToListOfSpeakers()" class="btn btn-default btn-sm"
uib-tooltip="{{ 'Manage the current list of speakers' | translate}}">
<i class="fa fa-microphone"></i>
</button>
</div>
</div>
</div><!-- end div ProjectorControlCtrl -->
</div>

View File

@ -1,12 +0,0 @@
<h1 translate>Edit message</h1>
<form name="messageForm" ng-submit="save(model)" novalidate>
<formly-form model="model" fields="formFields">
<button type="submit" ng-disabled="motionForm.$invalid" class="btn btn-primary" translate>
Save
</button>
<button type="button" ng-click="closeThisDialog()" class="btn btn-default" translate>
Cancel
</button>
</formly-form>
</form>

View File

@ -1,17 +0,0 @@
<div class="form-group">
<div ng-if="to.label">
<label class="control-label">
{{ to.label | translate }}
</label>
</div>
<div class="btn-group">
<label ng-repeat="option in to.options" class="btn btn-default btn-sm"
ng-class="{active: (model[options.key] === option.value)}"
ng-disabled="option.disabled">
<input type="radio" ng-value="option.value"
ng-model="model[options.key]" ng-checked="model[options.key] === option.value"
ng-disabled="option.disabled" ng-change="to.change(option.value)">
{{ option.name | translate }}
</label>
</div>
</div>

View File

@ -1,10 +0,0 @@
<!-- custom angular formly template for angular-chosen multiple form field -->
<select multiple chosen
data-ng-model="model[options.key]"
ng-required="{{ to.required }}"
ng-options="{{ to.ngOptions }}"
search-contains="true"
placeholder-text-multiple="'{{ to.placeholder }}'"
no-results-text="'No results available ...' | translate"
class="form-control">
</select>

View File

@ -1,12 +0,0 @@
<!-- custom angular formly template for angular-chosen single form field -->
<select chosen
data-ng-model="model[options.key]"
ng-required="{{ to.required }}"
ng-options="{{ to.ngOptions }}"
allow-single-deselect="true"
search-contains="true"
placeholder-text-single="'{{ to.placeholder }}'"
no-results-text="'No results available ...' | translate"
class="form-control">
<option value=""></option>
</select>

View File

@ -1,4 +0,0 @@
<div ng-controller="SlideClockCtrl" id="currentTime">
<i class="fa fa-clock-o"></i>
{{ servertime | date:'HH:mm' }}
</div>

View File

@ -1,9 +0,0 @@
<div ng-controller="SlideCountdownCtrl">
<div class="countdown well pull-right"
ng-class="{
'negative': countdown.seconds <= 0,
'warning_time': countdown.seconds <= config('agenda_countdown_warning_time') && countdown.seconds > 0 }">
{{ countdown.seconds | osSecondsToTime}}
<div class="description">{{ countdown.description }}</div>
</div>
</div>

View File

@ -1,4 +0,0 @@
<div ng-controller="SlideMessageCtrl">
<div class="message_background"></div>
<div class="message well" ng-class="{'identify': element.identify}" ng-bind-html="(element.identify ? identifyMessage : message.message) | trusted"></div>
</div>

Some files were not shown because too many files have changed in this diff Show More