Merge pull request #4038 from emanuelschuetze/motion-ui
Improved UI of motion list and detail view
This commit is contained in:
commit
e694d9e0dd
@ -131,19 +131,19 @@ export class HttpService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Exectures a post on a url with a certain object
|
* Exectures a post on a url with a certain object
|
||||||
* @param url string of the url to send semothing to
|
* @param url The url to send the request to.
|
||||||
* @param data The data to send
|
* @param data An optional payload for the request.
|
||||||
* @param header optional HTTP header if required
|
* @param header optional HTTP header if required
|
||||||
* @returns A promise holding a generic
|
* @returns A promise holding a generic
|
||||||
*/
|
*/
|
||||||
public async post<T>(url: string, data: any, header?: HttpHeaders): Promise<T> {
|
public async post<T>(url: string, data?: any, header?: HttpHeaders): Promise<T> {
|
||||||
return await this.send<T>(url, HTTPMethod.POST, data, header);
|
return await this.send<T>(url, HTTPMethod.POST, data, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exectures a put on a url with a certain object
|
* Exectures a put on a url with a certain object
|
||||||
* @param url string of the url to send semothing to
|
* @param url The url to send the request to.
|
||||||
* @param data the object that should be send
|
* @param data The payload for the request.
|
||||||
* @param header optional HTTP header if required
|
* @param header optional HTTP header if required
|
||||||
* @returns A promise holding a generic
|
* @returns A promise holding a generic
|
||||||
*/
|
*/
|
||||||
@ -153,8 +153,8 @@ export class HttpService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Exectures a put on a url with a certain object
|
* Exectures a put on a url with a certain object
|
||||||
* @param url the url that should be called
|
* @param url The url to send the request to.
|
||||||
* @param data: The data to send
|
* @param data: The payload for the request.
|
||||||
* @param header optional HTTP header if required
|
* @param header optional HTTP header if required
|
||||||
* @returns A promise holding a generic
|
* @returns A promise holding a generic
|
||||||
*/
|
*/
|
||||||
@ -164,7 +164,7 @@ export class HttpService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a delete request.
|
* Makes a delete request.
|
||||||
* @param url the url that should be called
|
* @param url The url to send the request to.
|
||||||
* @param data An optional data to send in the requestbody.
|
* @param data An optional data to send in the requestbody.
|
||||||
* @param header optional HTTP header if required
|
* @param header optional HTTP header if required
|
||||||
* @returns A promise holding a generic
|
* @returns A promise holding a generic
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
<mat-accordion multi='true' class='on-transition-fade'>
|
<mat-accordion multi='true' class='on-transition-fade'>
|
||||||
|
|
||||||
<!-- MetaInfo Panel-->
|
<!-- MetaInfo Panel-->
|
||||||
<mat-expansion-panel #metaInfoPanel [expanded]="editMotion" class='meta-info-block meta-info-panel'>
|
<mat-expansion-panel #metaInfoPanel [expanded]="true" class='meta-info-block meta-info-panel'>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title>
|
<mat-panel-title>
|
||||||
<mat-icon>info</mat-icon>
|
<mat-icon>info</mat-icon>
|
||||||
@ -93,9 +93,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
|
||||||
<os-motion-comments *ngIf="!newMotion" [motion]="motion"></os-motion-comments>
|
|
||||||
<os-personal-note *ngIf="!newMotion" [motion]="motion"></os-personal-note>
|
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<mat-expansion-panel #contentPanel [expanded]='true'>
|
<mat-expansion-panel #contentPanel [expanded]='true'>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
@ -109,6 +106,9 @@
|
|||||||
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
|
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<os-motion-comments *ngIf="!newMotion" [motion]="motion"></os-motion-comments>
|
||||||
|
<os-personal-note *ngIf="!newMotion" [motion]="motion"></os-personal-note>
|
||||||
</mat-accordion>
|
</mat-accordion>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
@ -150,7 +150,7 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Submitter -->
|
<!-- Submitters -->
|
||||||
<div *ngIf="motion && motion.submitters || newMotion">
|
<div *ngIf="motion && motion.submitters || newMotion">
|
||||||
<div *ngIf="newMotion">
|
<div *ngIf="newMotion">
|
||||||
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
|
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
|
||||||
@ -166,75 +166,97 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Supporter -->
|
<!-- Supporters -->
|
||||||
<div *ngIf='motion && motion.hasSupporters() || editMotion'>
|
<div *ngIf='motion && minSupporters'>
|
||||||
<!-- print all motion supporters -->
|
|
||||||
<div *ngIf="editMotion">
|
<div *ngIf="editMotion">
|
||||||
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
|
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
|
||||||
<os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="metaInfoForm.get('supporters_id')"
|
<os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="metaInfoForm.get('supporters_id')"
|
||||||
[multiple]="true" listname="{{ 'Supporters' | translate }}" [InputListValues]="supporterObserver"></os-search-value-selector>
|
[multiple]="true" listname="{{ 'Supporters' | translate }}" [InputListValues]="supporterObserver"></os-search-value-selector>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!editMotion && motion.hasSupporters()">
|
<div *ngIf="!editMotion">
|
||||||
<h4 translate>Supporters</h4>
|
<h4 *ngIf="perms.isAllowed('support', motion) || motion.hasSupporters()" translate>Supporters</h4>
|
||||||
<ul *ngFor="let supporter of motion.supporters">
|
<!-- support button -->
|
||||||
<li>{{ supporter.full_name }}</li>
|
<button type="button" *ngIf="perms.isAllowed('support', motion)" (click)=support() mat-raised-button color="primary">
|
||||||
</ul>
|
<mat-icon>thumb_up</mat-icon>
|
||||||
|
{{ 'Support' | translate }}
|
||||||
|
</button>
|
||||||
|
<!-- unsupport button -->
|
||||||
|
<button type="button" *ngIf="perms.isAllowed('unsupport', motion)" (click)=unsupport() mat-raised-button color="primary">
|
||||||
|
<mat-icon>thumb_down</mat-icon>
|
||||||
|
{{ 'Unsupport' | translate }}
|
||||||
|
</button>
|
||||||
|
<!-- show supporters (TODO: open in dialog) -->
|
||||||
|
<button type="button" *ngIf="motion.hasSupporters()" (click)=openSupportersDialog() mat-button>
|
||||||
|
{{ motion.supporters.length }} {{ 'supporters' | translate }}
|
||||||
|
</button>
|
||||||
|
<p *ngIf="showSupporters">
|
||||||
|
<mat-chip-list *ngFor="let supporter of motion.supporters">
|
||||||
|
<mat-chip>{{ supporter.full_name }}</mat-chip>
|
||||||
|
</mat-chip-list>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- State -->
|
<!-- State -->
|
||||||
<div *ngIf='motion && motion.state'>
|
<div *ngIf='motion && !editMotion'>
|
||||||
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata'];complement:true">
|
|
||||||
<ng-container *ngIf="!newMotion">
|
|
||||||
<h4 translate>State</h4>
|
<h4 translate>State</h4>
|
||||||
{{ motion.state }}
|
<mat-menu #stateMenu='matMenu'>
|
||||||
</ng-container>
|
<button *ngFor='let state of motion.nextStates' mat-menu-item
|
||||||
</div>
|
(click)=setState(state.id)>{{ state.name | translate }}
|
||||||
<div *ngIf="!editMotion">
|
</button>
|
||||||
<mat-form-field *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
|
|
||||||
<mat-select placeholder="{{ 'State' | translate }}" formControlName='state_id' (selectionChange)="onChangeState($event)">
|
|
||||||
<mat-option [value]="motion.state_id">{{ motion.state }}</mat-option>
|
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<mat-option *ngFor="let state of motion.nextStates" [value]="state.id">{{ state }}</mat-option>
|
<button mat-menu-item (click)=setState(null)>
|
||||||
<mat-divider></mat-divider>
|
<mat-icon>replay</mat-icon> {{ 'Reset state' | translate }}
|
||||||
<mat-option [value]="null">
|
</button>
|
||||||
<mat-icon>replay</mat-icon>
|
</mat-menu>
|
||||||
<span translate>Reset State</span>
|
<mat-basic-chip [matMenuTriggerFor]='stateMenu' [ngClass]="{
|
||||||
</mat-option>
|
'green': motion.state.css_class === 'success',
|
||||||
</mat-select>
|
'red': motion.state.css_class === 'danger',
|
||||||
</mat-form-field>
|
'grey': motion.state.css_class === 'default',
|
||||||
</div>
|
'lightblue': motion.state.css_class === 'primary' }">
|
||||||
|
{{ motion.state.name | translate }}
|
||||||
|
</mat-basic-chip>
|
||||||
|
|
||||||
|
<!--*osPerms="['motions.can_manage', 'motions.can_manage_metadata']; -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Recommendation -->
|
<!-- Recommendation -->
|
||||||
<div *ngIf='motion && motion.state && recommender'>
|
<div *ngIf='motion && recommender && !editMotion'>
|
||||||
<mat-form-field *ngIf="!editMotion && !newMotion">
|
<h4 translate>{{ recommender }}</h4>
|
||||||
<mat-select [placeholder]=recommender formControlName='recommendation_id' (selectionChange)="onChangerRecommenderState($event)">
|
<mat-menu #recommendationMenu='matMenu'>
|
||||||
<mat-option *ngFor="let recommendation of motion.possibleRecommendations" [value]="recommendation.id">
|
<button *ngFor='let recommendation of motion.possibleRecommendations' mat-menu-item
|
||||||
{{ recommendation.recommendation_label | translate }}
|
(click)=setRecommendation(recommendation.id)>{{ recommendation.recommendation_label | translate }}
|
||||||
</mat-option>
|
</button>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<mat-option [value]="null">
|
<button mat-menu-item (click)=setRecommendation(null)>
|
||||||
<mat-icon>replay</mat-icon><span translate>Reset recommendation</span>
|
<mat-icon>replay</mat-icon> {{ 'Reset recommendation' | translate }}
|
||||||
</mat-option>
|
</button>
|
||||||
</mat-select>
|
</mat-menu>
|
||||||
</mat-form-field>
|
<mat-basic-chip [matMenuTriggerFor]='recommendationMenu' class="bluegrey">
|
||||||
|
{{ motion.recommendation ? (motion.recommendation.recommendation_label | translate) : ('not set' | translate) }}
|
||||||
|
</mat-basic-chip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Category -->
|
<!-- Category -->
|
||||||
<div *ngIf="motion && motion.category_id || editMotion">
|
<!-- Disabled during "new motion" since changing has no effect -->
|
||||||
<div *ngIf='!editMotion'>
|
<div *ngIf="motion && !editMotion">
|
||||||
<h4 translate>Category</h4>
|
<h4 translate>Category</h4>
|
||||||
{{ motion.category }}
|
<mat-menu #categoryMenu='matMenu'>
|
||||||
</div>
|
<button *ngFor='let category of categoryObserver.value' mat-menu-item
|
||||||
<div *ngIf="editMotion || newMotion">
|
(click)=setCategory(category.id)>{{ category }}
|
||||||
<os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="metaInfoForm.get('category_id')"
|
</button>
|
||||||
[multiple]="false" listname="{{ 'Category' | translate }}" [InputListValues]="categoryObserver" includeNone="true"></os-search-value-selector>
|
<button mat-menu-item (click)=setCategory(null)>
|
||||||
</div>
|
---
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
<mat-basic-chip [matMenuTriggerFor]='categoryMenu' class="grey">
|
||||||
|
{{ motion.category ? motion.category : ('not set' | translate) }}
|
||||||
|
</mat-basic-chip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Workflow (just during creation) -->
|
<!-- Workflow -->
|
||||||
<div *ngIf="editMotion">
|
<div *ngIf="editMotion">
|
||||||
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
|
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
|
||||||
<os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="metaInfoForm.get('workflow_id')"
|
<os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="metaInfoForm.get('workflow_id')"
|
||||||
@ -270,7 +292,7 @@
|
|||||||
<button type="button" mat-icon-button [matMenuTriggerFor]="lineNumberingMenu" matTooltip="{{ 'Line numbering' | translate }}">
|
<button type="button" mat-icon-button [matMenuTriggerFor]="lineNumberingMenu" matTooltip="{{ 'Line numbering' | translate }}">
|
||||||
<mat-icon>format_list_numbered</mat-icon>
|
<mat-icon>format_list_numbered</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" mat-icon-button [matMenuTriggerFor]="changeRecoMenu" matTooltip="{{ 'Change recommendations' | translate }}">
|
<button *ngIf="allChangingObjects.length > 0" type="button" mat-icon-button [matMenuTriggerFor]="changeRecoMenu" matTooltip="{{ 'Change recommendations' | translate }}">
|
||||||
<mat-icon>rate_review</mat-icon>
|
<mat-icon>rate_review</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -293,21 +315,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div *ngIf="motion && motion.title || editMotion">
|
<div *ngIf="motion && editMotion">
|
||||||
<div *ngIf='!editMotion'>
|
<mat-form-field class="wide-form">
|
||||||
<h4>{{motion.title}}</h4>
|
<input matInput osAutofocus placeholder="{{ 'Title' | translate }}"
|
||||||
</div>
|
formControlName='title' [value]='motionCopy.title' required>
|
||||||
|
|
||||||
<mat-form-field *ngIf="editMotion" class="wide-form">
|
|
||||||
<input matInput osAutofocus placeholder="{{ 'Title' | translate }}" formControlName='title' [value]='motionCopy.title'
|
|
||||||
required>
|
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Text -->
|
<!-- Text -->
|
||||||
<!-- TODO: this is a config variable. Read it out -->
|
<span class="text-prefix-label">{{ preamble | translate }}</span>
|
||||||
<span class="text-prefix-label" translate>The assembly may decide:</span>
|
|
||||||
<ng-container *ngIf='motion && !editMotion && !motion.isStatuteAmendment()'>
|
<ng-container *ngIf='motion && !editMotion && !motion.isStatuteAmendment()'>
|
||||||
<div *ngIf="!isRecoModeDiff()" class="motion-text" [class.line-numbers-none]="isLineNumberingNone()"
|
<div *ngIf="!isRecoModeDiff()" class="motion-text" [class.line-numbers-none]="isLineNumberingNone()"
|
||||||
[class.line-numbers-inline]="isLineNumberingInline()" [class.line-numbers-outside]="isLineNumberingOutside()">
|
[class.line-numbers-inline]="isLineNumberingInline()" [class.line-numbers-outside]="isLineNumberingOutside()">
|
||||||
@ -343,17 +359,18 @@
|
|||||||
[init]="tinyMceSettings"
|
[init]="tinyMceSettings"
|
||||||
*ngIf="editMotion"
|
*ngIf="editMotion"
|
||||||
></editor>
|
></editor>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<!-- Line number Menu -->
|
<!-- Line number Menu -->
|
||||||
<mat-menu #lineNumberingMenu="matMenu" >
|
<mat-menu #lineNumberingMenu="matMenu">
|
||||||
<button mat-menu-item translate (click)=setLineNumberingMode(0)>none</button>
|
<div *ngIf="motion">
|
||||||
<button mat-menu-item translate (click)=setLineNumberingMode(1)>inline</button>
|
<button mat-menu-item translate (click)=setLineNumberingMode(0) [ngClass]="{ 'selected': motion.lnMode === 0 }">none</button>
|
||||||
<button mat-menu-item translate (click)=setLineNumberingMode(2)>outside</button>
|
<button mat-menu-item translate (click)=setLineNumberingMode(1) [ngClass]="{ 'selected': motion.lnMode === 1 }">inline</button>
|
||||||
|
<button mat-menu-item translate (click)=setLineNumberingMode(2) [ngClass]="{ 'selected': motion.lnMode === 2 }">outside</button>
|
||||||
|
</div>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<!-- Diff View Menu -->
|
<!-- Diff View Menu -->
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { MatDialog, MatExpansionPanel, MatSnackBar, MatSelectChange, MatCheckboxChange } from '@angular/material';
|
import { MatDialog, MatExpansionPanel, MatSnackBar, MatCheckboxChange } from '@angular/material';
|
||||||
|
|
||||||
import { Category } from '../../../../shared/models/motions/category';
|
import { Category } from '../../../../shared/models/motions/category';
|
||||||
import { ViewportService } from '../../../../core/services/viewport.service';
|
import { ViewportService } from '../../../../core/services/viewport.service';
|
||||||
@ -28,6 +28,7 @@ import { StatuteParagraphRepositoryService } from '../../services/statute-paragr
|
|||||||
import { ConfigService } from '../../../../core/services/config.service';
|
import { ConfigService } from '../../../../core/services/config.service';
|
||||||
import { Workflow } from 'app/shared/models/motions/workflow';
|
import { Workflow } from 'app/shared/models/motions/workflow';
|
||||||
import { take, takeWhile, multicast, skipWhile } from 'rxjs/operators';
|
import { take, takeWhile, multicast, skipWhile } from 'rxjs/operators';
|
||||||
|
import { LocalPermissionsService } from '../../services/local-permissions.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for the motion detail view
|
* Component for the motion detail view
|
||||||
@ -94,11 +95,22 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
|||||||
private _motion: ViewMotion;
|
private _motion: ViewMotion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value of the configuration variable `motions_statutes_enabled` - are statutes enabled?
|
* Value of the config variable `motions_statutes_enabled` - are statutes enabled?
|
||||||
* @TODO replace by direct access to config variable, once it's available from the templates
|
* @TODO replace by direct access to config variable, once it's available from the templates
|
||||||
*/
|
*/
|
||||||
public statutesEnabled: boolean;
|
public statutesEnabled: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of the config variable `motions_min_supporters`
|
||||||
|
*/
|
||||||
|
public minSupporters: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of the config variable `motions_preamble`
|
||||||
|
*/
|
||||||
|
public preamble: string;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy of the motion that the user might edit
|
* Copy of the motion that the user might edit
|
||||||
*/
|
*/
|
||||||
@ -154,6 +166,11 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public supporterObserver: BehaviorSubject<User[]>;
|
public supporterObserver: BehaviorSubject<User[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the name of supporters are visible
|
||||||
|
*/
|
||||||
|
public showSupporters = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value for os-motion-detail-diff: when this is set, that component scrolls to the given change
|
* Value for os-motion-detail-diff: when this is set, that component scrolls to the given change
|
||||||
*/
|
*/
|
||||||
@ -193,6 +210,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
|||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
public vp: ViewportService,
|
public vp: ViewportService,
|
||||||
|
public perms: LocalPermissionsService,
|
||||||
private op: OperatorService,
|
private op: OperatorService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@ -226,11 +244,22 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
|||||||
this.workflowObserver.next(DS.getAll(Workflow));
|
this.workflowObserver.next(DS.getAll(Workflow));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// load config variables
|
||||||
this.configService.get('motions_statutes_enabled').subscribe(
|
this.configService.get('motions_statutes_enabled').subscribe(
|
||||||
(enabled: boolean): void => {
|
(enabled: boolean): void => {
|
||||||
this.statutesEnabled = enabled;
|
this.statutesEnabled = enabled;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
this.configService.get('motions_min_supporters').subscribe(
|
||||||
|
(supporters: number): void => {
|
||||||
|
this.minSupporters = supporters;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.configService.get('motions_preamble').subscribe(
|
||||||
|
(preamble: string): void => {
|
||||||
|
this.preamble = preamble;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -343,7 +372,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
|||||||
* The AutoUpdate-Service should see a change once it arrives and show it
|
* The AutoUpdate-Service should see a change once it arrives and show it
|
||||||
* in the list view automatically
|
* in the list view automatically
|
||||||
*
|
*
|
||||||
* TODO: state is not yet saved. Need a special "put" command. Repo should handle this.
|
|
||||||
*/
|
*/
|
||||||
public async saveMotion(): Promise<void> {
|
public async saveMotion(): Promise<void> {
|
||||||
const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value };
|
const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value };
|
||||||
@ -410,7 +438,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the motions line numbering mode
|
* Sets the motions line numbering mode
|
||||||
* @param mode Needs to fot to the enum defined in ViewMotion
|
* @param mode Needs to got the enum defined in ViewMotion
|
||||||
*/
|
*/
|
||||||
public setLineNumberingMode(mode: LineNumberingMode): void {
|
public setLineNumberingMode(mode: LineNumberingMode): void {
|
||||||
this.motion.lnMode = mode;
|
this.motion.lnMode = mode;
|
||||||
@ -578,19 +606,49 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executed after selecting a state
|
* Supports the motion (as requested user)
|
||||||
* @param selection MatSelectChange that contains the workflow id
|
|
||||||
*/
|
*/
|
||||||
public onChangeState(selection: MatSelectChange): void {
|
public support(): void {
|
||||||
this.repo.setState(this.motion, selection.value);
|
this.repo.support(this.motion).then(null, this.raiseError);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executed after selecting the recommenders state
|
* Unsupports the motion
|
||||||
* @param selection MatSelectChange that contains the workflow id
|
|
||||||
*/
|
*/
|
||||||
public onChangerRecommenderState(selection: MatSelectChange): void {
|
public unsupport(): void {
|
||||||
this.repo.setRecommenderState(this.motion, selection.value);
|
this.repo.unsupport(this.motion).then(null, this.raiseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the dialog with all supporters.
|
||||||
|
* TODO: open dialog here!
|
||||||
|
*/
|
||||||
|
public openSupportersDialog(): void {
|
||||||
|
this.showSupporters = !this.showSupporters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the state
|
||||||
|
* @param id Motion state id
|
||||||
|
*/
|
||||||
|
public setState(id: number): void {
|
||||||
|
this.repo.setState(this.motion, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the recommendation
|
||||||
|
* @param id Motion recommendation id
|
||||||
|
*/
|
||||||
|
public setRecommendation(id: number): void {
|
||||||
|
this.repo.setRecommendation(this.motion, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the category for current motion
|
||||||
|
* @param id Motion category id
|
||||||
|
*/
|
||||||
|
public setCategory(id: number): void {
|
||||||
|
this.repo.setCatetory(this.motion, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,6 +65,20 @@
|
|||||||
<span translate>by</span>
|
<span translate>by</span>
|
||||||
{{ motion.submitters }}
|
{{ motion.submitters }}
|
||||||
</span>
|
</span>
|
||||||
|
<br>
|
||||||
|
<!-- state -->
|
||||||
|
<mat-basic-chip [ngClass]="{
|
||||||
|
'green': motion.state.css_class === 'success',
|
||||||
|
'red': motion.state.css_class === 'danger',
|
||||||
|
'grey': motion.state.css_class === 'default',
|
||||||
|
'lightblue': motion.state.css_class === 'primary' }">
|
||||||
|
{{ motion.state.name | translate }}
|
||||||
|
</mat-basic-chip>
|
||||||
|
|
||||||
|
<!-- recommendation -->
|
||||||
|
<span *ngIf="motion.recommendation" >
|
||||||
|
<mat-basic-chip class="bluegrey">{{ motion.recommendation.recommendation_label | translate }}</mat-basic-chip>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@ -72,13 +86,10 @@
|
|||||||
<!-- state column -->
|
<!-- state column -->
|
||||||
<ng-container matColumnDef="state">
|
<ng-container matColumnDef="state">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>State</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header>State</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let motion">
|
<mat-cell *matCellDef="let motion" (click)="selectMotion(motion)">
|
||||||
<!--div *ngIf='isDisplayIcon(motion.state) && motion.state' class='innerTable'>
|
<div *ngIf='motion.category' class='small'>
|
||||||
<mat-icon>{{ getStateIcon(motion.state) }}</mat-icon>
|
<mat-icon>device_hub</mat-icon> {{ motion.category }}
|
||||||
</div>-->
|
</div>
|
||||||
<mat-chip-list>
|
|
||||||
<mat-chip color="accent">{{ motion.state }}</mat-chip>
|
|
||||||
</mat-chip-list>
|
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@ -98,7 +109,7 @@
|
|||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
||||||
<mat-row [ngClass]="selectedRows.indexOf(row) >= 0 ? 'selected': ''" (click)='selectItem(row, $event)'
|
<mat-row [ngClass]="selectedRows.indexOf(row) >= 0 ? 'selected': ''" (click)='selectItem(row, $event)'
|
||||||
*matRowDef="let row; columns: getColumnDefinition()">
|
*matRowDef="let row; columns: getColumnDefinition()" class="lg">
|
||||||
</mat-row>
|
</mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/** css hacks https://codepen.io/edge0703/pen/iHJuA */
|
/** css hacks https://codepen.io/edge0703/pen/iHJuA */
|
||||||
.innerTable {
|
.innerTable {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: top;
|
||||||
line-height: normal;
|
line-height: 150%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.os-listview-table {
|
.os-listview-table {
|
||||||
@ -23,10 +23,10 @@
|
|||||||
|
|
||||||
.motion-list-title {
|
.motion-list-title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.motion-list-from {
|
.motion-list-from {
|
||||||
margin-top: 5px;
|
|
||||||
color: rgba(0, 0, 0, 0.5);
|
color: rgba(0, 0, 0, 0.5);
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
}
|
}
|
||||||
|
@ -227,6 +227,11 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
this._block = block;
|
this._block = block;
|
||||||
|
|
||||||
// TODO: Should be set using a a config variable
|
// TODO: Should be set using a a config variable
|
||||||
|
/*this._configService.get('motions_default_line_numbering').subscribe(
|
||||||
|
(mode: string): void => {
|
||||||
|
this.lnMode = LineNumberingMode.Outside;
|
||||||
|
}
|
||||||
|
);*/
|
||||||
this.lnMode = LineNumberingMode.Outside;
|
this.lnMode = LineNumberingMode.Outside;
|
||||||
this.crMode = ChangeRecoMode.Original;
|
this.crMode = ChangeRecoMode.Original;
|
||||||
this.lineLength = 80;
|
this.lineLength = 80;
|
||||||
@ -265,9 +270,9 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
this.updateItem(update as Item);
|
this.updateItem(update as Item);
|
||||||
} else if (update instanceof MotionBlock) {
|
} else if (update instanceof MotionBlock) {
|
||||||
this.updateMotionBlock(update);
|
this.updateMotionBlock(update);
|
||||||
|
} else if (update instanceof User) {
|
||||||
|
this.updateUser(update as User);
|
||||||
}
|
}
|
||||||
// TODO: There is no way (yet) to add Submitters to a motion
|
|
||||||
// Thus, this feature could not be tested
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -310,6 +315,23 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update routine for the agenda Item
|
||||||
|
* @param update potentially the changed agenda Item. Needs manual verification
|
||||||
|
*/
|
||||||
|
public updateUser(update: User): void {
|
||||||
|
if (this.motion) {
|
||||||
|
if (this.motion.submitters && this.motion.submitters.findIndex(user => user.user_id === update.id)) {
|
||||||
|
const userIndex = this.submitters.findIndex(user => user.id === update.id);
|
||||||
|
this.submitters[userIndex] = update as User;
|
||||||
|
}
|
||||||
|
if (this.motion.supporters_id && this.motion.supporters_id.includes(update.id)) {
|
||||||
|
const userIndex = this.supporters.findIndex(user => user.id === update.id);
|
||||||
|
this.supporters[userIndex] = update as User;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public hasSupporters(): boolean {
|
public hasSupporters(): boolean {
|
||||||
return !!(this.supporters && this.supporters.length > 0);
|
return !!(this.supporters && this.supporters.length > 0);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { LocalPermissionsService } from './local-permissions.service';
|
||||||
|
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||||
|
|
||||||
|
describe('LocalPermissionsService', () => {
|
||||||
|
beforeEach(() => TestBed.configureTestingModule({ imports: [E2EImportsModule] }));
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
const service: LocalPermissionsService = TestBed.get(LocalPermissionsService);
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,55 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class LocalPermissionsService {
|
||||||
|
|
||||||
|
public configMinSupporters: number;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private operator: OperatorService,
|
||||||
|
private configService: ConfigService,
|
||||||
|
) {
|
||||||
|
// load config variables
|
||||||
|
this.configService.get('motions_min_supporters').subscribe(
|
||||||
|
(supporters: number): void => {
|
||||||
|
this.configMinSupporters = supporters;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should determine if the user (Operator) has the
|
||||||
|
* correct permission to perform the given action.
|
||||||
|
*
|
||||||
|
* actions might be:
|
||||||
|
* - support
|
||||||
|
*
|
||||||
|
* @param action the action the user tries to perform
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -106,7 +106,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
* Creates a (real) motion with patched data and delegate it
|
* Creates a (real) motion with patched data and delegate it
|
||||||
* to the {@link DataSendService}
|
* to the {@link DataSendService}
|
||||||
*
|
*
|
||||||
* @param update the form data containing the update values
|
* @param update the form data containing the updated values
|
||||||
* @param viewMotion The View Motion. If not present, a new motion will be created
|
* @param viewMotion The View Motion. If not present, a new motion will be created
|
||||||
* TODO: Remove the viewMotion and make it actually distignuishable from save()
|
* TODO: Remove the viewMotion and make it actually distignuishable from save()
|
||||||
*/
|
*/
|
||||||
@ -123,7 +123,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
* Creates a (real) motion with patched data and delegate it
|
* Creates a (real) motion with patched data and delegate it
|
||||||
* to the {@link DataSendService}
|
* to the {@link DataSendService}
|
||||||
*
|
*
|
||||||
* @param update the form data containing the update values
|
* @param update the form data containing the updated values
|
||||||
* @param viewMotion The View Motion. If not present, a new motion will be created
|
* @param viewMotion The View Motion. If not present, a new motion will be created
|
||||||
*/
|
*/
|
||||||
public async update(update: Partial<Motion>, viewMotion: ViewMotion): Promise<void> {
|
public async update(update: Partial<Motion>, viewMotion: ViewMotion): Promise<void> {
|
||||||
@ -158,11 +158,23 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
* Set the recommenders state of a motion
|
* Set the recommenders state of a motion
|
||||||
*
|
*
|
||||||
* @param viewMotion target motion
|
* @param viewMotion target motion
|
||||||
* @param stateId the number that indicates the state
|
* @param recommendationId the number that indicates the recommendation
|
||||||
*/
|
*/
|
||||||
public async setRecommenderState(viewMotion: ViewMotion, stateId: number): Promise<void> {
|
public async setRecommendation(viewMotion: ViewMotion, recommendationId: number): Promise<void> {
|
||||||
const restPath = `/rest/motions/motion/${viewMotion.id}/set_recommendation/`;
|
const restPath = `/rest/motions/motion/${viewMotion.id}/set_recommendation/`;
|
||||||
await this.httpService.put(restPath, { recommendation: stateId });
|
await this.httpService.put(restPath, { recommendation: recommendationId });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the category of a motion
|
||||||
|
*
|
||||||
|
* @param viewMotion target motion
|
||||||
|
* @param categoryId the number that indicates the category
|
||||||
|
*/
|
||||||
|
public async setCatetory(viewMotion: ViewMotion, categoryId: number): Promise<void> {
|
||||||
|
const motion = viewMotion.motion;
|
||||||
|
motion.category_id = categoryId;
|
||||||
|
await this.update(motion, viewMotion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -175,6 +187,26 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
await this.httpService.post(url, data);
|
await this.httpService.post(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supports the motion
|
||||||
|
*
|
||||||
|
* @param viewMotion target motion
|
||||||
|
*/
|
||||||
|
public async support(viewMotion: ViewMotion): Promise<void> {
|
||||||
|
const url = `/rest/motions/motion/${viewMotion.id}/support/`;
|
||||||
|
await this.httpService.post(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsupports the motion
|
||||||
|
*
|
||||||
|
* @param viewMotion target motion
|
||||||
|
*/
|
||||||
|
public async unsupport(viewMotion: ViewMotion): Promise<void> {
|
||||||
|
const url = `/rest/motions/motion/${viewMotion.id}/support/`;
|
||||||
|
await this.httpService.delete(url);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format the motion text using the line numbering and change
|
* Format the motion text using the line numbering and change
|
||||||
* reco algorithm.
|
* reco algorithm.
|
||||||
|
@ -41,6 +41,10 @@ body {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
.generic-mini-button {
|
.generic-mini-button {
|
||||||
bottom: -28px;
|
bottom: -28px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
@ -114,6 +118,9 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: rgba(0, 0, 0, 0.055);
|
background-color: rgba(0, 0, 0, 0.055);
|
||||||
}
|
}
|
||||||
|
mat-row.lg {
|
||||||
|
height: 90px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-plus-distance {
|
.card-plus-distance {
|
||||||
@ -157,7 +164,6 @@ mat-panel-title mat-icon {
|
|||||||
padding-right: 30px;
|
padding-right: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.hidden-cell {
|
.hidden-cell {
|
||||||
flex: 0;
|
flex: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
@ -188,3 +194,59 @@ mat-panel-title mat-icon {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mat-chip,
|
||||||
|
.mat-basic-chip {
|
||||||
|
font-size: 12px;
|
||||||
|
min-height: 22px !important;
|
||||||
|
border-radius: 5px !important;
|
||||||
|
padding: 4px 8px !important;
|
||||||
|
margin: 8px 8px 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-chip:focus,
|
||||||
|
.mat-basic-chip:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
button.mat-menu-item.selected {
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Colors **/
|
||||||
|
.lightblue {
|
||||||
|
background-color: rgb(33, 150, 243) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.darkblue {
|
||||||
|
background-color: rgb(63, 81, 181) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green,
|
||||||
|
.success {
|
||||||
|
background-color: rgb(76, 175, 80) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red,
|
||||||
|
.error {
|
||||||
|
background-color: rgb(255, 82, 82) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow,
|
||||||
|
.warning {
|
||||||
|
background-color: rgb(255, 193, 7) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bluegrey {
|
||||||
|
background-color: rgb(96, 125, 139) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grey {
|
||||||
|
background-color: #e0e0e0 !important;
|
||||||
|
color: rgba(0, 0, 0, 0.87) !important;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user