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",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"lodash": "~3.0.1",
|
||||
"lodash": "~3.10.0",
|
||||
"jquery": "~2.1.4",
|
||||
"jquery.cookie": "~1.4.1",
|
||||
"bootstrap-css-only": "~3.3.4",
|
||||
"angular": "~1.3.15",
|
||||
"angular-bootstrap": "~0.14.2",
|
||||
"angular-messages": "~1.3.15",
|
||||
"angular-animate": "~1.3.15",
|
||||
"angular-csv-import": "~0.0.15",
|
||||
"angular-loading-bar": "~0.7.1",
|
||||
"angular-ui-router": "~0.2.13",
|
||||
"angular-ui-select": "~0.13",
|
||||
"angular-ui-switch": "~0.1.0",
|
||||
"angular-ui-tree": "~2.2.0",
|
||||
"angular-gettext": "~2.0.2",
|
||||
"angular-sanitize": "~1.3.15",
|
||||
"bootstrap-css-only": "~3.3.5",
|
||||
"angular": "~1.4.7",
|
||||
"angular-messages": "~1.4.7",
|
||||
"angular-animate": "~1.4.7",
|
||||
"angular-sanitize": "~1.4.7",
|
||||
"angular-bootstrap": "~0.14.3",
|
||||
"angular-csv-import": "~0.0.26",
|
||||
"angular-formly-templates-bootstrap": "~6.1.5",
|
||||
"angular-formly": "~7.3.2",
|
||||
"angular-loading-bar": "~0.8.0",
|
||||
"angular-ui-router": "~0.2.15",
|
||||
"angular-ui-select": "~0.13.1",
|
||||
"angular-ui-switch": "~0.1.1",
|
||||
"angular-ui-tree": "~2.10.0",
|
||||
"angular-gettext": "~2.1.2",
|
||||
"angular-xeditable": "~0.1.9",
|
||||
"ng-fab-form": "~1.2.7",
|
||||
"ngBootbox": "~0.0.5",
|
||||
"ngBootbox": "~0.1.2",
|
||||
"sockjs": "~0.3.4",
|
||||
"font-awesome-bower": "4.3.0",
|
||||
"js-data": "~2.3.0",
|
||||
"js-data-angular": "~3.0.0",
|
||||
"font-awesome-bower": "~4.4.0",
|
||||
"js-data": "~2.8.1",
|
||||
"js-data-angular": "~3.1.0",
|
||||
"ng-file-upload": "~9.1.2"
|
||||
}
|
||||
}
|
||||
|
@ -49,92 +49,49 @@ body {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
.spacer {
|
||||
.spacer, .spacer-top {
|
||||
margin-top: 7px;
|
||||
}
|
||||
.spacer-right {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.hoverActions {
|
||||
font-size: 85%;
|
||||
}
|
||||
.hiddenDiv {
|
||||
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 {
|
||||
opacity: 0; }
|
||||
/* ngAnimate classes */
|
||||
.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 {
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
overflow: visible;
|
||||
background: #c00640; }
|
||||
.validation ul {
|
||||
display: block;
|
||||
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; }
|
||||
@keyframes fade-out {
|
||||
0% { opacity: 1; background: none; }
|
||||
25% { opacity: 1; background: #f8efc0; }
|
||||
100% { opacity: 0; background: none; }
|
||||
}
|
||||
@keyframes fade-in {
|
||||
0% { opacity: 0; background: none; }
|
||||
25% { opacity: 1; background: #dff0d8; }
|
||||
100% { opacity: 1; background: none; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -296,9 +253,9 @@ tr.selected td {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.smallhr {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
border-color: #333333;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
border-color: #cccccc;
|
||||
}
|
||||
.resultcolumn {
|
||||
font-weight: bold;
|
||||
|
@ -6,8 +6,9 @@
|
||||
angular.module('OpenSlidesApp.core.site', [
|
||||
'OpenSlidesApp.core',
|
||||
'ui.router',
|
||||
'formly',
|
||||
'formlyBootstrap',
|
||||
'ngBootbox',
|
||||
'ngFabForm',
|
||||
'ngMessages',
|
||||
'ngCsvImport',
|
||||
'ngSanitize', // TODO: only use this in functions that need it.
|
||||
@ -260,13 +261,6 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
$locationProvider.html5Mode(true);
|
||||
})
|
||||
|
||||
// config for ng-fab-form
|
||||
.config(function(ngFabFormProvider) {
|
||||
ngFabFormProvider.extendConfig({
|
||||
setAsteriskForRequiredLabel: true
|
||||
});
|
||||
})
|
||||
|
||||
// Helper to add ui.router states at runtime.
|
||||
// Needed for the django url_patterns.
|
||||
.provider('runtimeStates', function($stateProvider) {
|
||||
@ -299,6 +293,26 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
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
|
||||
// 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)
|
||||
"""A many-to-many relation to all states, that can be choosen from this state."""
|
||||
|
||||
icon = models.CharField(max_length=255)
|
||||
"""A string representing the url to the icon-image."""
|
||||
css_class = models.CharField(max_length=255, default='primary')
|
||||
"""
|
||||
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)
|
||||
"""
|
||||
@ -764,7 +768,6 @@ class State(RESTModelMixin, models.Model):
|
||||
This behavior can be changed by the form and view, e. g. via the
|
||||
MotionDisableVersioningMixin.
|
||||
"""
|
||||
# TODO: preferred_for = ChoiceField
|
||||
|
||||
leave_old_version_active = models.BooleanField(default=False)
|
||||
"""If true, new versions are not automaticly set active."""
|
||||
|
@ -49,7 +49,7 @@ class StateSerializer(ModelSerializer):
|
||||
'id',
|
||||
'name',
|
||||
'action_word',
|
||||
'icon',
|
||||
'css_class',
|
||||
'required_permission_to_see',
|
||||
'allow_support',
|
||||
'allow_create_poll',
|
||||
@ -77,9 +77,17 @@ class MotionLogSerializer(ModelSerializer):
|
||||
"""
|
||||
Serializer for motion.models.MotionLog objects.
|
||||
"""
|
||||
message = SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
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):
|
||||
|
@ -189,13 +189,16 @@ def create_builtin_workflows(sender, **kwargs):
|
||||
allow_submitter_edit=True)
|
||||
state_1_2 = State.objects.create(name=ugettext_noop('accepted'),
|
||||
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'),
|
||||
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'),
|
||||
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)
|
||||
workflow_1.first_state = state_1_1
|
||||
workflow_1.save()
|
||||
@ -216,35 +219,43 @@ def create_builtin_workflows(sender, **kwargs):
|
||||
state_2_3 = State.objects.create(name=ugettext_noop('accepted'),
|
||||
workflow=workflow_2,
|
||||
action_word=ugettext_noop('Accept'),
|
||||
versioning=True)
|
||||
versioning=True,
|
||||
css_class='success')
|
||||
state_2_4 = State.objects.create(name=ugettext_noop('rejected'),
|
||||
workflow=workflow_2,
|
||||
action_word=ugettext_noop('Reject'),
|
||||
versioning=True)
|
||||
versioning=True,
|
||||
css_class='danger')
|
||||
state_2_5 = State.objects.create(name=ugettext_noop('withdrawed'),
|
||||
workflow=workflow_2,
|
||||
action_word=ugettext_noop('Withdraw'),
|
||||
versioning=True)
|
||||
versioning=True,
|
||||
css_class='default')
|
||||
state_2_6 = State.objects.create(name=ugettext_noop('adjourned'),
|
||||
workflow=workflow_2,
|
||||
action_word=ugettext_noop('Adjourn'),
|
||||
versioning=True)
|
||||
versioning=True,
|
||||
css_class='default')
|
||||
state_2_7 = State.objects.create(name=ugettext_noop('not concerned'),
|
||||
workflow=workflow_2,
|
||||
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'),
|
||||
workflow=workflow_2,
|
||||
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'),
|
||||
workflow=workflow_2,
|
||||
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)'),
|
||||
workflow=workflow_2,
|
||||
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_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
|
||||
|
@ -40,7 +40,7 @@ angular.module('OpenSlidesApp.motions', [])
|
||||
}
|
||||
])
|
||||
|
||||
// Load all MotionWorkflows at stateup
|
||||
// Load all MotionWorkflows at startup
|
||||
.run([
|
||||
'Workflow',
|
||||
function (Workflow) {
|
||||
@ -53,7 +53,7 @@ angular.module('OpenSlidesApp.motions', [])
|
||||
'Config',
|
||||
function (DS, Config) {
|
||||
return DS.defineResource({
|
||||
name: 'motions/poll',
|
||||
name: 'motions/motionpoll',
|
||||
relations: {
|
||||
belongsTo: {
|
||||
'motions/motion': {
|
||||
@ -63,61 +63,91 @@ angular.module('OpenSlidesApp.motions', [])
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getYesPercent: function () {
|
||||
getYesPercent: function (valueOnly) {
|
||||
var config = Config.get('motions_poll_100_percent_base').value;
|
||||
if (config == "WITHOUT_INVALID" && this.votesvalid > 0) {
|
||||
return "(" + Math.round(this.yes * 100 / this.votesvalid * 10) / 10 + " %)";
|
||||
} else if (config == "WITH_INVALID" && this.votescast > 0) {
|
||||
return "(" + Math.round(this.yes * 100 / (this.votescast) * 10) / 10 + " %)";
|
||||
var returnvalue;
|
||||
if (config == "WITHOUT_INVALID" && this.votesvalid > 0 && this.yes >= 0) {
|
||||
returnvalue = Math.round(this.yes * 100 / this.votesvalid * 10) / 10;
|
||||
} else if (config == "WITH_INVALID" && this.votescast > 0 && this.yes >= 0) {
|
||||
returnvalue = Math.round(this.yes * 100 / (this.votescast) * 10) / 10;
|
||||
} 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;
|
||||
if (config == "WITHOUT_INVALID" && this.votesvalid > 0) {
|
||||
return "(" + Math.round(this.no * 100 / this.votesvalid * 10) / 10 + " %)";
|
||||
} else if (config == "WITH_INVALID" && this.votescast > 0) {
|
||||
return "(" + Math.round(this.no * 100 / (this.votescast) * 10) / 10 + " %)";
|
||||
var returnvalue;
|
||||
if (config == "WITHOUT_INVALID" && this.votesvalid > 0 && this.no >= 0) {
|
||||
returnvalue = Math.round(this.no * 100 / this.votesvalid * 10) / 10;
|
||||
} else if (config == "WITH_INVALID" && this.votescast > 0 && this.no >= 0) {
|
||||
returnvalue = Math.round(this.no * 100 / (this.votescast) * 10) / 10;
|
||||
} 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;
|
||||
if (config == "WITHOUT_INVALID" && this.votesvalid > 0) {
|
||||
return "(" + Math.round(this.abstain * 100 / this.votesvalid * 10) / 10 + " %)";
|
||||
} else if (config == "WITH_INVALID" && this.votescast > 0) {
|
||||
return "(" + Math.round(this.abstain * 100 / (this.votescast) * 10) / 10 + " %)";
|
||||
var returnvalue;
|
||||
if (config == "WITHOUT_INVALID" && this.votesvalid > 0 && this.abstain >= 0) {
|
||||
returnvalue = Math.round(this.abstain * 100 / this.votesvalid * 10) / 10;
|
||||
} else if (config == "WITH_INVALID" && this.votescast > 0 && this.abstain >= 0) {
|
||||
returnvalue = Math.round(this.abstain * 100 / (this.votescast) * 10) / 10;
|
||||
} 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;
|
||||
if (config == "WITHOUT_INVALID") {
|
||||
return "(100 %)";
|
||||
} else if (config == "WITH_INVALID") {
|
||||
return "(" + Math.round(this.votesvalid * 100 / (this.votescast) * 10) / 10 + " %)";
|
||||
var returnvalue;
|
||||
if (config == "WITHOUT_INVALID" && this.votevalid >= 0) {
|
||||
returnvalue = 100;
|
||||
} else if (config == "WITH_INVALID" && this.votevalid >= 0) {
|
||||
returnvalue = Math.round(this.votesvalid * 100 / (this.votescast) * 10) / 10;
|
||||
} 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;
|
||||
if (config == "WITH_INVALID") {
|
||||
return "(" + Math.round(this.votesinvalid * 100 / (this.votescast) * 10) / 10 + " %)";
|
||||
var returnvalue;
|
||||
if (config == "WITH_INVALID" && this.voteinvalid >= 0) {
|
||||
returnvalue = Math.round(this.votesinvalid * 100 / (this.votescast) * 10) / 10;
|
||||
} 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;
|
||||
if (config == "WITH_INVALID") {
|
||||
return "(100 %)";
|
||||
var returnvalue;
|
||||
if (config == "WITH_INVALID" && this.votecast >= 0) {
|
||||
returnvalue = 100;
|
||||
} else {
|
||||
return null;
|
||||
returnvalue = null;
|
||||
}
|
||||
if (!valueOnly && returnvalue != null) {
|
||||
returnvalue = "(" + returnvalue + "%)";
|
||||
}
|
||||
return returnvalue;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -183,6 +213,10 @@ angular.module('OpenSlidesApp.motions', [])
|
||||
localField: 'tags',
|
||||
localKeys: 'tags_id',
|
||||
},
|
||||
'mediafiles/mediafile': {
|
||||
localField: 'attachments',
|
||||
localKeys: 'attachments_id',
|
||||
},
|
||||
'users/user': [
|
||||
{
|
||||
localField: 'submitters',
|
||||
@ -193,7 +227,7 @@ angular.module('OpenSlidesApp.motions', [])
|
||||
localKeys: 'supporters_id',
|
||||
}
|
||||
],
|
||||
'motions/poll': {
|
||||
'motions/motionpoll': {
|
||||
localField: 'polls',
|
||||
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) {
|
||||
return DS.defineResource({
|
||||
name: 'motions/category',
|
||||
@ -252,6 +424,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
categories: function(Category) {
|
||||
return Category.findAll();
|
||||
},
|
||||
tags: function(Tag) {
|
||||
return Tag.findAll();
|
||||
},
|
||||
users: function(User) {
|
||||
return User.findAll();
|
||||
}
|
||||
@ -259,9 +434,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
})
|
||||
.state('motions.motion.create', {
|
||||
resolve: {
|
||||
items: function(Agenda) {
|
||||
return Agenda.findAll();
|
||||
},
|
||||
categories: function(Category) {
|
||||
return Category.findAll();
|
||||
},
|
||||
@ -273,6 +445,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
},
|
||||
mediafiles: function(Mediafile) {
|
||||
return Mediafile.findAll();
|
||||
},
|
||||
workflows: function(Workflow) {
|
||||
return Workflow.findAll();
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -287,9 +462,12 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
users: function(User) {
|
||||
return User.findAll();
|
||||
},
|
||||
mediafiles: function(Mediafile) {
|
||||
return Mediafile.findAll();
|
||||
},
|
||||
tags: function(Tag) {
|
||||
return Tag.findAll();
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('motions.motion.detail.update', {
|
||||
@ -297,9 +475,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
'@motions.motion': {}
|
||||
},
|
||||
resolve: {
|
||||
items: function(Agenda) {
|
||||
return Agenda.findAll();
|
||||
},
|
||||
categories: function(Category) {
|
||||
return Category.findAll();
|
||||
},
|
||||
@ -311,6 +486,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
},
|
||||
mediafiles: function(Mediafile) {
|
||||
return Mediafile.findAll();
|
||||
},
|
||||
workflows: function(Workflow) {
|
||||
return Workflow.findAll();
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -351,11 +529,16 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
'$state',
|
||||
'Motion',
|
||||
'Category',
|
||||
'Tag',
|
||||
'Workflow',
|
||||
'User',
|
||||
function($scope, $state, Motion, Category, User) {
|
||||
function($scope, $state, Motion, Category, Tag, Workflow, User) {
|
||||
Motion.bindAll({}, $scope, 'motions');
|
||||
Category.bindAll({}, $scope, 'categories');
|
||||
Tag.bindAll({}, $scope, 'tags');
|
||||
Workflow.bindAll({}, $scope, 'workflows');
|
||||
User.bindAll({}, $scope, 'users');
|
||||
$scope.alert = {};
|
||||
|
||||
// setup table sorting
|
||||
$scope.sortColumn = 'identifier';
|
||||
@ -369,6 +552,21 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
$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
|
||||
$scope.hoverIn = function () {
|
||||
$scope.showEditActions = true;
|
||||
@ -432,24 +630,37 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
|
||||
.controller('MotionDetailCtrl', [
|
||||
'$scope',
|
||||
'$http',
|
||||
'Motion',
|
||||
'Category',
|
||||
'Workflow',
|
||||
'Mediafile',
|
||||
'Tag',
|
||||
'User',
|
||||
'Workflow',
|
||||
'motion',
|
||||
'$http',
|
||||
function($scope, Motion, Category, Workflow, User, motion, $http) {
|
||||
function($scope, $http, Motion, Category, Mediafile, Tag, User, Workflow, motion) {
|
||||
Motion.bindOne(motion.id, $scope, 'motion');
|
||||
Category.bindAll({}, $scope, 'categories');
|
||||
Workflow.bindAll({}, $scope, 'workflows');
|
||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||
Tag.bindAll({}, $scope, 'tags');
|
||||
User.bindAll({}, $scope, 'users');
|
||||
Workflow.bindAll({}, $scope, 'workflows');
|
||||
Motion.loadRelations(motion, 'agenda_item');
|
||||
var state = motion.state;
|
||||
state.getNextStates()
|
||||
$scope.alert = {}; // TODO: show alert in template
|
||||
// TODO: make 'motion.attachments' useable and itteratable in template
|
||||
// Motion.loadRelations(motion, 'attachments');
|
||||
|
||||
$scope.update_state = function (state_id) {
|
||||
$http.put('/rest/motions/motion/' + motion.id + '/set_state/', {'state': state_id});
|
||||
$scope.alert = {};
|
||||
$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) {
|
||||
$http.put('/rest/motions/motion/' + motion.id + '/set_state/', {});
|
||||
@ -473,64 +684,123 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
votesvalid: poll.votesvalid,
|
||||
votesinvalid: poll.votesinvalid,
|
||||
votescast: poll.votescast
|
||||
})
|
||||
.then(function(success) {
|
||||
$scope.alert.show = 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 };
|
||||
});
|
||||
poll.isEditMode = false;
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
.controller('MotionCreateCtrl',
|
||||
function($scope, $state, $http, Motion, Agenda, User, Category, Workflow, Tag, Mediafile) {
|
||||
Agenda.bindAll({}, $scope, 'items');
|
||||
User.bindAll({}, $scope, 'users');
|
||||
Category.bindAll({}, $scope, 'categories');
|
||||
Workflow.bindAll({}, $scope, 'workflows');
|
||||
Tag.bindAll({}, $scope, 'tags');
|
||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||
|
||||
$scope.motion = {};
|
||||
$scope.save = function (motion) {
|
||||
Motion.create(motion).then(
|
||||
function(success) {
|
||||
$state.go('motions.motion.list');
|
||||
}
|
||||
);
|
||||
};
|
||||
})
|
||||
|
||||
.controller('MotionUpdateCtrl', [
|
||||
.controller('MotionCreateCtrl', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'$http',
|
||||
'gettext',
|
||||
'Motion',
|
||||
'Agenda',
|
||||
'User',
|
||||
'MotionFormFieldFactory',
|
||||
'Category',
|
||||
'Workflow',
|
||||
'Tag',
|
||||
'Config',
|
||||
'Mediafile',
|
||||
'motion',
|
||||
function ($scope, $state, $http, Motion, Agenda, User, Category, Workflow, Tag, Mediafile, motion) {
|
||||
Agenda.bindAll({}, $scope, 'items');
|
||||
User.bindAll({}, $scope, 'users');
|
||||
'Tag',
|
||||
'User',
|
||||
'Workflow',
|
||||
function($scope, $state, gettext, Motion, MotionFormFieldFactory, Category, Config, Mediafile, Tag, User, Workflow) {
|
||||
Category.bindAll({}, $scope, 'categories');
|
||||
Workflow.bindAll({}, $scope, 'workflows');
|
||||
Tag.bindAll({}, $scope, 'tags');
|
||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||
Tag.bindAll({}, $scope, 'tags');
|
||||
User.bindAll({}, $scope, 'users');
|
||||
Workflow.bindAll({}, $scope, 'workflows');
|
||||
|
||||
$scope.motion = motion;
|
||||
// get latest version for edit
|
||||
$scope.motion.title = $scope.motion.getTitle(-1);
|
||||
$scope.motion.text = $scope.motion.getText(-1);
|
||||
$scope.motion.reason = $scope.motion.getReason(-1);
|
||||
|
||||
// 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) {
|
||||
Motion.save(motion).then(
|
||||
Motion.create(motion).then(
|
||||
function(success) {
|
||||
$state.go('motions.motion.list');
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
])
|
||||
|
||||
.controller('MotionUpdateCtrl', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'gettext',
|
||||
'Motion',
|
||||
'Category',
|
||||
'Config',
|
||||
'Mediafile',
|
||||
'MotionFormFieldFactory',
|
||||
'Tag',
|
||||
'User',
|
||||
'Workflow',
|
||||
'motion',
|
||||
function($scope, $state, gettext, Motion, Category, Config, Mediafile, MotionFormFieldFactory, Tag, User, Workflow, motion) {
|
||||
Category.bindAll({}, $scope, 'categories');
|
||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||
Tag.bindAll({}, $scope, 'tags');
|
||||
User.bindAll({}, $scope, 'users');
|
||||
Workflow.bindAll({}, $scope, 'workflows');
|
||||
|
||||
// set initial values for form model
|
||||
$scope.model = motion;
|
||||
$scope.model.more = false;
|
||||
// get all form fields
|
||||
$scope.formFields = MotionFormFieldFactory.getFormFields();
|
||||
// override default values for update form
|
||||
for (var i = 0; i < $scope.formFields.length; i++) {
|
||||
if ($scope.formFields[i].key == "identifier") {
|
||||
// show identifier field
|
||||
$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>
|
||||
</h1>
|
||||
|
||||
agenda_id: {{ motion.agenda_item }}
|
||||
|
||||
<br><br>state: {{ motion.state }}
|
||||
|
||||
<br><br>next states: {{ motion.state.getNextStates() }}
|
||||
<!-- TODO: show list of speakers controls for { motion.agenda_item } -->
|
||||
|
||||
<div id="submenu">
|
||||
<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>
|
||||
</a>
|
||||
<!-- 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"
|
||||
title="{{ 'Edit' | translate}}">
|
||||
<i class="fa fa-pencil"></i>
|
||||
@ -41,43 +37,74 @@ agenda_id: {{ motion.agenda_item }}
|
||||
<h3 translate>Text</h3>
|
||||
<div class="white-space-pre-line" ng-bind-html="motion.getText()"></div>
|
||||
|
||||
<h3 translate>Reason</h3>
|
||||
<div class="white-space-pre-line" ng-bind-html="motion.getReason()"></div>
|
||||
<!-- reason -->
|
||||
<div ng-if="motion.getReason() != ''">
|
||||
<h3 translate>Reason</h3>
|
||||
<div class="white-space-pre-line" ng-bind-html="motion.getReason()"></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="well">
|
||||
<!-- submitters -->
|
||||
<h3 translate>Submitters</h3>
|
||||
<div ng-repeat="submitter in motion.submitters">
|
||||
{{ submitter.get_full_name() }}<br>
|
||||
</div>
|
||||
|
||||
<!-- supporters -->
|
||||
<div ng-if="config('motions_min_supporters') > 0">
|
||||
<h3 translate>Supporters</h3>
|
||||
<ol>
|
||||
<li ng-repeat="supporters in motion.supporters">
|
||||
{{ supporters.get_full_name() }}
|
||||
{{ supporters.get_full_name() }}
|
||||
</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>
|
||||
|
||||
<h3 translate>State</h3>
|
||||
<span class="label label-primary">{{ motion.state.name | translate }}</span>
|
||||
<button os-perms-lite="motions.can_manage" ng-click="motion.isStatusEditMode=true"
|
||||
class="btn btn-default btn-xs">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</button>
|
||||
<div ng-if="motion.isStatusEditMode" os-perms-lite="motions.can_manage">
|
||||
<span class="label" ng-class="'label-'+motion.state.css_class">
|
||||
{{ motion.state.name | translate }}
|
||||
</span>
|
||||
<div ng-if="motion.allowed_actions.change_state" class="spacer">
|
||||
<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"
|
||||
ng-click="update_state(state_id)">
|
||||
State #{{state_id}}
|
||||
<button ng-repeat="state in motion.state.getNextStates()" ng-click="update_state(state)"
|
||||
class="btn btn-default btn-sm">
|
||||
{{state.action_word}}
|
||||
</button>
|
||||
<button ng-if="motion.allowed_actions.reset_state" ng-click="reset_state()"
|
||||
class="btn btn-danger btn-xs">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<translate>Reset state</translate>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button ng-click="reset_state()"
|
||||
class="btn btn-danger btn-xs spacer">
|
||||
<i class="fa fa-exclamation-triangle"></i> Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -93,67 +120,85 @@ agenda_id: {{ motion.agenda_item }}
|
||||
class="btn btn-default btn-xs">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
<br>
|
||||
<form ng-show="poll.isEditMode">
|
||||
<!-- yes -->
|
||||
<div class="input-group col-sm-8 spacer">
|
||||
<div class="input-group-addon" title="{{ 'Yes' | translate }}"><i class="fa fa-thumbs-up"></i></div>
|
||||
<input type="number" ng-model="poll.yes" class="form-control input-sm" placeholder="{{ 'Yes' | translate }}">
|
||||
</div>
|
||||
<!-- no -->
|
||||
<div class="input-group col-sm-8">
|
||||
<div class="input-group-addon" title="{{ 'No' | translate }}"><i class="fa fa-thumbs-down"></i></div>
|
||||
<input type="number" ng-model="poll.no" class="form-control input-sm" placeholder="{{ 'No' | translate }}">
|
||||
</div>
|
||||
<!-- abstain -->
|
||||
<div class="input-group col-sm-8">
|
||||
<div class="input-group-addon" title="{{ 'Abstain' | translate }}"><b>∅</b></div>
|
||||
<input type="number" ng-model="poll.abstain" class="form-control input-sm" placeholder="{{ 'Abstain' | translate }}">
|
||||
</div>
|
||||
<!-- valid votes -->
|
||||
<div class="input-group col-sm-8 spacer">
|
||||
<div class="input-group-addon" title="{{ 'Valid votes' | translate }}"><i class="fa fa-check"></i></div>
|
||||
<input type="number" ng-model="poll.votesvalid" class="form-control input-sm" placeholder="{{ 'Valid votes' | translate }}">
|
||||
</div>
|
||||
<!-- invalid votes -->
|
||||
<div class="input-group col-sm-8">
|
||||
<div class="input-group-addon" title="{{ 'Invalid votes' | translate }}"><i class="fa fa-ban"></i></div>
|
||||
<input type="number" ng-model="poll.votesinvalid" class="form-control input-sm" placeholder="{{ 'Invalid votes' | translate }}">
|
||||
</div>
|
||||
<!-- votes cast -->
|
||||
<div class="input-group col-sm-8 spacer">
|
||||
<div class="input-group-addon" title="{{ 'Votes cast' | translate }}"><b>∑</b></div>
|
||||
<input type="number" ng-model="poll.votescast" class="form-control input-sm" placeholder="{{ 'Votes cast' | translate }}">
|
||||
</div>
|
||||
<!-- buttons -->
|
||||
<div class="spacer">
|
||||
<button type="submit" ng-click="update_poll(poll)" class="btn btn-primary" translate>
|
||||
Save
|
||||
</button>
|
||||
<button ng-click="poll.isEditMode=false;" class="btn btn-default" translate>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div ng-show="!poll.isEditMode && poll.yes">
|
||||
<div ng-show="poll.isEditMode" class="spacer">
|
||||
<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 -->
|
||||
<div class="input-group col-sm-8 spacer">
|
||||
<div class="input-group-addon" title="{{ 'Yes' | translate }}"><i class="fa fa-thumbs-up"></i></div>
|
||||
<input type="number" ng-model="poll.yes" class="form-control input-sm" placeholder="{{ 'Yes' | translate }}">
|
||||
</div>
|
||||
<!-- no -->
|
||||
<div class="input-group col-sm-8">
|
||||
<div class="input-group-addon" title="{{ 'No' | translate }}"><i class="fa fa-thumbs-down"></i></div>
|
||||
<input type="number" ng-model="poll.no" class="form-control input-sm" placeholder="{{ 'No' | translate }}">
|
||||
</div>
|
||||
<!-- abstain -->
|
||||
<div class="input-group col-sm-8">
|
||||
<div class="input-group-addon" title="{{ 'Abstain' | translate }}"><b>∅</b></div>
|
||||
<input type="number" ng-model="poll.abstain" class="form-control input-sm" placeholder="{{ 'Abstain' | translate }}">
|
||||
</div>
|
||||
<!-- valid votes -->
|
||||
<div class="input-group col-sm-8 spacer">
|
||||
<div class="input-group-addon" title="{{ 'Valid votes' | translate }}"><i class="fa fa-check"></i></div>
|
||||
<input type="number" ng-model="poll.votesvalid" class="form-control input-sm" placeholder="{{ 'Valid votes' | translate }}">
|
||||
</div>
|
||||
<!-- invalid votes -->
|
||||
<div class="input-group col-sm-8">
|
||||
<div class="input-group-addon" title="{{ 'Invalid votes' | translate }}"><i class="fa fa-ban"></i></div>
|
||||
<input type="number" ng-model="poll.votesinvalid" class="form-control input-sm" placeholder="{{ 'Invalid votes' | translate }}">
|
||||
</div>
|
||||
<!-- votes cast -->
|
||||
<div class="input-group col-sm-8 spacer">
|
||||
<div class="input-group-addon" title="{{ 'Votes cast' | translate }}"><b>∑</b></div>
|
||||
<input type="number" ng-model="poll.votescast" class="form-control input-sm" placeholder="{{ 'Votes cast' | translate }}">
|
||||
</div>
|
||||
<!-- buttons -->
|
||||
<div class="spacer">
|
||||
<button type="submit" ng-click="update_poll(poll)" class="btn btn-primary" translate>
|
||||
Save
|
||||
</button>
|
||||
<button ng-click="poll.isEditMode=false;" class="btn btn-default" translate>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div ng-show="!poll.isEditMode && poll.yes >= -2">
|
||||
<!-- yes -->
|
||||
<div class="result_label">
|
||||
<i class="fa fa-thumbs-up"></i>
|
||||
<translate>Yes</translate>:
|
||||
</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 -->
|
||||
<div class="result_label">
|
||||
<i class="fa fa-thumbs-down"></i>
|
||||
<translate>No</translate>:
|
||||
</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 -->
|
||||
<div class="resutl_label">
|
||||
<div class="result_label">
|
||||
<b>∅</b>
|
||||
<translate>Abstain</translate>:
|
||||
</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">
|
||||
<!-- valid votes -->
|
||||
<div ng-if="poll.votesvalid">
|
||||
@ -161,7 +206,7 @@ agenda_id: {{ motion.agenda_item }}
|
||||
<i class="fa fa-check"></i>
|
||||
<translate>Valid votes</translate>:
|
||||
</div>
|
||||
<div class="result_value">{{ poll.votesvalid }} {{poll.getVotesValidPercent()}}</div>
|
||||
<div class="result_value">{{ poll.votesvalid }} {{ poll.getVotesValidPercent() }}</div>
|
||||
</div>
|
||||
<!-- invalid votes -->
|
||||
<div ng-if="poll.votesinvalid">
|
||||
@ -169,20 +214,20 @@ agenda_id: {{ motion.agenda_item }}
|
||||
<i class="fa fa-ban"></i>
|
||||
<translate>Invalid votes</translate>:
|
||||
</div>
|
||||
<div class="result_value">{{ poll.votesinvalid }} {{poll.getVotesInvalidPercent()}}</div>
|
||||
<div class="result_value">{{ poll.votesinvalid }} {{ poll.getVotesInvalidPercent() }}</div>
|
||||
</div>
|
||||
<hr class="smallhr" ng-if="poll.votescast">
|
||||
<!-- votes cast -->
|
||||
<div ng-if="poll.votescast">
|
||||
<div class="resutl_label">
|
||||
<div class="result_label">
|
||||
<b>∑</b>
|
||||
<translate>Votes cast</translate>:
|
||||
</div>
|
||||
<div class="result_value">{{ poll.votescast }} {{poll.getVotesCastPercent()}}</div>
|
||||
<div class="result_value">{{ poll.votescast }} {{ poll.getVotesCastPercent() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
<translate>New poll</translate>
|
||||
</button>
|
||||
@ -191,12 +236,11 @@ agenda_id: {{ motion.agenda_item }}
|
||||
{{ motion.category.name }}</a>
|
||||
|
||||
<h3 translate>Tags</h3>
|
||||
<span ng-repeat="tag in motion.tags">
|
||||
<span class="label label-default">
|
||||
<p ng-repeat="tag in motion.tags">
|
||||
<span class="label label-default spacer-top">
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,94 +8,14 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form name="motionForm">
|
||||
<div ng-if="motion.id" class="form-group">
|
||||
<label for="inputIdentifier" translate>Identifier</label>
|
||||
<input type="text" ng-model="motion.identifier" class="form-control" name="inputIdentifier">
|
||||
</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 ui-sref="motions.motion.list" class="btn btn-default" translate>
|
||||
Cancel
|
||||
</button>
|
||||
<form name="motionForm" ng-submit="save(model)">
|
||||
<formly-form model="model" fields="formFields">
|
||||
<button type="submit" ng-disabled="motionForm.$invalid" class="btn btn-primary" translate>
|
||||
Submit
|
||||
</button>
|
||||
<button ui-sref="motions.motion.list" class="btn btn-default" translate>
|
||||
Cancel
|
||||
</button>
|
||||
</formly-form>
|
||||
</form>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<h1 translate>Motions</h1>
|
||||
|
||||
<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>
|
||||
<translate>New</translate>
|
||||
</a>
|
||||
@ -37,12 +37,20 @@
|
||||
<i class="fa fa-trash fa-lg"></i>
|
||||
<translate>Delete selected motions</translate>
|
||||
</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>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" os-focus-me ng-model="filter.search" class="form-control"
|
||||
placeholder="{{ 'Filter' | translate}}">
|
||||
<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"
|
||||
placeholder="{{ 'Filter' | translate}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -80,8 +88,9 @@
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
<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"
|
||||
class="animate-item"
|
||||
ng-class="{ 'activeline': motion.isProjected(), 'selected': motion.selected }">
|
||||
<!-- projector column -->
|
||||
<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" ng-mouseover="motion.hover=true" ng-mouseleave="motion.hover=false">
|
||||
<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}">
|
||||
<a ui-sref="motions.motion.detail.update({id: motion.id })" translate>Edit</a> |
|
||||
<a href="" ng-click="motion.quickEdit=true" translate>QuickEdit</a> |
|
||||
<a href="" class="text-danger"
|
||||
ng-bootbox-confirm="Are you sure you want to delete <b>{{ motion.getTitle() }}</b>?"
|
||||
ng-bootbox-confirm-action="deleteSingleMotion(motion)" translate>Delete</a>
|
||||
<div class="hoverActions" ng-class="{'hiddenDiv': !motion.hover}">
|
||||
<span ng-if="motion.allowed_actions.update">
|
||||
<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"
|
||||
ng-bootbox-confirm="Are you sure you want to delete <b>{{ motion.getTitle() }}</b>?"
|
||||
ng-bootbox-confirm-action="deleteSingleMotion(motion)" translate>Delete</a>
|
||||
</span>
|
||||
</div>
|
||||
<td ng-if="!motion.quickEdit" class="optional">
|
||||
<div ng-repeat="submitter in motion.submitters">
|
||||
@ -112,9 +128,11 @@
|
||||
<td ng-if="!motion.quickEdit" class="optional">
|
||||
{{ motion.category.name }}
|
||||
<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 -->
|
||||
<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>
|
||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{alert.msg}}
|
||||
@ -170,20 +188,15 @@
|
||||
</ui-select>
|
||||
</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 class="spacer">
|
||||
<button ng-click="motion.quickEdit=false" class="btn btn-default pull-left" translate>
|
||||
Cancel
|
||||
</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
|
||||
</button>
|
||||
<a ui-sref="motions.motion.detail.update({id: motion.id })" class="pull-right"
|
||||
translate>Edit motion...</a>
|
||||
<a ng-if="motion.allowed_actions.update" ui-sref="motions.motion.detail.update({id: motion.id })"
|
||||
class="pull-right" translate>Edit motion...</a>
|
||||
</div>
|
||||
</table>
|
||||
|
@ -66,6 +66,16 @@ class MotionViewSet(ModelViewSet):
|
||||
result = False
|
||||
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):
|
||||
"""
|
||||
Customized view endpoint to create a new motion.
|
||||
|
@ -9,6 +9,14 @@ angular.module('OpenSlidesApp.users', [])
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
computed: {
|
||||
full_name: function () {
|
||||
return this.get_full_name();
|
||||
},
|
||||
short_name: function () {
|
||||
return this.get_short_name();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
|
Loading…
Reference in New Issue
Block a user