Improved UI of motion list and detail view

This commit is contained in:
Emanuel Schütze 2018-11-23 13:59:14 +01:00 committed by Sean Engelhardt
parent 86e858c800
commit ed47ac4884
10 changed files with 382 additions and 112 deletions

View File

@ -131,19 +131,19 @@ export class HttpService {
/**
* Exectures a post on a url with a certain object
* @param url string of the url to send semothing to
* @param data The data to send
* @param url The url to send the request to.
* @param data An optional payload for the request.
* @param header optional HTTP header if required
* @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);
}
/**
* Exectures a put on a url with a certain object
* @param url string of the url to send semothing to
* @param data the object that should be send
* @param url The url to send the request to.
* @param data The payload for the request.
* @param header optional HTTP header if required
* @returns A promise holding a generic
*/
@ -153,8 +153,8 @@ export class HttpService {
/**
* Exectures a put on a url with a certain object
* @param url the url that should be called
* @param data: The data to send
* @param url The url to send the request to.
* @param data: The payload for the request.
* @param header optional HTTP header if required
* @returns A promise holding a generic
*/
@ -164,7 +164,7 @@ export class HttpService {
/**
* 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 header optional HTTP header if required
* @returns A promise holding a generic

View File

@ -79,7 +79,7 @@
<mat-accordion multi='true' class='on-transition-fade'>
<!-- 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-panel-title>
<mat-icon>info</mat-icon>
@ -93,9 +93,6 @@
</div>
</mat-expansion-panel>
<os-motion-comments *ngIf="!newMotion" [motion]="motion"></os-motion-comments>
<os-personal-note *ngIf="!newMotion" [motion]="motion"></os-personal-note>
<!-- Content -->
<mat-expansion-panel #contentPanel [expanded]='true'>
<mat-expansion-panel-header>
@ -109,6 +106,9 @@
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
</div>
</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>
</ng-template>
@ -150,7 +150,7 @@
</mat-form-field>
</div>
<!-- Submitter -->
<!-- Submitters -->
<div *ngIf="motion && motion.submitters || newMotion">
<div *ngIf="newMotion">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
@ -166,75 +166,97 @@
</div>
</div>
<!-- Supporter -->
<div *ngIf='motion && motion.hasSupporters() || editMotion'>
<!-- print all motion supporters -->
<!-- Supporters -->
<div *ngIf='motion && minSupporters'>
<div *ngIf="editMotion">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="metaInfoForm.get('supporters_id')"
[multiple]="true" listname="{{ 'Supporters' | translate }}" [InputListValues]="supporterObserver"></os-search-value-selector>
</div>
</div>
<div *ngIf="!editMotion && motion.hasSupporters()">
<h4 translate>Supporters</h4>
<ul *ngFor="let supporter of motion.supporters">
<li>{{ supporter.full_name }}</li>
</ul>
<div *ngIf="!editMotion">
<h4 *ngIf="perms.isAllowed('support', motion) || motion.hasSupporters()" translate>Supporters</h4>
<!-- support button -->
<button type="button" *ngIf="perms.isAllowed('support', motion)" (click)=support() mat-raised-button color="primary">
<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>
<!-- State -->
<div *ngIf='motion && motion.state'>
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata'];complement:true">
<ng-container *ngIf="!newMotion">
<h4 translate>State</h4>
{{ motion.state }}
</ng-container>
</div>
<div *ngIf="!editMotion">
<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-option *ngFor="let state of motion.nextStates" [value]="state.id">{{ state }}</mat-option>
<mat-divider></mat-divider>
<mat-option [value]="null">
<mat-icon>replay</mat-icon>
<span translate>Reset State</span>
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf='motion && !editMotion'>
<h4 translate>State</h4>
<mat-menu #stateMenu='matMenu'>
<button *ngFor='let state of motion.nextStates' mat-menu-item
(click)=setState(state.id)>{{ state.name | translate }}
</button>
<mat-divider></mat-divider>
<button mat-menu-item (click)=setState(null)>
<mat-icon>replay</mat-icon> {{ 'Reset state' | translate }}
</button>
</mat-menu>
<mat-basic-chip [matMenuTriggerFor]='stateMenu' [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>
<!--*osPerms="['motions.can_manage', 'motions.can_manage_metadata']; -->
</div>
<!-- Recommendation -->
<div *ngIf='motion && motion.state && recommender'>
<mat-form-field *ngIf="!editMotion && !newMotion">
<mat-select [placeholder]=recommender formControlName='recommendation_id' (selectionChange)="onChangerRecommenderState($event)">
<mat-option *ngFor="let recommendation of motion.possibleRecommendations" [value]="recommendation.id">
{{ recommendation.recommendation_label | translate }}
</mat-option>
<mat-divider></mat-divider>
<mat-option [value]="null">
<mat-icon>replay</mat-icon><span translate>Reset recommendation</span>
</mat-option>
</mat-select>
</mat-form-field>
<div *ngIf='motion && recommender && !editMotion'>
<h4 translate>{{ recommender }}</h4>
<mat-menu #recommendationMenu='matMenu'>
<button *ngFor='let recommendation of motion.possibleRecommendations' mat-menu-item
(click)=setRecommendation(recommendation.id)>{{ recommendation.recommendation_label | translate }}
</button>
<mat-divider></mat-divider>
<button mat-menu-item (click)=setRecommendation(null)>
<mat-icon>replay</mat-icon> {{ 'Reset recommendation' | translate }}
</button>
</mat-menu>
<mat-basic-chip [matMenuTriggerFor]='recommendationMenu' class="bluegrey">
{{ motion.recommendation ? (motion.recommendation.recommendation_label | translate) : ('not set' | translate) }}
</mat-basic-chip>
</div>
<!-- Category -->
<div *ngIf="motion && motion.category_id || editMotion">
<div *ngIf='!editMotion'>
<h4 translate>Category</h4>
{{ motion.category }}
</div>
<div *ngIf="editMotion || newMotion">
<os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="metaInfoForm.get('category_id')"
[multiple]="false" listname="{{ 'Category' | translate }}" [InputListValues]="categoryObserver" includeNone="true"></os-search-value-selector>
</div>
<!-- Disabled during "new motion" since changing has no effect -->
<div *ngIf="motion && !editMotion">
<h4 translate>Category</h4>
<mat-menu #categoryMenu='matMenu'>
<button *ngFor='let category of categoryObserver.value' mat-menu-item
(click)=setCategory(category.id)>{{ category }}
</button>
<button mat-menu-item (click)=setCategory(null)>
---
</button>
</mat-menu>
<mat-basic-chip [matMenuTriggerFor]='categoryMenu' class="grey">
{{ motion.category ? motion.category : ('not set' | translate) }}
</mat-basic-chip>
</div>
<!-- Workflow (just during creation) -->
<!-- Workflow -->
<div *ngIf="editMotion">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<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 }}">
<mat-icon>format_list_numbered</mat-icon>
</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>
</button>
</div>
@ -293,21 +315,15 @@
</div>
<!-- Title -->
<div *ngIf="motion && motion.title || editMotion">
<div *ngIf='!editMotion'>
<h4>{{motion.title}}</h4>
</div>
<mat-form-field *ngIf="editMotion" class="wide-form">
<input matInput osAutofocus placeholder="{{ 'Title' | translate }}" formControlName='title' [value]='motionCopy.title'
required>
<div *ngIf="motion && editMotion">
<mat-form-field class="wide-form">
<input matInput osAutofocus placeholder="{{ 'Title' | translate }}"
formControlName='title' [value]='motionCopy.title' required>
</mat-form-field>
</div>
<!-- Text -->
<!-- TODO: this is a config variable. Read it out -->
<span class="text-prefix-label" translate>The assembly may decide:</span>
<span class="text-prefix-label">{{ preamble | translate }}</span>
<ng-container *ngIf='motion && !editMotion && !motion.isStatuteAmendment()'>
<div *ngIf="!isRecoModeDiff()" class="motion-text" [class.line-numbers-none]="isLineNumberingNone()"
[class.line-numbers-inline]="isLineNumberingInline()" [class.line-numbers-outside]="isLineNumberingOutside()">
@ -343,17 +359,18 @@
[init]="tinyMceSettings"
*ngIf="editMotion"
></editor>
</div>
</form>
</ng-template>
<!-- Line number Menu -->
<mat-menu #lineNumberingMenu="matMenu" >
<button mat-menu-item translate (click)=setLineNumberingMode(0)>none</button>
<button mat-menu-item translate (click)=setLineNumberingMode(1)>inline</button>
<button mat-menu-item translate (click)=setLineNumberingMode(2)>outside</button>
<mat-menu #lineNumberingMenu="matMenu">
<div *ngIf="motion">
<button mat-menu-item translate (click)=setLineNumberingMode(0) [ngClass]="{ 'selected': motion.lnMode === 0 }">none</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>
<!-- Diff View Menu -->

View File

@ -1,7 +1,7 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
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 { ViewportService } from '../../../../core/services/viewport.service';
@ -28,6 +28,7 @@ import { StatuteParagraphRepositoryService } from '../../services/statute-paragr
import { ConfigService } from '../../../../core/services/config.service';
import { Workflow } from 'app/shared/models/motions/workflow';
import { take, takeWhile, multicast, skipWhile } from 'rxjs/operators';
import { LocalPermissionsService } from '../../services/local-permissions.service';
/**
* Component for the motion detail view
@ -94,11 +95,22 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
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
*/
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
*/
@ -154,6 +166,11 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
*/
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
*/
@ -193,6 +210,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
translate: TranslateService,
matSnackBar: MatSnackBar,
public vp: ViewportService,
public perms: LocalPermissionsService,
private op: OperatorService,
private router: Router,
private route: ActivatedRoute,
@ -226,11 +244,22 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
this.workflowObserver.next(DS.getAll(Workflow));
}
});
// load config variables
this.configService.get('motions_statutes_enabled').subscribe(
(enabled: boolean): void => {
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
* 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> {
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
* @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 {
this.motion.lnMode = mode;
@ -578,19 +606,49 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
}
/**
* Executed after selecting a state
* @param selection MatSelectChange that contains the workflow id
* Supports the motion (as requested user)
*/
public onChangeState(selection: MatSelectChange): void {
this.repo.setState(this.motion, selection.value);
public support(): void {
this.repo.support(this.motion).then(null, this.raiseError);
}
/**
* Executed after selecting the recommenders state
* @param selection MatSelectChange that contains the workflow id
* Unsupports the motion
*/
public onChangerRecommenderState(selection: MatSelectChange): void {
this.repo.setRecommenderState(this.motion, selection.value);
public unsupport(): void {
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);
}
/**

View File

@ -65,6 +65,20 @@
<span translate>by</span>
{{ motion.submitters }}
</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>
</mat-cell>
</ng-container>
@ -72,13 +86,10 @@
<!-- state column -->
<ng-container matColumnDef="state">
<mat-header-cell *matHeaderCellDef mat-sort-header>State</mat-header-cell>
<mat-cell *matCellDef="let motion">
<!--div *ngIf='isDisplayIcon(motion.state) && motion.state' class='innerTable'>
<mat-icon>{{ getStateIcon(motion.state) }}</mat-icon>
</div>-->
<mat-chip-list>
<mat-chip color="accent">{{ motion.state }}</mat-chip>
</mat-chip-list>
<mat-cell *matCellDef="let motion" (click)="selectMotion(motion)">
<div *ngIf='motion.category' class='small'>
<mat-icon>device_hub</mat-icon> {{ motion.category }}
</div>
</mat-cell>
</ng-container>
@ -98,7 +109,7 @@
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
<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-table>

View File

@ -1,8 +1,8 @@
/** css hacks https://codepen.io/edge0703/pen/iHJuA */
.innerTable {
display: inline-block;
vertical-align: middle;
line-height: normal;
vertical-align: top;
line-height: 150%;
}
.os-listview-table {
@ -23,10 +23,10 @@
.motion-list-title {
font-weight: bold;
font-size: 16px;
}
.motion-list-from {
margin-top: 5px;
color: rgba(0, 0, 0, 0.5);
font-size: 90%;
}

View File

@ -227,6 +227,11 @@ export class ViewMotion extends BaseViewModel {
this._block = block;
// 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.crMode = ChangeRecoMode.Original;
this.lineLength = 80;
@ -265,9 +270,9 @@ export class ViewMotion extends BaseViewModel {
this.updateItem(update as Item);
} else if (update instanceof MotionBlock) {
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 {
return !!(this.supporters && this.supporters.length > 0);
}

View File

@ -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();
});
});

View File

@ -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;
}
}
}
}

View File

@ -106,7 +106,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
* Creates a (real) motion with patched data and delegate it
* 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
* 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
* 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
*/
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
*
* @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/`;
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);
}
/**
* 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
* reco algorithm.

View File

@ -41,6 +41,10 @@ body {
padding: 0;
}
.small {
font-size: 90%;
}
.generic-mini-button {
bottom: -28px;
z-index: 100;
@ -114,6 +118,9 @@ body {
cursor: pointer;
background-color: rgba(0, 0, 0, 0.055);
}
mat-row.lg {
height: 90px;
}
}
.card-plus-distance {
@ -157,7 +164,6 @@ mat-panel-title mat-icon {
padding-right: 30px;
}
.hidden-cell {
flex: 0;
width: 0;
@ -188,3 +194,59 @@ mat-panel-title mat-icon {
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;
}