diff --git a/client/src/app/site/motions/components/manage-submitters/manage-submitters.component.html b/client/src/app/site/motions/components/manage-submitters/manage-submitters.component.html index 40742d722..491192dce 100644 --- a/client/src/app/site/motions/components/manage-submitters/manage-submitters.component.html +++ b/client/src/app/site/motions/components/manage-submitters/manage-submitters.component.html @@ -1,17 +1,24 @@

Submitters -

-
+
{{ submitter.full_name }}
-
+
- - @@ -80,7 +79,7 @@ - - - - +
+ + + +
@@ -110,9 +110,7 @@
-

- {{ motion.title }} -

+

{{ motion.title }}

@@ -124,18 +122,13 @@
- -
- -
+
-
- -
+
@@ -145,11 +138,14 @@ - - - +
@@ -162,10 +158,7 @@ - - +
@@ -229,24 +222,23 @@ {{ state.name | translate }} - + {{ motion.state.name | translate }} + + {{ motion.state.name | translate }} - -
@@ -261,17 +253,33 @@ {{ recommendation.recommendation_label | translate }} - - + {{ motion.recommendation ? (motion.recommendation.recommendation_label | translate) : ('not set' | translate) }} + + {{ + motion.recommendation + ? (motion.recommendation.recommendation_label | translate) + : ('not set' | translate) + }} + + @@ -291,7 +299,10 @@ {{ category }} - + + {{ motion.category ? motion.category : '–' }} + + {{ motion.category ? motion.category : '–' }}
@@ -305,7 +316,10 @@ {{ block }} - + + {{ motion.motion_block ? motion.motion_block : '–' }} + + {{ motion.motion_block ? motion.motion_block : '–' }}
@@ -380,7 +394,7 @@
-
+
-
+
-
+
-
+
{ await this.repo.createPoll(this.motion); } @@ -1000,7 +991,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * Check if a recommendation can be followed. Checks for permissions and additionally if a recommentadion is present */ - public get canFollowRecommendation(): boolean { + public canFollowRecommendation(): boolean { if ( this.perms.isAllowed('createPoll', this.motion) && this.motion.recommendation && @@ -1024,4 +1015,24 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { public async toggleFavorite(): Promise { this.personalNoteService.setPersonalNoteStar(this.motion.motion, !this.motion.star); } + + /** + * Translate the state's css class into a color + * + * @returns a string representing a color + */ + public getStateCssColor(): string { + switch (this.motion.state.css_class) { + case 'success': + return 'green'; + case 'danger': + return 'red'; + case 'default': + return 'grey'; + case 'primary': + return 'lightblue'; + default: + return ''; + } + } } diff --git a/client/src/app/site/motions/components/motion-list/motion-list.component.html b/client/src/app/site/motions/components/motion-list/motion-list.component.html index 36e54038c..482c075f4 100644 --- a/client/src/app/site/motions/components/motion-list/motion-list.component.html +++ b/client/src/app/site/motions/components/motion-list/motion-list.component.html @@ -1,4 +1,4 @@ - +

Motions

@@ -17,154 +17,159 @@
- - + + - - - - - - {{ isSelected(motion) ? 'check_circle' : '' }} - - + + + + + + {{ isSelected(motion) ? 'check_circle' : '' }} + + - - - Identifier - -
{{ motion.identifier }}
-
-
+ + + Identifier + +
{{ motion.identifier }}
+
+
- - - Title - -
- {{ motion.title }} - - {{ motion.star ? 'star' : 'star_border' }} + + + Title + +
+ {{ motion.title }} + + {{ motion.star ? 'star' : 'star_border' }} + - - - - - attach_file - + + + + attach_file + -
- - by {{ motion.submitters }} - -
- - - {{ motion.state.name | translate }} - +
+ + by {{ motion.submitters }} + +
+ + + {{ motion.state.name | translate }} + - - - {{ - motion.recommendation.recommendation_label | translate - }} - -
-
-
- - - - State - -
-
- device_hub - {{ motion.category }} + + + {{ + motion.recommendation.recommendation_label | translate + }} +
-
- widgets - {{ motion.motion_block.title }} + + + + + + State + +
+
+ device_hub + {{ motion.category }} +
+
+ widgets + {{ motion.motion_block.title }} +
-
- - + + - - - Speakers - - - - + + + Speakers + + + + - - - - + + + + - + +
- - - - - - - - - +
+ +
+
+ + + + + + + + +
-
+
- - +
+ + +
- + diff --git a/client/src/app/site/motions/components/motion-list/motion-list.component.ts b/client/src/app/site/motions/components/motion-list/motion-list.component.ts index d6a79eae4..b8ca7a486 100644 --- a/client/src/app/site/motions/components/motion-list/motion-list.component.ts +++ b/client/src/app/site/motions/components/motion-list/motion-list.component.ts @@ -3,22 +3,23 @@ import { Router, ActivatedRoute } from '@angular/router'; import { Title } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; -import { ConfigService } from '../../../../core/services/config.service'; -import { MotionCsvExportService } from '../../services/motion-csv-export.service'; -import { ListViewBaseComponent } from '../../../base/list-view-base'; -import { MatSnackBar } from '@angular/material'; -import { ViewMotion } from '../../models/view-motion'; -import { WorkflowState } from '../../../../shared/models/motions/workflow-state'; -import { MotionMultiselectService } from '../../services/motion-multiselect.service'; -import { TagRepositoryService } from 'app/site/tags/services/tag-repository.service'; import { CategoryRepositoryService } from '../../services/category-repository.service'; +import { ConfigService } from '../../../../core/services/config.service'; +import { ListViewBaseComponent } from '../../../base/list-view-base'; +import { LocalPermissionsService } from '../../services/local-permissions.service'; +import { MatSnackBar } from '@angular/material'; import { MotionBlockRepositoryService } from '../../services/motion-block-repository.service'; +import { MotionCsvExportService } from '../../services/motion-csv-export.service'; import { MotionFilterListService } from '../../services/motion-filter-list.service'; +import { MotionMultiselectService } from '../../services/motion-multiselect.service'; import { MotionSortListService } from '../../services/motion-sort-list.service'; +import { TagRepositoryService } from 'app/site/tags/services/tag-repository.service'; +import { ViewCategory } from '../../models/view-category'; +import { ViewMotion } from '../../models/view-motion'; +import { ViewMotionBlock } from '../../models/view-motion-block'; import { ViewTag } from 'app/site/tags/models/view-tag'; import { ViewWorkflow } from '../../models/view-workflow'; -import { ViewCategory } from '../../models/view-category'; -import { ViewMotionBlock } from '../../models/view-motion-block'; +import { WorkflowState } from '../../../../shared/models/motions/workflow-state'; import { WorkflowRepositoryService } from '../../services/workflow-repository.service'; /** @@ -75,6 +76,7 @@ export class MotionListComponent extends ListViewBaseComponent imple * @param userRepo * @param sortService * @param filterService + * @param perms LocalPermissionService */ public constructor( titleService: Title, @@ -90,7 +92,8 @@ export class MotionListComponent extends ListViewBaseComponent imple private motionCsvExport: MotionCsvExportService, public multiselectService: MotionMultiselectService, public sortService: MotionSortListService, - public filterService: MotionFilterListService + public filterService: MotionFilterListService, + public perms: LocalPermissionsService ) { super(titleService, translate, matSnackBar); diff --git a/client/src/app/site/motions/components/motion-poll/motion-poll.component.html b/client/src/app/site/motions/components/motion-poll/motion-poll.component.html index 2cd150512..6995789cc 100644 --- a/client/src/app/site/motions/components/motion-poll/motion-poll.component.html +++ b/client/src/app/site/motions/components/motion-poll/motion-poll.component.html @@ -6,10 +6,13 @@
- {{ getIcon(key) }} + + {{ getIcon(key) }} +
- {{ getLabel(key) }}: {{ getNumber(key) }} + {{ getLabel(key) }}: {{ getNumber(key) }} ({{ getPercent(key) }}%)
@@ -22,39 +25,53 @@
-
+
-
Quorum not calculable.
-
- - - thumb_down - thumb_up +
+
Quorum not calculable.
+
+ + + thumb_down + thumb_up + + + + reached. + not reached. + +  —  No quorum calculated + - - - reached. - not reached. - -  —  No quorum calculated - - +
- + - - diff --git a/client/src/app/site/motions/services/local-permissions.service.ts b/client/src/app/site/motions/services/local-permissions.service.ts index 36d1e1397..c239ccfcc 100644 --- a/client/src/app/site/motions/services/local-permissions.service.ts +++ b/client/src/app/site/motions/services/local-permissions.service.ts @@ -2,49 +2,124 @@ import { Injectable } from '@angular/core'; import { OperatorService } from '../../../core/services/operator.service'; import { ViewMotion } from '../models/view-motion'; import { ConfigService } from '../../../core/services/config.service'; +import { ConstantsService } from 'app/core/services/constants.service'; @Injectable({ providedIn: 'root' }) export class LocalPermissionsService { public configMinSupporters: number; + private amendmentEnabled: boolean; + private amendmentOfAmendment: boolean; - public constructor(private operator: OperatorService, private configService: ConfigService) { + public constructor( + private operator: OperatorService, + private configService: ConfigService, + private constants: ConstantsService + ) { // load config variables this.configService .get('motions_min_supporters') .subscribe(supporters => (this.configMinSupporters = supporters)); + this.configService.get('motions_amendments_enabled').subscribe(enabled => (this.amendmentEnabled = enabled)); + this.constants + .get('OpenSlidesSettings') + .subscribe(settings => (this.amendmentOfAmendment = settings.MOTIONS_ALLOW_AMENDMENTS_OF_AMENDMENTS)); } /** - * Should determine if the user (Operator) has the - * correct permission to perform the given action. + * Determine if the user (Operator) has the correct permission to perform the given action. * * actions might be: + * - create * - support * - unsupport * - createpoll + * - update + * - update_submitters + * - delete + * - change_metadata + * - reset_state + * - change_recommendation + * - can_create_amendments + * - can_manage_metadata + * - manage * * @param action the action the user tries to perform + * @param motion the motion for which to perform the action */ public isAllowed(action: string, motion?: ViewMotion): boolean { - if (motion) { - switch (action) { - case 'support': - return ( - this.operator.hasPerms('motions.can_support') && - this.configMinSupporters > 0 && - motion.state.allow_support && - motion.submitters.indexOf(this.operator.user) === -1 && - motion.supporters.indexOf(this.operator.user) === -1 - ); - case 'unsupport': - return motion.state.allow_support && motion.supporters.indexOf(this.operator.user) !== -1; - case 'createpoll': - return this.operator.hasPerms('motions.can_manage') && motion.state.allow_create_poll; - default: + switch (action) { + case 'create': + return this.operator.hasPerms('motions.can_create'); + case 'support': + if (!motion) { return false; - } + } + return ( + this.operator.hasPerms('motions.can_support') && + this.configMinSupporters > 0 && + motion.state.allow_support && + motion.submitters.indexOf(this.operator.user) === -1 && + motion.supporters.indexOf(this.operator.user) === -1 + ); + case 'unsupport': + if (!motion) { + return false; + } + return motion.state.allow_support && motion.supporters.indexOf(this.operator.user) !== -1; + case 'createpoll': + if (!motion) { + return false; + } + return ( + (this.operator.hasPerms('motions.can_manage') || + this.operator.hasPerms('motions.can_manage_metadata')) && + motion.state.allow_create_poll + ); + case 'update': + if (!motion) { + return false; + } + return ( + this.operator.hasPerms('motions.can_manage') && + motion.state.allow_submitter_edit && + motion.submitters.some(submitter => submitter.id === this.operator.user.id) + ); + case 'update_submitters': + return this.operator.hasPerms('motions.can_manage'); + case 'delete': + if (!motion) { + return false; + } + return ( + this.operator.hasPerms('motions.can_manage') && + motion.state.allow_submitter_edit && + motion.submitters.some(submitter => submitter.id === this.operator.user.id) + ); + case 'change_metadata': + return ( + this.operator.hasPerms('motions.can_manage') || + this.operator.hasPerms('motions.can_manage_metadata') + ); + case 'can_create_amendments': + if (!motion) { + return false; + } + return ( + this.operator.hasPerms('motions.can_create_amendments') && + this.amendmentEnabled && + (!motion.parent_id || (motion.parent_id && this.amendmentOfAmendment)) + ); + case 'can_manage_metadata': + return ( + this.operator.hasPerms('motions.can_manage') && + this.operator.hasPerms('motions.can_manage_metadata') + ); + case 'manage': + return this.operator.hasPerms('motions.can_manage'); + default: + return false; } } } diff --git a/client/src/app/site/motions/services/motion-repository.service.ts b/client/src/app/site/motions/services/motion-repository.service.ts index bff4f7d46..5d8343253 100644 --- a/client/src/app/site/motions/services/motion-repository.service.ts +++ b/client/src/app/site/motions/services/motion-repository.service.ts @@ -692,4 +692,13 @@ export class MotionRepositoryService extends BaseRepository await this.httpService.post(restPath); } } + /** + * Check if a motion currently has any amendments + * + * @param motion A viewMotion + * @returns True if there is at eleast one amendment + */ + public hasAmendments(motion: ViewMotion): boolean { + return this.getViewModelList().filter(allMotions => allMotions.parent_id === motion.id).length > 0; + } }