Reworked all motions templates.
motion detail: - added progres bar for motionpoll - added support/unsupport function - show log motion list: - added state filter - added css animations for enter/leave motion form: - use angular-formly (instead of old ng-fab-forms with no angular 1.4.x support) general: - Workflow states use new field 'css_class' (instead of unused 'icon'). Added migration file. - added 'allowed_actions' to rest api for each motion (by Norman) - updated all JavaScript dependencies (bower.json)
This commit is contained in:
parent
c379544e97
commit
ed72a90306
39
bower.json
39
bower.json
@ -2,29 +2,30 @@
|
|||||||
"name": "OpenSlides",
|
"name": "OpenSlides",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "~3.0.1",
|
"lodash": "~3.10.0",
|
||||||
"jquery": "~2.1.4",
|
"jquery": "~2.1.4",
|
||||||
"jquery.cookie": "~1.4.1",
|
"jquery.cookie": "~1.4.1",
|
||||||
"bootstrap-css-only": "~3.3.4",
|
"bootstrap-css-only": "~3.3.5",
|
||||||
"angular": "~1.3.15",
|
"angular": "~1.4.7",
|
||||||
"angular-bootstrap": "~0.14.2",
|
"angular-messages": "~1.4.7",
|
||||||
"angular-messages": "~1.3.15",
|
"angular-animate": "~1.4.7",
|
||||||
"angular-animate": "~1.3.15",
|
"angular-sanitize": "~1.4.7",
|
||||||
"angular-csv-import": "~0.0.15",
|
"angular-bootstrap": "~0.14.3",
|
||||||
"angular-loading-bar": "~0.7.1",
|
"angular-csv-import": "~0.0.26",
|
||||||
"angular-ui-router": "~0.2.13",
|
"angular-formly-templates-bootstrap": "~6.1.5",
|
||||||
"angular-ui-select": "~0.13",
|
"angular-formly": "~7.3.2",
|
||||||
"angular-ui-switch": "~0.1.0",
|
"angular-loading-bar": "~0.8.0",
|
||||||
"angular-ui-tree": "~2.2.0",
|
"angular-ui-router": "~0.2.15",
|
||||||
"angular-gettext": "~2.0.2",
|
"angular-ui-select": "~0.13.1",
|
||||||
"angular-sanitize": "~1.3.15",
|
"angular-ui-switch": "~0.1.1",
|
||||||
|
"angular-ui-tree": "~2.10.0",
|
||||||
|
"angular-gettext": "~2.1.2",
|
||||||
"angular-xeditable": "~0.1.9",
|
"angular-xeditable": "~0.1.9",
|
||||||
"ng-fab-form": "~1.2.7",
|
"ngBootbox": "~0.1.2",
|
||||||
"ngBootbox": "~0.0.5",
|
|
||||||
"sockjs": "~0.3.4",
|
"sockjs": "~0.3.4",
|
||||||
"font-awesome-bower": "4.3.0",
|
"font-awesome-bower": "~4.4.0",
|
||||||
"js-data": "~2.3.0",
|
"js-data": "~2.8.1",
|
||||||
"js-data-angular": "~3.0.0",
|
"js-data-angular": "~3.1.0",
|
||||||
"ng-file-upload": "~9.1.2"
|
"ng-file-upload": "~9.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,92 +49,49 @@ body {
|
|||||||
color: red;
|
color: red;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.spacer {
|
.spacer, .spacer-top {
|
||||||
margin-top: 7px;
|
margin-top: 7px;
|
||||||
}
|
}
|
||||||
|
.spacer-right {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
.hoverActions {
|
.hoverActions {
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
}
|
}
|
||||||
.hiddenDiv {
|
.hiddenDiv {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
/* override bootstraps's progress bar for poll results*/
|
||||||
|
.progress {
|
||||||
|
height: 12px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
/* voting results */
|
||||||
|
.result_label {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO: used by ng-fab-forms */
|
|
||||||
.validation-success {
|
|
||||||
opacity: 1;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
right: -7.2px;
|
|
||||||
bottom: -7.2px;
|
|
||||||
font-size: 28.8px;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
line-height: 36px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 36px;
|
|
||||||
color: #62b14c;
|
|
||||||
transition: all ease-out 0.32s; }
|
|
||||||
.validation-success:after {
|
|
||||||
display: block;
|
|
||||||
content: '\e013';
|
|
||||||
font-family: 'Glyphicons Halflings'; }
|
|
||||||
.validation-success.ng-hide {
|
|
||||||
transition-delay: 0s;
|
|
||||||
transition: all ease-out 0.12s;
|
|
||||||
opacity: 0;
|
|
||||||
transform: rotate(360deg); }
|
|
||||||
|
|
||||||
.ng-hide-remove li {
|
/* ngAnimate classes */
|
||||||
opacity: 0; }
|
.animate-item.ng-enter {
|
||||||
|
-webkit-animation: fade-in 1s linear;
|
||||||
|
animation: fade-in 1.5s linear;
|
||||||
|
}
|
||||||
|
.animate-item.ng-leave {
|
||||||
|
-webkit-animation: fade-out 1s linear;
|
||||||
|
animation: fade-out 1.5s linear;
|
||||||
|
}
|
||||||
|
|
||||||
.validation {
|
@keyframes fade-out {
|
||||||
color: #fff;
|
0% { opacity: 1; background: none; }
|
||||||
margin: 0;
|
25% { opacity: 1; background: #f8efc0; }
|
||||||
position: relative;
|
100% { opacity: 0; background: none; }
|
||||||
font-size: 14px;
|
}
|
||||||
overflow: visible;
|
@keyframes fade-in {
|
||||||
background: #c00640; }
|
0% { opacity: 0; background: none; }
|
||||||
.validation ul {
|
25% { opacity: 1; background: #dff0d8; }
|
||||||
display: block;
|
100% { opacity: 1; background: none; }
|
||||||
overflow: hidden; }
|
}
|
||||||
.validation li {
|
|
||||||
display: block;
|
|
||||||
line-height: 1;
|
|
||||||
background: #c00640;
|
|
||||||
position: absolute;
|
|
||||||
right: -4px;
|
|
||||||
top: -10px;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 2px 10px;
|
|
||||||
color: #fff;
|
|
||||||
transform: rotate(0deg);
|
|
||||||
transition: all ease-in 0.2s;
|
|
||||||
opacity: 1;
|
|
||||||
transition-delay: 0s; }
|
|
||||||
.validation li.ng-enter {
|
|
||||||
opacity: 0;
|
|
||||||
top: 0; }
|
|
||||||
.validation li.ng-leave {
|
|
||||||
transition: all ease-in 0s;
|
|
||||||
opacity: 0; }
|
|
||||||
*:focus + .validation li {
|
|
||||||
background-color: #63bff8 !important; }
|
|
||||||
|
|
||||||
input.ng-touched.ng-invalid:not(.ng-valid), textarea.ng-touched.ng-invalid:not(.ng-valid), select.ng-touched.ng-invalid:not(.ng-valid) {
|
|
||||||
border-color: #c00640; }
|
|
||||||
input:focus, input:focus.ng-touched.ng-invalid:not(.ng-valid), textarea:focus, textarea:focus.ng-touched.ng-invalid:not(.ng-valid), select:focus, select:focus.ng-touched.ng-invalid:not(.ng-valid) {
|
|
||||||
border-color: #63bff8; }
|
|
||||||
input.ng-valid-required.ng-valid:not(.ng-invalid), textarea.ng-valid-required.ng-valid:not(.ng-invalid), select.ng-valid-required.ng-valid:not(.ng-invalid) {
|
|
||||||
border-color: #62b14c; }
|
|
||||||
|
|
||||||
form[class*="ng-invalid"] button.btn[type=submit] {
|
|
||||||
background: #63bff8;
|
|
||||||
transition: none; }
|
|
||||||
|
|
||||||
form button.btn[type=submit] {
|
|
||||||
transition: all ease-in 0.5s;
|
|
||||||
background: #62b14c; }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -296,9 +253,9 @@ tr.selected td {
|
|||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
.smallhr {
|
.smallhr {
|
||||||
margin-top: 2px;
|
margin-top: 5px;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 5px;
|
||||||
border-color: #333333;
|
border-color: #cccccc;
|
||||||
}
|
}
|
||||||
.resultcolumn {
|
.resultcolumn {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -6,8 +6,9 @@
|
|||||||
angular.module('OpenSlidesApp.core.site', [
|
angular.module('OpenSlidesApp.core.site', [
|
||||||
'OpenSlidesApp.core',
|
'OpenSlidesApp.core',
|
||||||
'ui.router',
|
'ui.router',
|
||||||
|
'formly',
|
||||||
|
'formlyBootstrap',
|
||||||
'ngBootbox',
|
'ngBootbox',
|
||||||
'ngFabForm',
|
|
||||||
'ngMessages',
|
'ngMessages',
|
||||||
'ngCsvImport',
|
'ngCsvImport',
|
||||||
'ngSanitize', // TODO: only use this in functions that need it.
|
'ngSanitize', // TODO: only use this in functions that need it.
|
||||||
@ -260,13 +261,6 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
$locationProvider.html5Mode(true);
|
$locationProvider.html5Mode(true);
|
||||||
})
|
})
|
||||||
|
|
||||||
// config for ng-fab-form
|
|
||||||
.config(function(ngFabFormProvider) {
|
|
||||||
ngFabFormProvider.extendConfig({
|
|
||||||
setAsteriskForRequiredLabel: true
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
// Helper to add ui.router states at runtime.
|
// Helper to add ui.router states at runtime.
|
||||||
// Needed for the django url_patterns.
|
// Needed for the django url_patterns.
|
||||||
.provider('runtimeStates', function($stateProvider) {
|
.provider('runtimeStates', function($stateProvider) {
|
||||||
@ -299,6 +293,26 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
editableOptions.theme = 'bs3';
|
editableOptions.theme = 'bs3';
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// angular formly config options
|
||||||
|
.run([
|
||||||
|
'formlyConfig',
|
||||||
|
function (formlyConfig) {
|
||||||
|
// NOTE: This next line is highly recommended. Otherwise Chrome's autocomplete will appear over your options!
|
||||||
|
formlyConfig.extras.removeChromeAutoComplete = true;
|
||||||
|
|
||||||
|
// Configure custom types
|
||||||
|
formlyConfig.setType({
|
||||||
|
name: 'ui-select-single',
|
||||||
|
extends: 'select',
|
||||||
|
templateUrl: 'static/templates/core/ui-select-single.html'
|
||||||
|
});
|
||||||
|
formlyConfig.setType({
|
||||||
|
name: 'ui-select-multiple',
|
||||||
|
extends: 'select',
|
||||||
|
templateUrl: 'static/templates/core/ui-select-multiple.html'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
// html-tag os-form-field to generate generic from fields
|
// html-tag os-form-field to generate generic from fields
|
||||||
// TODO: make it possible to use other fields then config fields
|
// TODO: make it possible to use other fields then config fields
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
<!-- custom angular formly template for ui-select multiple form field -->
|
||||||
|
<ui-select multiple data-ng-model="model[options.key]" data-required="{{to.required}}"
|
||||||
|
data-disabled="{{to.disabled}}" theme="bootstrap">
|
||||||
|
<ui-select-match placeholder="{{to.placeholder}}">
|
||||||
|
{{$item[to.labelProp]}}
|
||||||
|
</ui-select-match>
|
||||||
|
<ui-select-choices data-repeat="{{to.ngOptions}}">
|
||||||
|
<div ng-bind-html="option[to.labelProp] | highlight: $select.search"></div>
|
||||||
|
</ui-select-choices>
|
||||||
|
</ui-select>
|
10
openslides/core/static/templates/core/ui-select-single.html
Normal file
10
openslides/core/static/templates/core/ui-select-single.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!-- custom angular formly template for ui-select single form field -->
|
||||||
|
<ui-select data-ng-model="model[options.key]" data-required="{{to.required}}"
|
||||||
|
data-disabled="{{to.disabled}}" theme="bootstrap">
|
||||||
|
<ui-select-match placeholder="{{to.placeholder}}" data-allow-clear="true">
|
||||||
|
{{$select.selected[to.labelProp]}}
|
||||||
|
</ui-select-match>
|
||||||
|
<ui-select-choices data-repeat="{{to.ngOptions}}">
|
||||||
|
<div ng-bind-html="option[to.labelProp] | highlight: $select.search"></div>
|
||||||
|
</ui-select-choices>
|
||||||
|
</ui-select>
|
29
openslides/motions/migrations/0004_auto_20151105_2312.py
Normal file
29
openslides/motions/migrations/0004_auto_20151105_2312.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('motions', '0003_auto_20150904_2029'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='state',
|
||||||
|
old_name='icon',
|
||||||
|
new_name='css_class',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='state',
|
||||||
|
name='css_class',
|
||||||
|
field=models.CharField(max_length=255, default='primary'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='state',
|
||||||
|
name='workflow',
|
||||||
|
field=models.ForeignKey(to='motions.Workflow', related_name='states'),
|
||||||
|
),
|
||||||
|
]
|
@ -736,8 +736,12 @@ class State(RESTModelMixin, models.Model):
|
|||||||
next_states = models.ManyToManyField('self', symmetrical=False)
|
next_states = models.ManyToManyField('self', symmetrical=False)
|
||||||
"""A many-to-many relation to all states, that can be choosen from this state."""
|
"""A many-to-many relation to all states, that can be choosen from this state."""
|
||||||
|
|
||||||
icon = models.CharField(max_length=255)
|
css_class = models.CharField(max_length=255, default='primary')
|
||||||
"""A string representing the url to the icon-image."""
|
"""
|
||||||
|
A css class string for showing the state name in a coloured label based on bootstrap,
|
||||||
|
e.g. 'danger' (red), 'success' (green), 'warning' (yellow), 'default' (grey).
|
||||||
|
Default value is 'primary' (blue).
|
||||||
|
"""
|
||||||
|
|
||||||
required_permission_to_see = models.CharField(max_length=255, blank=True)
|
required_permission_to_see = models.CharField(max_length=255, blank=True)
|
||||||
"""
|
"""
|
||||||
@ -764,7 +768,6 @@ class State(RESTModelMixin, models.Model):
|
|||||||
This behavior can be changed by the form and view, e. g. via the
|
This behavior can be changed by the form and view, e. g. via the
|
||||||
MotionDisableVersioningMixin.
|
MotionDisableVersioningMixin.
|
||||||
"""
|
"""
|
||||||
# TODO: preferred_for = ChoiceField
|
|
||||||
|
|
||||||
leave_old_version_active = models.BooleanField(default=False)
|
leave_old_version_active = models.BooleanField(default=False)
|
||||||
"""If true, new versions are not automaticly set active."""
|
"""If true, new versions are not automaticly set active."""
|
||||||
|
@ -49,7 +49,7 @@ class StateSerializer(ModelSerializer):
|
|||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
'action_word',
|
'action_word',
|
||||||
'icon',
|
'css_class',
|
||||||
'required_permission_to_see',
|
'required_permission_to_see',
|
||||||
'allow_support',
|
'allow_support',
|
||||||
'allow_create_poll',
|
'allow_create_poll',
|
||||||
@ -77,9 +77,17 @@ class MotionLogSerializer(ModelSerializer):
|
|||||||
"""
|
"""
|
||||||
Serializer for motion.models.MotionLog objects.
|
Serializer for motion.models.MotionLog objects.
|
||||||
"""
|
"""
|
||||||
|
message = SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MotionLog
|
model = MotionLog
|
||||||
fields = ('message_list', 'person', 'time',)
|
fields = ('message_list', 'person', 'time', 'message',)
|
||||||
|
|
||||||
|
def get_message(self, obj):
|
||||||
|
"""
|
||||||
|
Concats the message parts to one string. Useful for smart template code.
|
||||||
|
"""
|
||||||
|
return str(obj)
|
||||||
|
|
||||||
|
|
||||||
class MotionPollSerializer(ModelSerializer):
|
class MotionPollSerializer(ModelSerializer):
|
||||||
|
@ -189,13 +189,16 @@ def create_builtin_workflows(sender, **kwargs):
|
|||||||
allow_submitter_edit=True)
|
allow_submitter_edit=True)
|
||||||
state_1_2 = State.objects.create(name=ugettext_noop('accepted'),
|
state_1_2 = State.objects.create(name=ugettext_noop('accepted'),
|
||||||
workflow=workflow_1,
|
workflow=workflow_1,
|
||||||
action_word=ugettext_noop('Accept'))
|
action_word=ugettext_noop('Accept'),
|
||||||
|
css_class='success')
|
||||||
state_1_3 = State.objects.create(name=ugettext_noop('rejected'),
|
state_1_3 = State.objects.create(name=ugettext_noop('rejected'),
|
||||||
workflow=workflow_1,
|
workflow=workflow_1,
|
||||||
action_word=ugettext_noop('Reject'))
|
action_word=ugettext_noop('Reject'),
|
||||||
|
css_class='danger')
|
||||||
state_1_4 = State.objects.create(name=ugettext_noop('not decided'),
|
state_1_4 = State.objects.create(name=ugettext_noop('not decided'),
|
||||||
workflow=workflow_1,
|
workflow=workflow_1,
|
||||||
action_word=ugettext_noop('Do not decide'))
|
action_word=ugettext_noop('Do not decide'),
|
||||||
|
css_class='default')
|
||||||
state_1_1.next_states.add(state_1_2, state_1_3, state_1_4)
|
state_1_1.next_states.add(state_1_2, state_1_3, state_1_4)
|
||||||
workflow_1.first_state = state_1_1
|
workflow_1.first_state = state_1_1
|
||||||
workflow_1.save()
|
workflow_1.save()
|
||||||
@ -216,35 +219,43 @@ def create_builtin_workflows(sender, **kwargs):
|
|||||||
state_2_3 = State.objects.create(name=ugettext_noop('accepted'),
|
state_2_3 = State.objects.create(name=ugettext_noop('accepted'),
|
||||||
workflow=workflow_2,
|
workflow=workflow_2,
|
||||||
action_word=ugettext_noop('Accept'),
|
action_word=ugettext_noop('Accept'),
|
||||||
versioning=True)
|
versioning=True,
|
||||||
|
css_class='success')
|
||||||
state_2_4 = State.objects.create(name=ugettext_noop('rejected'),
|
state_2_4 = State.objects.create(name=ugettext_noop('rejected'),
|
||||||
workflow=workflow_2,
|
workflow=workflow_2,
|
||||||
action_word=ugettext_noop('Reject'),
|
action_word=ugettext_noop('Reject'),
|
||||||
versioning=True)
|
versioning=True,
|
||||||
|
css_class='danger')
|
||||||
state_2_5 = State.objects.create(name=ugettext_noop('withdrawed'),
|
state_2_5 = State.objects.create(name=ugettext_noop('withdrawed'),
|
||||||
workflow=workflow_2,
|
workflow=workflow_2,
|
||||||
action_word=ugettext_noop('Withdraw'),
|
action_word=ugettext_noop('Withdraw'),
|
||||||
versioning=True)
|
versioning=True,
|
||||||
|
css_class='default')
|
||||||
state_2_6 = State.objects.create(name=ugettext_noop('adjourned'),
|
state_2_6 = State.objects.create(name=ugettext_noop('adjourned'),
|
||||||
workflow=workflow_2,
|
workflow=workflow_2,
|
||||||
action_word=ugettext_noop('Adjourn'),
|
action_word=ugettext_noop('Adjourn'),
|
||||||
versioning=True)
|
versioning=True,
|
||||||
|
css_class='default')
|
||||||
state_2_7 = State.objects.create(name=ugettext_noop('not concerned'),
|
state_2_7 = State.objects.create(name=ugettext_noop('not concerned'),
|
||||||
workflow=workflow_2,
|
workflow=workflow_2,
|
||||||
action_word=ugettext_noop('Do not concern'),
|
action_word=ugettext_noop('Do not concern'),
|
||||||
versioning=True)
|
versioning=True,
|
||||||
|
css_class='default')
|
||||||
state_2_8 = State.objects.create(name=ugettext_noop('commited a bill'),
|
state_2_8 = State.objects.create(name=ugettext_noop('commited a bill'),
|
||||||
workflow=workflow_2,
|
workflow=workflow_2,
|
||||||
action_word=ugettext_noop('Commit a bill'),
|
action_word=ugettext_noop('Commit a bill'),
|
||||||
versioning=True)
|
versioning=True,
|
||||||
|
css_class='default')
|
||||||
state_2_9 = State.objects.create(name=ugettext_noop('needs review'),
|
state_2_9 = State.objects.create(name=ugettext_noop('needs review'),
|
||||||
workflow=workflow_2,
|
workflow=workflow_2,
|
||||||
action_word=ugettext_noop('Needs review'),
|
action_word=ugettext_noop('Needs review'),
|
||||||
versioning=True)
|
versioning=True,
|
||||||
|
css_class='default')
|
||||||
state_2_10 = State.objects.create(name=ugettext_noop('rejected (not authorized)'),
|
state_2_10 = State.objects.create(name=ugettext_noop('rejected (not authorized)'),
|
||||||
workflow=workflow_2,
|
workflow=workflow_2,
|
||||||
action_word=ugettext_noop('Reject (not authorized)'),
|
action_word=ugettext_noop('Reject (not authorized)'),
|
||||||
versioning=True)
|
versioning=True,
|
||||||
|
css_class='default')
|
||||||
state_2_1.next_states.add(state_2_2, state_2_5, state_2_10)
|
state_2_1.next_states.add(state_2_2, state_2_5, state_2_10)
|
||||||
state_2_2.next_states.add(state_2_3, state_2_4, state_2_5, state_2_6, state_2_7, state_2_8, state_2_9)
|
state_2_2.next_states.add(state_2_3, state_2_4, state_2_5, state_2_6, state_2_7, state_2_8, state_2_9)
|
||||||
workflow_2.first_state = state_2_1
|
workflow_2.first_state = state_2_1
|
||||||
|
@ -40,7 +40,7 @@ angular.module('OpenSlidesApp.motions', [])
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
// Load all MotionWorkflows at stateup
|
// Load all MotionWorkflows at startup
|
||||||
.run([
|
.run([
|
||||||
'Workflow',
|
'Workflow',
|
||||||
function (Workflow) {
|
function (Workflow) {
|
||||||
@ -53,7 +53,7 @@ angular.module('OpenSlidesApp.motions', [])
|
|||||||
'Config',
|
'Config',
|
||||||
function (DS, Config) {
|
function (DS, Config) {
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: 'motions/poll',
|
name: 'motions/motionpoll',
|
||||||
relations: {
|
relations: {
|
||||||
belongsTo: {
|
belongsTo: {
|
||||||
'motions/motion': {
|
'motions/motion': {
|
||||||
@ -63,61 +63,91 @@ angular.module('OpenSlidesApp.motions', [])
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getYesPercent: function () {
|
getYesPercent: function (valueOnly) {
|
||||||
var config = Config.get('motions_poll_100_percent_base').value;
|
var config = Config.get('motions_poll_100_percent_base').value;
|
||||||
if (config == "WITHOUT_INVALID" && this.votesvalid > 0) {
|
var returnvalue;
|
||||||
return "(" + Math.round(this.yes * 100 / this.votesvalid * 10) / 10 + " %)";
|
if (config == "WITHOUT_INVALID" && this.votesvalid > 0 && this.yes >= 0) {
|
||||||
} else if (config == "WITH_INVALID" && this.votescast > 0) {
|
returnvalue = Math.round(this.yes * 100 / this.votesvalid * 10) / 10;
|
||||||
return "(" + Math.round(this.yes * 100 / (this.votescast) * 10) / 10 + " %)";
|
} else if (config == "WITH_INVALID" && this.votescast > 0 && this.yes >= 0) {
|
||||||
|
returnvalue = Math.round(this.yes * 100 / (this.votescast) * 10) / 10;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
returnvalue = null;
|
||||||
}
|
}
|
||||||
|
if (!valueOnly && returnvalue != null) {
|
||||||
|
returnvalue = "(" + returnvalue + "%)";
|
||||||
|
}
|
||||||
|
return returnvalue;
|
||||||
},
|
},
|
||||||
getNoPercent: function () {
|
getNoPercent: function (valueOnly) {
|
||||||
var config = Config.get('motions_poll_100_percent_base').value;
|
var config = Config.get('motions_poll_100_percent_base').value;
|
||||||
if (config == "WITHOUT_INVALID" && this.votesvalid > 0) {
|
var returnvalue;
|
||||||
return "(" + Math.round(this.no * 100 / this.votesvalid * 10) / 10 + " %)";
|
if (config == "WITHOUT_INVALID" && this.votesvalid > 0 && this.no >= 0) {
|
||||||
} else if (config == "WITH_INVALID" && this.votescast > 0) {
|
returnvalue = Math.round(this.no * 100 / this.votesvalid * 10) / 10;
|
||||||
return "(" + Math.round(this.no * 100 / (this.votescast) * 10) / 10 + " %)";
|
} else if (config == "WITH_INVALID" && this.votescast > 0 && this.no >= 0) {
|
||||||
|
returnvalue = Math.round(this.no * 100 / (this.votescast) * 10) / 10;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
returnvalue = null;
|
||||||
}
|
}
|
||||||
|
if (!valueOnly && returnvalue != null) {
|
||||||
|
returnvalue = "(" + returnvalue + "%)";
|
||||||
|
}
|
||||||
|
return returnvalue;
|
||||||
},
|
},
|
||||||
getAbstainPercent: function () {
|
getAbstainPercent: function (valueOnly) {
|
||||||
var config = Config.get('motions_poll_100_percent_base').value;
|
var config = Config.get('motions_poll_100_percent_base').value;
|
||||||
if (config == "WITHOUT_INVALID" && this.votesvalid > 0) {
|
var returnvalue;
|
||||||
return "(" + Math.round(this.abstain * 100 / this.votesvalid * 10) / 10 + " %)";
|
if (config == "WITHOUT_INVALID" && this.votesvalid > 0 && this.abstain >= 0) {
|
||||||
} else if (config == "WITH_INVALID" && this.votescast > 0) {
|
returnvalue = Math.round(this.abstain * 100 / this.votesvalid * 10) / 10;
|
||||||
return "(" + Math.round(this.abstain * 100 / (this.votescast) * 10) / 10 + " %)";
|
} else if (config == "WITH_INVALID" && this.votescast > 0 && this.abstain >= 0) {
|
||||||
|
returnvalue = Math.round(this.abstain * 100 / (this.votescast) * 10) / 10;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
returnvalue = null;
|
||||||
}
|
}
|
||||||
|
if (!valueOnly && returnvalue != null) {
|
||||||
|
returnvalue = "(" + returnvalue + "%)";
|
||||||
|
}
|
||||||
|
return returnvalue;
|
||||||
},
|
},
|
||||||
getVotesValidPercent: function () {
|
getVotesValidPercent: function (valueOnly) {
|
||||||
var config = Config.get('motions_poll_100_percent_base').value;
|
var config = Config.get('motions_poll_100_percent_base').value;
|
||||||
if (config == "WITHOUT_INVALID") {
|
var returnvalue;
|
||||||
return "(100 %)";
|
if (config == "WITHOUT_INVALID" && this.votevalid >= 0) {
|
||||||
} else if (config == "WITH_INVALID") {
|
returnvalue = 100;
|
||||||
return "(" + Math.round(this.votesvalid * 100 / (this.votescast) * 10) / 10 + " %)";
|
} else if (config == "WITH_INVALID" && this.votevalid >= 0) {
|
||||||
|
returnvalue = Math.round(this.votesvalid * 100 / (this.votescast) * 10) / 10;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
returnvalue = null;
|
||||||
}
|
}
|
||||||
|
if (!valueOnly && returnvalue != null) {
|
||||||
|
returnvalue = "(" + returnvalue + "%)";
|
||||||
|
}
|
||||||
|
return returnvalue;
|
||||||
},
|
},
|
||||||
getVotesInvalidPercent: function () {
|
getVotesInvalidPercent: function (valueOnly) {
|
||||||
var config = Config.get('motions_poll_100_percent_base').value;
|
var config = Config.get('motions_poll_100_percent_base').value;
|
||||||
if (config == "WITH_INVALID") {
|
var returnvalue;
|
||||||
return "(" + Math.round(this.votesinvalid * 100 / (this.votescast) * 10) / 10 + " %)";
|
if (config == "WITH_INVALID" && this.voteinvalid >= 0) {
|
||||||
|
returnvalue = Math.round(this.votesinvalid * 100 / (this.votescast) * 10) / 10;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
returnvalue = null;
|
||||||
}
|
}
|
||||||
|
if (!valueOnly && returnvalue != null) {
|
||||||
|
returnvalue = "(" + returnvalue + "%)";
|
||||||
|
}
|
||||||
|
return returnvalue;
|
||||||
},
|
},
|
||||||
getVotesCastPercent: function () {
|
getVotesCastPercent: function (valueOnly) {
|
||||||
var config = Config.get('motions_poll_100_percent_base').value;
|
var config = Config.get('motions_poll_100_percent_base').value;
|
||||||
if (config == "WITH_INVALID") {
|
var returnvalue;
|
||||||
return "(100 %)";
|
if (config == "WITH_INVALID" && this.votecast >= 0) {
|
||||||
|
returnvalue = 100;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
returnvalue = null;
|
||||||
}
|
}
|
||||||
|
if (!valueOnly && returnvalue != null) {
|
||||||
|
returnvalue = "(" + returnvalue + "%)";
|
||||||
|
}
|
||||||
|
return returnvalue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -183,6 +213,10 @@ angular.module('OpenSlidesApp.motions', [])
|
|||||||
localField: 'tags',
|
localField: 'tags',
|
||||||
localKeys: 'tags_id',
|
localKeys: 'tags_id',
|
||||||
},
|
},
|
||||||
|
'mediafiles/mediafile': {
|
||||||
|
localField: 'attachments',
|
||||||
|
localKeys: 'attachments_id',
|
||||||
|
},
|
||||||
'users/user': [
|
'users/user': [
|
||||||
{
|
{
|
||||||
localField: 'submitters',
|
localField: 'submitters',
|
||||||
@ -193,7 +227,7 @@ angular.module('OpenSlidesApp.motions', [])
|
|||||||
localKeys: 'supporters_id',
|
localKeys: 'supporters_id',
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'motions/poll': {
|
'motions/motionpoll': {
|
||||||
localField: 'polls',
|
localField: 'polls',
|
||||||
foreignKey: 'motion_id',
|
foreignKey: 'motion_id',
|
||||||
}
|
}
|
||||||
@ -209,6 +243,144 @@ angular.module('OpenSlidesApp.motions', [])
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// Provide generic motion form fields for create and update view
|
||||||
|
.factory('MotionFormFieldFactory', [
|
||||||
|
'gettext',
|
||||||
|
'Category',
|
||||||
|
'Config',
|
||||||
|
'Mediafile',
|
||||||
|
'Tag',
|
||||||
|
'User',
|
||||||
|
'Workflow',
|
||||||
|
function (gettext, Category, Config, Mediafile, Tag, User, Workflow) {
|
||||||
|
return {
|
||||||
|
getFormFields: function () {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'identifier',
|
||||||
|
type: 'input',
|
||||||
|
templateOptions: {
|
||||||
|
label: gettext('Identifier')
|
||||||
|
},
|
||||||
|
hide: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'submitters_id',
|
||||||
|
type: 'ui-select-multiple',
|
||||||
|
templateOptions: {
|
||||||
|
label: gettext('Submitters'),
|
||||||
|
optionsAttr: 'bs-options',
|
||||||
|
options: User.getAll(),
|
||||||
|
ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search',
|
||||||
|
valueProp: 'id',
|
||||||
|
labelProp: 'full_name',
|
||||||
|
placeholder: gettext('Select or search a submitter...')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'title',
|
||||||
|
type: 'input',
|
||||||
|
templateOptions: {
|
||||||
|
label: gettext('Title'),
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'text',
|
||||||
|
type: 'textarea',
|
||||||
|
templateOptions: {
|
||||||
|
label: gettext('Text'),
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'reason',
|
||||||
|
type: 'textarea',
|
||||||
|
templateOptions: {
|
||||||
|
label: gettext('Reason')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'more',
|
||||||
|
type: 'checkbox',
|
||||||
|
templateOptions: {
|
||||||
|
label: gettext('Show extended fields')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'attachments_id',
|
||||||
|
type: 'ui-select-multiple',
|
||||||
|
templateOptions: {
|
||||||
|
label: gettext('Attachment'),
|
||||||
|
optionsAttr: 'bs-options',
|
||||||
|
options: Mediafile.getAll(),
|
||||||
|
ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search',
|
||||||
|
valueProp: 'id',
|
||||||
|
labelProp: 'title_or_filename',
|
||||||
|
placeholder: gettext('Select or search an attachment...')
|
||||||
|
},
|
||||||
|
hideExpression: '!model.more'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'category_id',
|
||||||
|
type: 'ui-select-single',
|
||||||
|
templateOptions: {
|
||||||
|
label: gettext('Category'),
|
||||||
|
optionsAttr: 'bs-options',
|
||||||
|
options: Category.getAll(),
|
||||||
|
ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search',
|
||||||
|
valueProp: 'id',
|
||||||
|
labelProp: 'name',
|
||||||
|
placeholder: gettext('Select or search a category...')
|
||||||
|
},
|
||||||
|
hideExpression: '!model.more'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tags_id',
|
||||||
|
type: 'ui-select-multiple',
|
||||||
|
templateOptions: {
|
||||||
|
label: gettext('Tags'),
|
||||||
|
optionsAttr: 'bs-options',
|
||||||
|
options: Tag.getAll(),
|
||||||
|
ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search',
|
||||||
|
valueProp: 'id',
|
||||||
|
labelProp: 'name',
|
||||||
|
placeholder: gettext('Select or search a tag...')
|
||||||
|
},
|
||||||
|
hideExpression: '!model.more'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'supporters_id',
|
||||||
|
type: 'ui-select-multiple',
|
||||||
|
templateOptions: {
|
||||||
|
label: gettext('Supporters'),
|
||||||
|
optionsAttr: 'bs-options',
|
||||||
|
options: User.getAll(),
|
||||||
|
ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search',
|
||||||
|
valueProp: 'id',
|
||||||
|
labelProp: 'full_name',
|
||||||
|
placeholder: gettext('Select or search a supporter...')
|
||||||
|
},
|
||||||
|
hideExpression: '!model.more'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'workflow_id',
|
||||||
|
type: 'ui-select-single',
|
||||||
|
templateOptions: {
|
||||||
|
label: gettext('Workflow'),
|
||||||
|
optionsAttr: 'bs-options',
|
||||||
|
options: Workflow.getAll(),
|
||||||
|
ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search',
|
||||||
|
valueProp: 'id',
|
||||||
|
labelProp: 'name',
|
||||||
|
placeholder: gettext('Select or search a workflow...')
|
||||||
|
},
|
||||||
|
hideExpression: '!model.more',
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
.factory('Category', ['DS', function(DS) {
|
.factory('Category', ['DS', function(DS) {
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: 'motions/category',
|
name: 'motions/category',
|
||||||
@ -252,6 +424,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
|||||||
categories: function(Category) {
|
categories: function(Category) {
|
||||||
return Category.findAll();
|
return Category.findAll();
|
||||||
},
|
},
|
||||||
|
tags: function(Tag) {
|
||||||
|
return Tag.findAll();
|
||||||
|
},
|
||||||
users: function(User) {
|
users: function(User) {
|
||||||
return User.findAll();
|
return User.findAll();
|
||||||
}
|
}
|
||||||
@ -259,9 +434,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
|||||||
})
|
})
|
||||||
.state('motions.motion.create', {
|
.state('motions.motion.create', {
|
||||||
resolve: {
|
resolve: {
|
||||||
items: function(Agenda) {
|
|
||||||
return Agenda.findAll();
|
|
||||||
},
|
|
||||||
categories: function(Category) {
|
categories: function(Category) {
|
||||||
return Category.findAll();
|
return Category.findAll();
|
||||||
},
|
},
|
||||||
@ -273,6 +445,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
|||||||
},
|
},
|
||||||
mediafiles: function(Mediafile) {
|
mediafiles: function(Mediafile) {
|
||||||
return Mediafile.findAll();
|
return Mediafile.findAll();
|
||||||
|
},
|
||||||
|
workflows: function(Workflow) {
|
||||||
|
return Workflow.findAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -287,9 +462,12 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
|||||||
users: function(User) {
|
users: function(User) {
|
||||||
return User.findAll();
|
return User.findAll();
|
||||||
},
|
},
|
||||||
|
mediafiles: function(Mediafile) {
|
||||||
|
return Mediafile.findAll();
|
||||||
|
},
|
||||||
tags: function(Tag) {
|
tags: function(Tag) {
|
||||||
return Tag.findAll();
|
return Tag.findAll();
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('motions.motion.detail.update', {
|
.state('motions.motion.detail.update', {
|
||||||
@ -297,9 +475,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
|||||||
'@motions.motion': {}
|
'@motions.motion': {}
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
items: function(Agenda) {
|
|
||||||
return Agenda.findAll();
|
|
||||||
},
|
|
||||||
categories: function(Category) {
|
categories: function(Category) {
|
||||||
return Category.findAll();
|
return Category.findAll();
|
||||||
},
|
},
|
||||||
@ -311,6 +486,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
|||||||
},
|
},
|
||||||
mediafiles: function(Mediafile) {
|
mediafiles: function(Mediafile) {
|
||||||
return Mediafile.findAll();
|
return Mediafile.findAll();
|
||||||
|
},
|
||||||
|
workflows: function(Workflow) {
|
||||||
|
return Workflow.findAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -351,11 +529,16 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
|||||||
'$state',
|
'$state',
|
||||||
'Motion',
|
'Motion',
|
||||||
'Category',
|
'Category',
|
||||||
|
'Tag',
|
||||||
|
'Workflow',
|
||||||
'User',
|
'User',
|
||||||
function($scope, $state, Motion, Category, User) {
|
function($scope, $state, Motion, Category, Tag, Workflow, User) {
|
||||||
Motion.bindAll({}, $scope, 'motions');
|
Motion.bindAll({}, $scope, 'motions');
|
||||||
Category.bindAll({}, $scope, 'categories');
|
Category.bindAll({}, $scope, 'categories');
|
||||||
|
Tag.bindAll({}, $scope, 'tags');
|
||||||
|
Workflow.bindAll({}, $scope, 'workflows');
|
||||||
User.bindAll({}, $scope, 'users');
|
User.bindAll({}, $scope, 'users');
|
||||||
|
$scope.alert = {};
|
||||||
|
|
||||||
// setup table sorting
|
// setup table sorting
|
||||||
$scope.sortColumn = 'identifier';
|
$scope.sortColumn = 'identifier';
|
||||||
@ -369,6 +552,21 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
|||||||
$scope.sortColumn = column;
|
$scope.sortColumn = column;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// collect all states of all workflows
|
||||||
|
// TODO: regard workflows only which are used by motions
|
||||||
|
$scope.states = [];
|
||||||
|
var workflows = Workflow.getAll();
|
||||||
|
angular.forEach(workflows, function (workflow) {
|
||||||
|
if (workflows.length > 1) {
|
||||||
|
var wf = {}
|
||||||
|
wf.name = "# "+workflow.name;
|
||||||
|
$scope.states.push(wf);
|
||||||
|
}
|
||||||
|
angular.forEach(workflow.states, function (state) {
|
||||||
|
$scope.states.push(state);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// hover edit actions
|
// hover edit actions
|
||||||
$scope.hoverIn = function () {
|
$scope.hoverIn = function () {
|
||||||
$scope.showEditActions = true;
|
$scope.showEditActions = true;
|
||||||
@ -432,24 +630,37 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
|||||||
|
|
||||||
.controller('MotionDetailCtrl', [
|
.controller('MotionDetailCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
|
'$http',
|
||||||
'Motion',
|
'Motion',
|
||||||
'Category',
|
'Category',
|
||||||
'Workflow',
|
'Mediafile',
|
||||||
|
'Tag',
|
||||||
'User',
|
'User',
|
||||||
|
'Workflow',
|
||||||
'motion',
|
'motion',
|
||||||
'$http',
|
function($scope, $http, Motion, Category, Mediafile, Tag, User, Workflow, motion) {
|
||||||
function($scope, Motion, Category, Workflow, User, motion, $http) {
|
|
||||||
Motion.bindOne(motion.id, $scope, 'motion');
|
Motion.bindOne(motion.id, $scope, 'motion');
|
||||||
Category.bindAll({}, $scope, 'categories');
|
Category.bindAll({}, $scope, 'categories');
|
||||||
Workflow.bindAll({}, $scope, 'workflows');
|
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||||
|
Tag.bindAll({}, $scope, 'tags');
|
||||||
User.bindAll({}, $scope, 'users');
|
User.bindAll({}, $scope, 'users');
|
||||||
|
Workflow.bindAll({}, $scope, 'workflows');
|
||||||
Motion.loadRelations(motion, 'agenda_item');
|
Motion.loadRelations(motion, 'agenda_item');
|
||||||
var state = motion.state;
|
// TODO: make 'motion.attachments' useable and itteratable in template
|
||||||
state.getNextStates()
|
// Motion.loadRelations(motion, 'attachments');
|
||||||
$scope.alert = {}; // TODO: show alert in template
|
|
||||||
|
|
||||||
$scope.update_state = function (state_id) {
|
$scope.alert = {};
|
||||||
$http.put('/rest/motions/motion/' + motion.id + '/set_state/', {'state': state_id});
|
$scope.isCollapsed = true;
|
||||||
|
|
||||||
|
$scope.support = function () {
|
||||||
|
$http.post('/rest/motions/motion/' + motion.id + '/support/');
|
||||||
|
}
|
||||||
|
$scope.unsupport = function () {
|
||||||
|
$http.delete('/rest/motions/motion/' + motion.id + '/support/');
|
||||||
|
console.log(motion);
|
||||||
|
}
|
||||||
|
$scope.update_state = function (state) {
|
||||||
|
$http.put('/rest/motions/motion/' + motion.id + '/set_state/', {'state': state.id});
|
||||||
}
|
}
|
||||||
$scope.reset_state = function (state_id) {
|
$scope.reset_state = function (state_id) {
|
||||||
$http.put('/rest/motions/motion/' + motion.id + '/set_state/', {});
|
$http.put('/rest/motions/motion/' + motion.id + '/set_state/', {});
|
||||||
@ -473,22 +684,53 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
|||||||
votesvalid: poll.votesvalid,
|
votesvalid: poll.votesvalid,
|
||||||
votesinvalid: poll.votesinvalid,
|
votesinvalid: poll.votesinvalid,
|
||||||
votescast: poll.votescast
|
votescast: poll.votescast
|
||||||
});
|
})
|
||||||
|
.then(function(success) {
|
||||||
|
$scope.alert.show = false;
|
||||||
poll.isEditMode = false;
|
poll.isEditMode = false;
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
var message = '';
|
||||||
|
for (var e in error.data) {
|
||||||
|
message += e + ': ' + error.data[e] + ' ';
|
||||||
|
}
|
||||||
|
$scope.alert = { type: 'danger', msg: message, show: true };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
.controller('MotionCreateCtrl',
|
.controller('MotionCreateCtrl', [
|
||||||
function($scope, $state, $http, Motion, Agenda, User, Category, Workflow, Tag, Mediafile) {
|
'$scope',
|
||||||
Agenda.bindAll({}, $scope, 'items');
|
'$state',
|
||||||
User.bindAll({}, $scope, 'users');
|
'gettext',
|
||||||
|
'Motion',
|
||||||
|
'MotionFormFieldFactory',
|
||||||
|
'Category',
|
||||||
|
'Config',
|
||||||
|
'Mediafile',
|
||||||
|
'Tag',
|
||||||
|
'User',
|
||||||
|
'Workflow',
|
||||||
|
function($scope, $state, gettext, Motion, MotionFormFieldFactory, Category, Config, Mediafile, Tag, User, Workflow) {
|
||||||
Category.bindAll({}, $scope, 'categories');
|
Category.bindAll({}, $scope, 'categories');
|
||||||
Workflow.bindAll({}, $scope, 'workflows');
|
|
||||||
Tag.bindAll({}, $scope, 'tags');
|
|
||||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||||
|
Tag.bindAll({}, $scope, 'tags');
|
||||||
|
User.bindAll({}, $scope, 'users');
|
||||||
|
Workflow.bindAll({}, $scope, 'workflows');
|
||||||
|
|
||||||
$scope.motion = {};
|
// get all form fields
|
||||||
|
$scope.formFields = MotionFormFieldFactory.getFormFields();
|
||||||
|
// override default values for create form
|
||||||
|
for (var i = 0; i < $scope.formFields.length; i++) {
|
||||||
|
if ($scope.formFields[i].key == "identifier") {
|
||||||
|
$scope.formFields[i].hide = true;
|
||||||
|
}
|
||||||
|
if ($scope.formFields[i].key == "workflow_id") {
|
||||||
|
// preselect default workflow
|
||||||
|
$scope.formFields[i].defaultValue = Config.get('motions_workflow').value;
|
||||||
|
}
|
||||||
|
}
|
||||||
$scope.save = function (motion) {
|
$scope.save = function (motion) {
|
||||||
Motion.create(motion).then(
|
Motion.create(motion).then(
|
||||||
function(success) {
|
function(success) {
|
||||||
@ -496,40 +738,68 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
})
|
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
.controller('MotionUpdateCtrl', [
|
.controller('MotionUpdateCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'$state',
|
'$state',
|
||||||
'$http',
|
'gettext',
|
||||||
'Motion',
|
'Motion',
|
||||||
'Agenda',
|
|
||||||
'User',
|
|
||||||
'Category',
|
'Category',
|
||||||
'Workflow',
|
'Config',
|
||||||
'Tag',
|
|
||||||
'Mediafile',
|
'Mediafile',
|
||||||
|
'MotionFormFieldFactory',
|
||||||
|
'Tag',
|
||||||
|
'User',
|
||||||
|
'Workflow',
|
||||||
'motion',
|
'motion',
|
||||||
function ($scope, $state, $http, Motion, Agenda, User, Category, Workflow, Tag, Mediafile, motion) {
|
function($scope, $state, gettext, Motion, Category, Config, Mediafile, MotionFormFieldFactory, Tag, User, Workflow, motion) {
|
||||||
Agenda.bindAll({}, $scope, 'items');
|
|
||||||
User.bindAll({}, $scope, 'users');
|
|
||||||
Category.bindAll({}, $scope, 'categories');
|
Category.bindAll({}, $scope, 'categories');
|
||||||
Workflow.bindAll({}, $scope, 'workflows');
|
|
||||||
Tag.bindAll({}, $scope, 'tags');
|
|
||||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||||
|
Tag.bindAll({}, $scope, 'tags');
|
||||||
|
User.bindAll({}, $scope, 'users');
|
||||||
|
Workflow.bindAll({}, $scope, 'workflows');
|
||||||
|
|
||||||
$scope.motion = motion;
|
// set initial values for form model
|
||||||
// get latest version for edit
|
$scope.model = motion;
|
||||||
$scope.motion.title = $scope.motion.getTitle(-1);
|
$scope.model.more = false;
|
||||||
$scope.motion.text = $scope.motion.getText(-1);
|
// get all form fields
|
||||||
$scope.motion.reason = $scope.motion.getReason(-1);
|
$scope.formFields = MotionFormFieldFactory.getFormFields();
|
||||||
|
// override default values for update form
|
||||||
$scope.save = function (motion) {
|
for (var i = 0; i < $scope.formFields.length; i++) {
|
||||||
Motion.save(motion).then(
|
if ($scope.formFields[i].key == "identifier") {
|
||||||
function(success) {
|
// show identifier field
|
||||||
$state.go('motions.motion.list');
|
$scope.formFields[i].hide = false;
|
||||||
}
|
}
|
||||||
);
|
if ($scope.formFields[i].key == "title") {
|
||||||
|
// get title of latest version
|
||||||
|
$scope.formFields[i].defaultValue = motion.getTitle(-1);
|
||||||
|
}
|
||||||
|
if ($scope.formFields[i].key == "text") {
|
||||||
|
// get text of latest version
|
||||||
|
$scope.formFields[i].defaultValue = motion.getText(-1);
|
||||||
|
}
|
||||||
|
if ($scope.formFields[i].key == "reason") {
|
||||||
|
// get reason of latest version
|
||||||
|
$scope.formFields[i].defaultValue = motion.getReason(-1);
|
||||||
|
}
|
||||||
|
if ($scope.formFields[i].key == "workflow_id") {
|
||||||
|
// get saved workflow id from state
|
||||||
|
$scope.formFields[i].defaultValue = motion.state.workflow_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// save form
|
||||||
|
$scope.save = function (model) {
|
||||||
|
Motion.save(model)
|
||||||
|
.then(function(success) {
|
||||||
|
$state.go('motions.motion.detail', {id: motion.id});
|
||||||
|
})
|
||||||
|
.catch(function(fallback) {
|
||||||
|
console.log(fallback);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
@ -6,11 +6,7 @@
|
|||||||
</small>
|
</small>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
agenda_id: {{ motion.agenda_item }}
|
<!-- TODO: show list of speakers controls for { motion.agenda_item } -->
|
||||||
|
|
||||||
<br><br>state: {{ motion.state }}
|
|
||||||
|
|
||||||
<br><br>next states: {{ motion.state.getNextStates() }}
|
|
||||||
|
|
||||||
<div id="submenu">
|
<div id="submenu">
|
||||||
<a ui-sref="motions.motion.list" class="btn btn-sm btn-default">
|
<a ui-sref="motions.motion.list" class="btn btn-sm btn-default">
|
||||||
@ -29,7 +25,7 @@ agenda_id: {{ motion.agenda_item }}
|
|||||||
<i class="fa fa-video-camera"></i>
|
<i class="fa fa-video-camera"></i>
|
||||||
</a>
|
</a>
|
||||||
<!-- edit -->
|
<!-- edit -->
|
||||||
<a ui-sref="motions.motion.detail.update({id: motion.id })" os-perms="motions.can_manage"
|
<a ng-if="motion.allowed_actions.update" ui-sref="motions.motion.detail.update({id: motion.id })"
|
||||||
class="btn btn-default btn-sm"
|
class="btn btn-default btn-sm"
|
||||||
title="{{ 'Edit' | translate}}">
|
title="{{ 'Edit' | translate}}">
|
||||||
<i class="fa fa-pencil"></i>
|
<i class="fa fa-pencil"></i>
|
||||||
@ -41,42 +37,73 @@ agenda_id: {{ motion.agenda_item }}
|
|||||||
<h3 translate>Text</h3>
|
<h3 translate>Text</h3>
|
||||||
<div class="white-space-pre-line" ng-bind-html="motion.getText()"></div>
|
<div class="white-space-pre-line" ng-bind-html="motion.getText()"></div>
|
||||||
|
|
||||||
|
<!-- reason -->
|
||||||
|
<div ng-if="motion.getReason() != ''">
|
||||||
<h3 translate>Reason</h3>
|
<h3 translate>Reason</h3>
|
||||||
<div class="white-space-pre-line" ng-bind-html="motion.getReason()"></div>
|
<div class="white-space-pre-line" ng-bind-html="motion.getReason()"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- attachments
|
||||||
|
TODO: make 'motion.attachments' useable and itteratable in template
|
||||||
|
<div ng-if="motion.attachments">
|
||||||
|
<h3 translate>Attachments</h3>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- log -->
|
||||||
|
<button type="button" class="btn btn-default spacer" ng-click="isCollapsed = !isCollapsed" translate>
|
||||||
|
Show log
|
||||||
|
</button>
|
||||||
|
<div uib-collapse="isCollapsed">
|
||||||
|
<div class="well well-sm">
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li ng-repeat="message in motion.log_messages">
|
||||||
|
<small>{{ message.message }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="well">
|
<div class="well">
|
||||||
|
<!-- submitters -->
|
||||||
<h3 translate>Submitters</h3>
|
<h3 translate>Submitters</h3>
|
||||||
<div ng-repeat="submitter in motion.submitters">
|
<div ng-repeat="submitter in motion.submitters">
|
||||||
{{ submitter.get_full_name() }}<br>
|
{{ submitter.get_full_name() }}<br>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- supporters -->
|
||||||
<div ng-if="config('motions_min_supporters') > 0">
|
<div ng-if="config('motions_min_supporters') > 0">
|
||||||
<h3 translate>Supporters</h3>
|
<h3 translate>Supporters</h3>
|
||||||
<ol>
|
<ol>
|
||||||
<li ng-repeat="supporters in motion.supporters">
|
<li ng-repeat="supporters in motion.supporters">
|
||||||
{{ supporters.get_full_name() }}
|
{{ supporters.get_full_name() }}
|
||||||
</ol>
|
</ol>
|
||||||
|
<!-- support button -->
|
||||||
|
<button ng-if="motion.allowed_actions.support" ng-click="support()" class="btn btn-primary btn-sm">
|
||||||
|
<i class="fa fa-heart"></i>
|
||||||
|
<translate>Support motion</translate>
|
||||||
|
</button>
|
||||||
|
<!-- unsupport button -->
|
||||||
|
<button ng-if="motion.allowed_actions.unsupport" ng-click="unsupport()" class="btn btn-default btn-sm">
|
||||||
|
<i class="fa fa-heart-o"></i>
|
||||||
|
<translate>Unsupport motion</translate>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 translate>State</h3>
|
<h3 translate>State</h3>
|
||||||
<span class="label label-primary">{{ motion.state.name | translate }}</span>
|
<span class="label" ng-class="'label-'+motion.state.css_class">
|
||||||
<button os-perms-lite="motions.can_manage" ng-click="motion.isStatusEditMode=true"
|
{{ motion.state.name | translate }}
|
||||||
class="btn btn-default btn-xs">
|
</span>
|
||||||
<i class="fa fa-pencil"></i>
|
<div ng-if="motion.allowed_actions.change_state" class="spacer">
|
||||||
</button>
|
|
||||||
<div ng-if="motion.isStatusEditMode" os-perms-lite="motions.can_manage">
|
|
||||||
<div class="btn-group-vertical spacer" role="group">
|
<div class="btn-group-vertical spacer" role="group">
|
||||||
<button ng-repeat="state_id in motion.state.next_states_id" class="btn btn-default btn-sm"
|
<button ng-repeat="state in motion.state.getNextStates()" ng-click="update_state(state)"
|
||||||
ng-click="update_state(state_id)">
|
class="btn btn-default btn-sm">
|
||||||
State #{{state_id}}
|
{{state.action_word}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
<button ng-if="motion.allowed_actions.reset_state" ng-click="reset_state()"
|
||||||
<div>
|
class="btn btn-danger btn-xs">
|
||||||
<button ng-click="reset_state()"
|
<i class="fa fa-exclamation-triangle"></i>
|
||||||
class="btn btn-danger btn-xs spacer">
|
<translate>Reset state</translate>
|
||||||
<i class="fa fa-exclamation-triangle"></i> Reset
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -93,8 +120,16 @@ agenda_id: {{ motion.agenda_item }}
|
|||||||
class="btn btn-default btn-xs">
|
class="btn btn-default btn-xs">
|
||||||
<i class="fa fa-times"></i>
|
<i class="fa fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
<br>
|
<div ng-show="poll.isEditMode" class="spacer">
|
||||||
<form ng-show="poll.isEditMode">
|
<form>
|
||||||
|
<p>
|
||||||
|
<translate>Special values</translate>:<br>
|
||||||
|
<span class="badge badge-success">-1</span> = <translate>majority</translate><br>
|
||||||
|
<span class="badge">-2</span> = <translate>undocumented</translate>
|
||||||
|
</p>
|
||||||
|
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||||
|
{{alert.msg}}
|
||||||
|
</alert>
|
||||||
<!-- yes -->
|
<!-- yes -->
|
||||||
<div class="input-group col-sm-8 spacer">
|
<div class="input-group col-sm-8 spacer">
|
||||||
<div class="input-group-addon" title="{{ 'Yes' | translate }}"><i class="fa fa-thumbs-up"></i></div>
|
<div class="input-group-addon" title="{{ 'Yes' | translate }}"><i class="fa fa-thumbs-up"></i></div>
|
||||||
@ -135,25 +170,35 @@ agenda_id: {{ motion.agenda_item }}
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div ng-show="!poll.isEditMode && poll.yes">
|
</div>
|
||||||
|
<div ng-show="!poll.isEditMode && poll.yes >= -2">
|
||||||
<!-- yes -->
|
<!-- yes -->
|
||||||
<div class="result_label">
|
<div class="result_label">
|
||||||
<i class="fa fa-thumbs-up"></i>
|
<i class="fa fa-thumbs-up"></i>
|
||||||
<translate>Yes</translate>:
|
<translate>Yes</translate>:
|
||||||
</div>
|
</div>
|
||||||
<div class="result_value">{{ poll.yes }} {{poll.getYesPercent()}}</div>
|
<div ng-if="poll.getYesPercent(true)">
|
||||||
|
<uib-progressbar value="poll.getYesPercent(true)" type="success"></uib-progressbar>
|
||||||
|
</div>
|
||||||
|
<div class="result_value">{{ poll.yes }} {{ poll.getYesPercent() }}</div>
|
||||||
<!-- no -->
|
<!-- no -->
|
||||||
<div class="result_label">
|
<div class="result_label">
|
||||||
<i class="fa fa-thumbs-down"></i>
|
<i class="fa fa-thumbs-down"></i>
|
||||||
<translate>No</translate>:
|
<translate>No</translate>:
|
||||||
</div>
|
</div>
|
||||||
<div class="result_value">{{ poll.no }} {{poll.getNoPercent()}}</div>
|
<div ng-if="poll.getNoPercent(true)">
|
||||||
|
<uib-progressbar value="poll.getNoPercent(true)" type="danger"></uib-progressbar>
|
||||||
|
</div>
|
||||||
|
<div class="result_value">{{ poll.no }} {{ poll.getNoPercent() }}</div>
|
||||||
<!-- abstain -->
|
<!-- abstain -->
|
||||||
<div class="resutl_label">
|
<div class="result_label">
|
||||||
<b>∅</b>
|
<b>∅</b>
|
||||||
<translate>Abstain</translate>:
|
<translate>Abstain</translate>:
|
||||||
</div>
|
</div>
|
||||||
<div class="result_value">{{ poll.abstain }} {{poll.getAbstainPercent()}}</div>
|
<div ng-if="poll.getAbstainPercent(true)">
|
||||||
|
<uib-progressbar value="poll.getAbstainPercent(true)" type="warning"></uib-progressbar>
|
||||||
|
</div>
|
||||||
|
<div class="result_value">{{ poll.abstain }} {{ poll.getAbstainPercent() }}</div>
|
||||||
<hr class="smallhr" ng-if="poll.votesvalid || poll.votesinvalid">
|
<hr class="smallhr" ng-if="poll.votesvalid || poll.votesinvalid">
|
||||||
<!-- valid votes -->
|
<!-- valid votes -->
|
||||||
<div ng-if="poll.votesvalid">
|
<div ng-if="poll.votesvalid">
|
||||||
@ -161,7 +206,7 @@ agenda_id: {{ motion.agenda_item }}
|
|||||||
<i class="fa fa-check"></i>
|
<i class="fa fa-check"></i>
|
||||||
<translate>Valid votes</translate>:
|
<translate>Valid votes</translate>:
|
||||||
</div>
|
</div>
|
||||||
<div class="result_value">{{ poll.votesvalid }} {{poll.getVotesValidPercent()}}</div>
|
<div class="result_value">{{ poll.votesvalid }} {{ poll.getVotesValidPercent() }}</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- invalid votes -->
|
<!-- invalid votes -->
|
||||||
<div ng-if="poll.votesinvalid">
|
<div ng-if="poll.votesinvalid">
|
||||||
@ -169,20 +214,20 @@ agenda_id: {{ motion.agenda_item }}
|
|||||||
<i class="fa fa-ban"></i>
|
<i class="fa fa-ban"></i>
|
||||||
<translate>Invalid votes</translate>:
|
<translate>Invalid votes</translate>:
|
||||||
</div>
|
</div>
|
||||||
<div class="result_value">{{ poll.votesinvalid }} {{poll.getVotesInvalidPercent()}}</div>
|
<div class="result_value">{{ poll.votesinvalid }} {{ poll.getVotesInvalidPercent() }}</div>
|
||||||
</div>
|
</div>
|
||||||
<hr class="smallhr" ng-if="poll.votescast">
|
<hr class="smallhr" ng-if="poll.votescast">
|
||||||
<!-- votes cast -->
|
<!-- votes cast -->
|
||||||
<div ng-if="poll.votescast">
|
<div ng-if="poll.votescast">
|
||||||
<div class="resutl_label">
|
<div class="result_label">
|
||||||
<b>∑</b>
|
<b>∑</b>
|
||||||
<translate>Votes cast</translate>:
|
<translate>Votes cast</translate>:
|
||||||
</div>
|
</div>
|
||||||
<div class="result_value">{{ poll.votescast }} {{poll.getVotesCastPercent()}}</div>
|
<div class="result_value">{{ poll.votescast }} {{ poll.getVotesCastPercent() }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ol>
|
</ol>
|
||||||
<button ng-click="create_poll()" class="btn btn-default btn-sm">
|
<button ng-if="motion.allowed_actions.create_poll" ng-click="create_poll()" class="btn btn-default btn-sm">
|
||||||
<i class="fa fa-bar-chart fa-lg"></i>
|
<i class="fa fa-bar-chart fa-lg"></i>
|
||||||
<translate>New poll</translate>
|
<translate>New poll</translate>
|
||||||
</button>
|
</button>
|
||||||
@ -191,12 +236,11 @@ agenda_id: {{ motion.agenda_item }}
|
|||||||
{{ motion.category.name }}</a>
|
{{ motion.category.name }}</a>
|
||||||
|
|
||||||
<h3 translate>Tags</h3>
|
<h3 translate>Tags</h3>
|
||||||
<span ng-repeat="tag in motion.tags">
|
<p ng-repeat="tag in motion.tags">
|
||||||
<span class="label label-default">
|
<span class="label label-default spacer-top">
|
||||||
{{ tag.name }}
|
{{ tag.name }}
|
||||||
</span>
|
</span>
|
||||||
|
</p>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,94 +8,14 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form name="motionForm">
|
<form name="motionForm" ng-submit="save(model)">
|
||||||
<div ng-if="motion.id" class="form-group">
|
<formly-form model="model" fields="formFields">
|
||||||
<label for="inputIdentifier" translate>Identifier</label>
|
<button type="submit" ng-disabled="motionForm.$invalid" class="btn btn-primary" translate>
|
||||||
<input type="text" ng-model="motion.identifier" class="form-control" name="inputIdentifier">
|
Submit
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="selectItem" translate>Agenda item</label>
|
|
||||||
<select ng-options="item.id as item.get_title for item in items"
|
|
||||||
ng-model="motion.item" class="form-control" name="selectItem">
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="selectSubmitter" translate>Submitters</label>
|
|
||||||
<ui-select multiple ng-model="motion.submitters_id" name="selectSubmitter">
|
|
||||||
<ui-select-match placeholder="{{ 'Select or search a submitter...' | translate }}">
|
|
||||||
{{ $item.get_full_name() }}
|
|
||||||
</ui-select-match>
|
|
||||||
<ui-select-choices repeat="user.id as user in users | filter: $select.search">
|
|
||||||
<div ng-bind-html="user.get_full_name() | highlight: $select.search"></div>
|
|
||||||
</ui-select-choices>
|
|
||||||
</ui-select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="inputTitle" translate>Title</label>
|
|
||||||
<input type="text" ng-model="motion.title" class="form-control" name="inputTitle" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="textText" translate>Text</label>
|
|
||||||
<textarea ng-model="motion.text" class="form-control" name="textText" required />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="textReason" translate>Reason</label>
|
|
||||||
<textarea ng-model="motion.reason" class="form-control" name="textReason" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="selectCategory" translate>Category</label>
|
|
||||||
<select ng-options="category.id as category.name for category in categories"
|
|
||||||
ng-model="motion.category_id" class="form-control" name="selectCategory">
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="selectTags" translate>Tags</label>
|
|
||||||
<ui-select multiple ng-model="motion.tags_id">
|
|
||||||
<ui-select-match placeholder="{{ 'Select or search a tag...' | translate }}">
|
|
||||||
{{ $item.name }}
|
|
||||||
</ui-select-match>
|
|
||||||
<ui-select-choices repeat="tag.id as tag in tags | filter: $select.search">
|
|
||||||
{{ tag.name }}
|
|
||||||
</ui-select-choices>
|
|
||||||
</ui-select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="selectAttachments" translate>Attachments</label>
|
|
||||||
<ui-select multiple ng-model="motion.attachments_id">
|
|
||||||
<ui-select-match placeholder="{{ 'Select or search an attachment...' | translate }}">
|
|
||||||
{{ $item.name }}
|
|
||||||
</ui-select-match>
|
|
||||||
<ui-select-choices repeat="file.id as file in mediafiles | filter: $select.search">
|
|
||||||
{{ file.title }}
|
|
||||||
</ui-select-choices>
|
|
||||||
</ui-select>
|
|
||||||
</div>
|
|
||||||
<!-- show supporters if enabled -->
|
|
||||||
<div class="form-group" ng-if="config('motions_min_supporters') > 0">
|
|
||||||
<label for="selectSupporter" translate>Supporters</label>
|
|
||||||
<ui-select multiple ng-model="motion.supporters_id">
|
|
||||||
<ui-select-match placeholder="{{ 'Select or search a supporter...' | translate }}">
|
|
||||||
{{ $item.get_full_name() }}
|
|
||||||
</ui-select-match>
|
|
||||||
<ui-select-choices repeat="user.id as user in users | filter: $select.search">
|
|
||||||
<div ng-bind-html="user.get_full_name() | highlight: $select.search"></div>
|
|
||||||
</ui-select-choices>
|
|
||||||
</ui-select>
|
|
||||||
</div>
|
|
||||||
<!-- TODO: preselect default workflow
|
|
||||||
does not work: ng-init="motion.workflow = config('motions_workflow')"-->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="selectWorkflow" translate>Workflow</label>
|
|
||||||
<select ng-options="workflow.id as workflow.name for workflow in workflows"
|
|
||||||
ng-model="motion.workflow"
|
|
||||||
class="form-control" name="selectWorkflow">
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" ng-click="save(motion)" class="btn btn-primary" translate>
|
|
||||||
Save
|
|
||||||
</button>
|
</button>
|
||||||
<button ui-sref="motions.motion.list" class="btn btn-default" translate>
|
<button ui-sref="motions.motion.list" class="btn btn-default" translate>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
</formly-form>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<h1 translate>Motions</h1>
|
<h1 translate>Motions</h1>
|
||||||
|
|
||||||
<div id="submenu">
|
<div id="submenu">
|
||||||
<a ui-sref="motions.motion.create" os-perms="motions.can_manage" class="btn btn-primary btn-sm">
|
<a ui-sref="motions.motion.create" os-perms="motions.can_create" class="btn btn-primary btn-sm">
|
||||||
<i class="fa fa-plus fa-lg"></i>
|
<i class="fa fa-plus fa-lg"></i>
|
||||||
<translate>New</translate>
|
<translate>New</translate>
|
||||||
</a>
|
</a>
|
||||||
@ -37,13 +37,21 @@
|
|||||||
<i class="fa fa-trash fa-lg"></i>
|
<i class="fa fa-trash fa-lg"></i>
|
||||||
<translate>Delete selected motions</translate>
|
<translate>Delete selected motions</translate>
|
||||||
</a>
|
</a>
|
||||||
<!-- TODO: add filters -->
|
<!-- state filter -->
|
||||||
|
|
||||||
|
<select ng-model="stateFilter" class="form-control" id="stateFilter">
|
||||||
|
<option value="" translate>--- Select state ---</option>
|
||||||
|
<option ng-repeat="state in states" value="{{ state.id }}">{{ state.name }}</option>
|
||||||
|
</select>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-addon"><i class="fa fa-filter"></i></div>
|
||||||
<input type="text" os-focus-me ng-model="filter.search" class="form-control"
|
<input type="text" os-focus-me ng-model="filter.search" class="form-control"
|
||||||
placeholder="{{ 'Filter' | translate}}">
|
placeholder="{{ 'Filter' | translate}}">
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table table-striped table-bordered table-hover">
|
<table class="table table-striped table-bordered table-hover">
|
||||||
@ -80,8 +88,9 @@
|
|||||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||||
</i>
|
</i>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="motion in motions | filter: filter.search |
|
<tr ng-repeat="motion in motions | filter: filter.search | filter: {state_id: stateFilter} |
|
||||||
orderBy: sortColumn:reverse"
|
orderBy: sortColumn:reverse"
|
||||||
|
class="animate-item"
|
||||||
ng-class="{ 'activeline': motion.isProjected(), 'selected': motion.selected }">
|
ng-class="{ 'activeline': motion.isProjected(), 'selected': motion.selected }">
|
||||||
<!-- projector column -->
|
<!-- projector column -->
|
||||||
<td ng-show="!isDeleteMode" os-perms-lite="core.can_manage_projector">
|
<td ng-show="!isDeleteMode" os-perms-lite="core.can_manage_projector">
|
||||||
@ -98,12 +107,19 @@
|
|||||||
<td ng-if="!motion.quickEdit">{{ motion.identifier }}
|
<td ng-if="!motion.quickEdit">{{ motion.identifier }}
|
||||||
<td ng-if="!motion.quickEdit" ng-mouseover="motion.hover=true" ng-mouseleave="motion.hover=false">
|
<td ng-if="!motion.quickEdit" ng-mouseover="motion.hover=true" ng-mouseleave="motion.hover=false">
|
||||||
<strong><a ui-sref="motions.motion.detail({id: motion.id})">{{ motion.getTitle() }}</a></strong>
|
<strong><a ui-sref="motions.motion.detail({id: motion.id})">{{ motion.getTitle() }}</a></strong>
|
||||||
<div os-perms-lite="motions.can_manage" class="hoverActions" ng-class="{'hiddenDiv': !motion.hover}">
|
<div class="hoverActions" ng-class="{'hiddenDiv': !motion.hover}">
|
||||||
<a ui-sref="motions.motion.detail.update({id: motion.id })" translate>Edit</a> |
|
<span ng-if="motion.allowed_actions.update">
|
||||||
<a href="" ng-click="motion.quickEdit=true" translate>QuickEdit</a> |
|
<a ui-sref="motions.motion.detail.update({ id: motion.id })" translate>Edit</a> |
|
||||||
|
</span>
|
||||||
|
<span ng-if="motion.allowed_actions.update">
|
||||||
|
<a href="" os-perms="motions.can_manage" ng-click="motion.quickEdit=true" translate>QuickEdit</a> |
|
||||||
|
</span>
|
||||||
|
<span ng-if="motion.allowed_actions.delete">
|
||||||
|
<!-- TODO: translate confirm message -->
|
||||||
<a href="" class="text-danger"
|
<a href="" class="text-danger"
|
||||||
ng-bootbox-confirm="Are you sure you want to delete <b>{{ motion.getTitle() }}</b>?"
|
ng-bootbox-confirm="Are you sure you want to delete <b>{{ motion.getTitle() }}</b>?"
|
||||||
ng-bootbox-confirm-action="deleteSingleMotion(motion)" translate>Delete</a>
|
ng-bootbox-confirm-action="deleteSingleMotion(motion)" translate>Delete</a>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<td ng-if="!motion.quickEdit" class="optional">
|
<td ng-if="!motion.quickEdit" class="optional">
|
||||||
<div ng-repeat="submitter in motion.submitters">
|
<div ng-repeat="submitter in motion.submitters">
|
||||||
@ -112,9 +128,11 @@
|
|||||||
<td ng-if="!motion.quickEdit" class="optional">
|
<td ng-if="!motion.quickEdit" class="optional">
|
||||||
{{ motion.category.name }}
|
{{ motion.category.name }}
|
||||||
<td ng-if="!motion.quickEdit" class="optional">
|
<td ng-if="!motion.quickEdit" class="optional">
|
||||||
<span class="label label-primary">{{ motion.state.name | translate }}</span>
|
<span class="label" ng-class="'label-'+motion.state.css_class">
|
||||||
|
{{ motion.state.name | translate }}
|
||||||
|
</span>
|
||||||
<!-- quickEdit columns -->
|
<!-- quickEdit columns -->
|
||||||
<td ng-if="motion.quickEdit" colspan="5">
|
<td ng-if="motion.quickEdit && motion.allowed_actions.update" colspan="5">
|
||||||
<h4>{{ motion.getTitle() }} <span class="text-muted">– Quick Edit</span></h4>
|
<h4>{{ motion.getTitle() }} <span class="text-muted">– Quick Edit</span></h4>
|
||||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||||
{{alert.msg}}
|
{{alert.msg}}
|
||||||
@ -170,20 +188,15 @@
|
|||||||
</ui-select>
|
</ui-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-6">
|
|
||||||
<label for="selectState" translate>State</label>
|
|
||||||
<select ng-model="motion.state" class="form-control" name="selectState">
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer">
|
<div class="spacer">
|
||||||
<button ng-click="motion.quickEdit=false" class="btn btn-default pull-left" translate>
|
<button ng-click="motion.quickEdit=false" class="btn btn-default pull-left" translate>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button ng-click="update(motion)" class="btn btn-primary" translate>
|
<button ng-if="motion.allowed_actions.update" ng-click="update(motion)" class="btn btn-primary" translate>
|
||||||
Update
|
Update
|
||||||
</button>
|
</button>
|
||||||
<a ui-sref="motions.motion.detail.update({id: motion.id })" class="pull-right"
|
<a ng-if="motion.allowed_actions.update" ui-sref="motions.motion.detail.update({id: motion.id })"
|
||||||
translate>Edit motion...</a>
|
class="pull-right" translate>Edit motion...</a>
|
||||||
</div>
|
</div>
|
||||||
</table>
|
</table>
|
||||||
|
@ -66,6 +66,16 @@ class MotionViewSet(ModelViewSet):
|
|||||||
result = False
|
result = False
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Customized view endpoint to retrieve a motion.
|
||||||
|
|
||||||
|
Adds the allowed actions for the motion.
|
||||||
|
"""
|
||||||
|
response = super().retrieve(request, *args, **kwargs)
|
||||||
|
response.data['allowed_actions'] = self.get_object().get_allowed_actions(request.user)
|
||||||
|
return response
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Customized view endpoint to create a new motion.
|
Customized view endpoint to create a new motion.
|
||||||
|
@ -9,6 +9,14 @@ angular.module('OpenSlidesApp.users', [])
|
|||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: name,
|
name: name,
|
||||||
useClass: jsDataModel,
|
useClass: jsDataModel,
|
||||||
|
computed: {
|
||||||
|
full_name: function () {
|
||||||
|
return this.get_full_name();
|
||||||
|
},
|
||||||
|
short_name: function () {
|
||||||
|
return this.get_short_name();
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getResourceName: function () {
|
getResourceName: function () {
|
||||||
return name;
|
return name;
|
||||||
|
Loading…
Reference in New Issue
Block a user