Motion Repository
Adds a repository to remove the logic in the motion domain object The repository will add a new model layer between the components and the domain objects implicitly add a new buttion into the motion detail view
This commit is contained in:
parent
a5f06d3347
commit
39f266d2de
@ -37,7 +37,7 @@ export class DataSendService {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return this.http.put<BaseModel>('rest/' + model.collectionString + '/' + model.id, model).pipe(
|
return this.http.patch<BaseModel>('rest/' + model.collectionString + '/' + model.id, model).pipe(
|
||||||
tap(
|
tap(
|
||||||
response => {
|
response => {
|
||||||
console.log('Update model. Response : ', response);
|
console.log('Update model. Response : ', response);
|
||||||
|
@ -39,10 +39,23 @@ export abstract class BaseModel extends OpenSlidesComponent implements Deseriali
|
|||||||
this._collectionString = collectionString;
|
this._collectionString = collectionString;
|
||||||
|
|
||||||
if (input) {
|
if (input) {
|
||||||
|
this.changeNullValuesToUndef(input);
|
||||||
this.deserialize(input);
|
this.deserialize(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent to send literally "null" if should be send
|
||||||
|
* @param input object to deserialize
|
||||||
|
*/
|
||||||
|
public changeNullValuesToUndef(input: any): void {
|
||||||
|
Object.keys(input).forEach(key => {
|
||||||
|
if (input[key] === null) {
|
||||||
|
input[key] = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns the collectionString.
|
* returns the collectionString.
|
||||||
*
|
*
|
||||||
|
@ -11,6 +11,7 @@ export abstract class Deserializer implements Deserializable {
|
|||||||
*/
|
*/
|
||||||
protected constructor(input?: any) {
|
protected constructor(input?: any) {
|
||||||
if (input) {
|
if (input) {
|
||||||
|
this.changeNullValuesToUndef(input);
|
||||||
this.deserialize(input);
|
this.deserialize(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -22,4 +23,16 @@ export abstract class Deserializer implements Deserializable {
|
|||||||
public deserialize(input: any): void {
|
public deserialize(input: any): void {
|
||||||
Object.assign(this, input);
|
Object.assign(this, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent to send literally "null" if should be send
|
||||||
|
* @param input object to deserialize
|
||||||
|
*/
|
||||||
|
public changeNullValuesToUndef(input: any): void {
|
||||||
|
Object.keys(input).forEach(key => {
|
||||||
|
if (input[key] === null) {
|
||||||
|
input[key] = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import { BaseModel } from '../base.model';
|
import { BaseModel } from '../base.model';
|
||||||
import { MotionSubmitter } from './motion-submitter';
|
import { MotionSubmitter } from './motion-submitter';
|
||||||
import { MotionLog } from './motion-log';
|
import { MotionLog } from './motion-log';
|
||||||
import { Config } from '../core/config';
|
|
||||||
import { Workflow } from './workflow';
|
|
||||||
import { User } from '../users/user';
|
|
||||||
import { Category } from './category';
|
import { Category } from './category';
|
||||||
import { WorkflowState } from './workflow-state';
|
|
||||||
import { MotionComment } from './motion-comment';
|
import { MotionComment } from './motion-comment';
|
||||||
|
import { Workflow } from './workflow';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of Motion.
|
* Representation of Motion.
|
||||||
@ -21,7 +18,7 @@ export class Motion extends BaseModel {
|
|||||||
public title: string;
|
public title: string;
|
||||||
public text: string;
|
public text: string;
|
||||||
public reason: string;
|
public reason: string;
|
||||||
public amendment_paragraphs: string;
|
public amendment_paragraphs: string[];
|
||||||
public modified_final_version: string;
|
public modified_final_version: string;
|
||||||
public parent_id: number;
|
public parent_id: number;
|
||||||
public category_id: number;
|
public category_id: number;
|
||||||
@ -30,6 +27,7 @@ export class Motion extends BaseModel {
|
|||||||
public submitters: MotionSubmitter[];
|
public submitters: MotionSubmitter[];
|
||||||
public supporters_id: number[];
|
public supporters_id: number[];
|
||||||
public comments: MotionComment[];
|
public comments: MotionComment[];
|
||||||
|
public workflow_id: number;
|
||||||
public state_id: number;
|
public state_id: number;
|
||||||
public state_extension: string;
|
public state_extension: string;
|
||||||
public state_required_permission_to_see: string;
|
public state_required_permission_to_see: string;
|
||||||
@ -41,12 +39,8 @@ export class Motion extends BaseModel {
|
|||||||
public agenda_item_id: number;
|
public agenda_item_id: number;
|
||||||
public log_messages: MotionLog[];
|
public log_messages: MotionLog[];
|
||||||
|
|
||||||
// dynamic values
|
|
||||||
public workflow: Workflow;
|
|
||||||
|
|
||||||
public constructor(input?: any) {
|
public constructor(input?: any) {
|
||||||
super('motions/motion', input);
|
super('motions/motion', input);
|
||||||
this.initDataStoreValues();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,121 +51,19 @@ export class Motion extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sets the and the workflow from either dataStore or WebSocket
|
* returns the motion submitters userIDs
|
||||||
*/
|
*/
|
||||||
public initDataStoreValues(): void {
|
public get submitterIds(): number[] {
|
||||||
// check the containing Workflows in DataStore
|
return this.submitters
|
||||||
const allWorkflows = this.DS.getAll(Workflow);
|
|
||||||
allWorkflows.forEach(localWorkflow => {
|
|
||||||
if (localWorkflow.isStateContained(this.state_id)) {
|
|
||||||
this.workflow = localWorkflow as Workflow;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// observe for new models
|
|
||||||
this.DS.changeObservable.subscribe(newModel => {
|
|
||||||
if (newModel instanceof Workflow) {
|
|
||||||
if (newModel.isStateContained(this.state_id)) {
|
|
||||||
this.workflow = newModel as Workflow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add a new motionSubmitter from user-object
|
|
||||||
* @param user the user
|
|
||||||
*/
|
|
||||||
public addSubmitter(user: User): void {
|
|
||||||
const newSubmitter = new MotionSubmitter();
|
|
||||||
newSubmitter.user_id = user.id;
|
|
||||||
this.submitters.push(newSubmitter);
|
|
||||||
console.log('did addSubmitter. this.submitters: ', this.submitters);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* return the submitters as uses objects
|
|
||||||
*/
|
|
||||||
public get submitterAsUser(): User[] {
|
|
||||||
const submitterIds: number[] = this.submitters
|
|
||||||
.sort((a: MotionSubmitter, b: MotionSubmitter) => {
|
.sort((a: MotionSubmitter, b: MotionSubmitter) => {
|
||||||
return a.weight - b.weight;
|
return a.weight - b.weight;
|
||||||
})
|
})
|
||||||
.map((submitter: MotionSubmitter) => submitter.user_id);
|
.map((submitter: MotionSubmitter) => submitter.user_id);
|
||||||
return this.DS.getMany<User>('users/user', submitterIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the category of a motion as object
|
|
||||||
*/
|
|
||||||
public get category(): Category {
|
|
||||||
return this.DS.get<Category>(Category, this.category_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the category in the motion
|
|
||||||
*/
|
|
||||||
public set category(newCategory: Category) {
|
|
||||||
this.category_id = newCategory.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* return the workflow state
|
|
||||||
*/
|
|
||||||
public get state(): WorkflowState {
|
|
||||||
if (this.workflow) {
|
|
||||||
return this.workflow.state_by_id(this.state_id);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns possible states for the motion
|
|
||||||
*/
|
|
||||||
public get nextStates(): WorkflowState[] {
|
|
||||||
if (this.workflow && this.state) {
|
|
||||||
return this.state.getNextStates(this.workflow);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the recommendation.
|
|
||||||
*
|
|
||||||
* TODO: Motion workflow needs to be specific on the server
|
|
||||||
*/
|
|
||||||
public get recommendation(): WorkflowState {
|
|
||||||
if (this.recommendation_id && this.workflow && this.workflow.id) {
|
|
||||||
const state = this.workflow.state_by_id(this.recommendation_id);
|
|
||||||
return state;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the value of 'config.motions_recommendations_by'
|
|
||||||
*/
|
|
||||||
public get recomBy(): string {
|
|
||||||
const motionsRecommendationsByConfig = this.DS.filter<Config>(
|
|
||||||
Config,
|
|
||||||
config => config.key === 'motions_recommendations_by'
|
|
||||||
)[0] as Config;
|
|
||||||
|
|
||||||
if (motionsRecommendationsByConfig) {
|
|
||||||
const recomByString: string = motionsRecommendationsByConfig.value as string;
|
|
||||||
return recomByString;
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public deserialize(input: any): void {
|
public deserialize(input: any): void {
|
||||||
Object.assign(this, input);
|
Object.assign(this, input);
|
||||||
|
|
||||||
this.submitters = [];
|
|
||||||
if (input.submitters instanceof Array) {
|
if (input.submitters instanceof Array) {
|
||||||
input.submitters.forEach(SubmitterData => {
|
input.submitters.forEach(SubmitterData => {
|
||||||
this.submitters.push(new MotionSubmitter(SubmitterData));
|
this.submitters.push(new MotionSubmitter(SubmitterData));
|
||||||
@ -194,4 +86,9 @@ export class Motion extends BaseModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hack to get them loaded at last
|
||||||
|
*/
|
||||||
BaseModel.registerCollectionElement('motions/motion', Motion);
|
BaseModel.registerCollectionElement('motions/motion', Motion);
|
||||||
|
BaseModel.registerCollectionElement('motions/category', Category);
|
||||||
|
BaseModel.registerCollectionElement('motions/workflow', Workflow);
|
||||||
|
@ -34,7 +34,7 @@ export class Workflow extends BaseModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public state_by_id(id: number): WorkflowState {
|
public getStateById(id: number): WorkflowState {
|
||||||
let targetState;
|
let targetState;
|
||||||
this.states.forEach(state => {
|
this.states.forEach(state => {
|
||||||
if (id === state.id) {
|
if (id === state.id) {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<main>
|
<main>
|
||||||
<mat-toolbar color="primary" translate>
|
|
||||||
Legal Notice
|
<mat-card class="os-card">
|
||||||
</mat-toolbar>
|
<h2 translate>Legal Notice</h2>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
<os-legal-notice-content></os-legal-notice-content>
|
<os-legal-notice-content></os-legal-notice-content>
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<main>
|
<main>
|
||||||
<mat-toolbar color="primary" translate>
|
|
||||||
Privacy Policy
|
<mat-card class="os-card">
|
||||||
</mat-toolbar>
|
<h2 translate>Privacy Policy</h2>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
<os-privacy-policy-content></os-privacy-policy-content>
|
<os-privacy-policy-content></os-privacy-policy-content>
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { MatSort, MatTable, MatTableDataSource } from '@angular/material';
|
import { MatSort, MatTable, MatTableDataSource } from '@angular/material';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { BaseComponent } from '../../../base.component';
|
import { BaseComponent } from '../../../../base.component';
|
||||||
import { Category } from '../../../shared/models/motions/category';
|
import { Category } from '../../../../shared/models/motions/category';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List view for the categories.
|
* List view for the categories.
|
@ -16,16 +16,26 @@
|
|||||||
<span *ngIf="editMotion"> {{contentForm.get('title').value}}</span>
|
<span *ngIf="editMotion"> {{contentForm.get('title').value}}</span>
|
||||||
<br>
|
<br>
|
||||||
<div *ngIf="motion" class='motion-submitter'>
|
<div *ngIf="motion" class='motion-submitter'>
|
||||||
<span translate>by</span> {{motion.submitterAsUser}}
|
<span translate>by</span> {{motion.submitters}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class='spacer'></span>
|
<span class='spacer'></span>
|
||||||
|
|
||||||
<button class='on-transition-fade' mat-icon-button [matMenuTriggerFor]="motionExtraMenu">
|
<!-- Button on the right-->
|
||||||
<fa-icon icon='ellipsis-v'></fa-icon>
|
<div *ngIf="editMotion">
|
||||||
</button>
|
<button (click)='cancelEditMotionButton()' class='on-transition-fade' mat-icon-button>
|
||||||
|
<fa-icon icon='times'></fa-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="!editMotion">
|
||||||
|
<button class='on-transition-fade' mat-icon-button [matMenuTriggerFor]="motionExtraMenu">
|
||||||
|
<fa-icon icon='ellipsis-v'></fa-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<mat-menu #motionExtraMenu="matMenu">
|
<mat-menu #motionExtraMenu="matMenu">
|
||||||
<!-- TODO the functions for the buttons -->
|
<!-- TODO: the functions for the buttons -->
|
||||||
<button mat-menu-item translate>Export As...</button>
|
<button mat-menu-item translate>Export As...</button>
|
||||||
<button mat-menu-item translate>Project</button>
|
<button mat-menu-item translate>Project</button>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
@ -137,24 +147,24 @@
|
|||||||
<!-- Submitter -->
|
<!-- Submitter -->
|
||||||
<div *ngIf="motion && motion.submitters || editMotion">
|
<div *ngIf="motion && motion.submitters || editMotion">
|
||||||
<h3 translate>Submitters</h3>
|
<h3 translate>Submitters</h3>
|
||||||
{{motion.submitterAsUser}}
|
{{motion.submitters}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Supporter -->
|
<!-- Supporter -->
|
||||||
<div *ngIf='motion && motion.supporters_id && motion.supporters_id.length > 0 || editMotion'>
|
<div *ngIf='motion && motion.hasSupporters() || editMotion'>
|
||||||
<h3 translate>Supporters</h3>
|
<h3 translate>Supporters</h3>
|
||||||
<!-- print all motion supporters -->
|
<!-- print all motion supporters -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- State -->
|
<!-- State -->
|
||||||
<div *ngIf='!newMotion && motion && motion.workflow && motion.state_id || editMotion'>
|
<div *ngIf='!newMotion && motion && motion.workflow && motion.state || editMotion'>
|
||||||
<div *ngIf='!editMotion'>
|
<div *ngIf='!editMotion'>
|
||||||
<h3 translate>State</h3>
|
<h3 translate>State</h3>
|
||||||
{{motion.state}}
|
{{motion.state}}
|
||||||
</div>
|
</div>
|
||||||
<mat-form-field *ngIf="editMotion && !newMotion">
|
<mat-form-field *ngIf="editMotion && !newMotion">
|
||||||
<mat-select placeholder='State' formControlName='state_id'>
|
<mat-select placeholder='State' formControlName='state_id'>
|
||||||
<mat-option [value]="motionCopy.state.id">{{motionCopy.state}}</mat-option>
|
<mat-option [value]="motionCopy.stateId">{{motionCopy.state}}</mat-option>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<mat-option *ngFor="let state of motionCopy.nextStates" [value]="state.id">{{state}}</mat-option>
|
<mat-option *ngFor="let state of motionCopy.nextStates" [value]="state.id">{{state}}</mat-option>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
@ -167,10 +177,10 @@
|
|||||||
|
|
||||||
<!-- Recommendation -->
|
<!-- Recommendation -->
|
||||||
<!-- The suggestion of the work group weather or not a motion should be accepted -->
|
<!-- The suggestion of the work group weather or not a motion should be accepted -->
|
||||||
<div *ngIf='motion && motion.recomBy && (motion.recommendation_id || editMotion)'>
|
<div *ngIf='motion && motion.recommender && (motion.recommendationId || editMotion)'>
|
||||||
<div *ngIf='!editMotion'>
|
<div *ngIf='!editMotion'>
|
||||||
<h3>{{motion.recomBy}}</h3>
|
<h3>{{motion.recommender}}</h3>
|
||||||
{{motion.recommendation.name}}
|
{{motion.recommendation}}
|
||||||
</div>
|
</div>
|
||||||
<mat-form-field *ngIf="motion && editMotion">
|
<mat-form-field *ngIf="motion && editMotion">
|
||||||
<mat-select placeholder='Recommendation' formControlName='recommendation_id'>
|
<mat-select placeholder='Recommendation' formControlName='recommendation_id'>
|
||||||
@ -187,7 +197,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Category -->
|
<!-- Category -->
|
||||||
<div *ngIf="motion && motion.category_id || editMotion">
|
<div *ngIf="motion && motion.categoryId || editMotion">
|
||||||
<div *ngIf='!editMotion'>
|
<div *ngIf='!editMotion'>
|
||||||
<h3 translate> Category</h3>
|
<h3 translate> Category</h3>
|
||||||
{{motion.category}}
|
{{motion.category}}
|
@ -3,11 +3,11 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||||
import { MatExpansionPanel } from '@angular/material';
|
import { MatExpansionPanel } from '@angular/material';
|
||||||
|
|
||||||
import { BaseComponent } from '../../../base.component';
|
import { BaseComponent } from '../../../../base.component';
|
||||||
import { Motion } from '../../../shared/models/motions/motion';
|
import { Category } from '../../../../shared/models/motions/category';
|
||||||
import { Category } from '../../../shared/models/motions/category';
|
import { ViewportService } from '../../../../core/services/viewport.service';
|
||||||
import { DataSendService } from '../../../core/services/data-send.service';
|
import { MotionRepositoryService } from '../../services/motion-repository.service';
|
||||||
import { ViewportService } from '../../../core/services/viewport.service';
|
import { ViewMotion } from '../../models/view-motion';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for the motion detail view
|
* Component for the motion detail view
|
||||||
@ -20,24 +20,16 @@ import { ViewportService } from '../../../core/services/viewport.service';
|
|||||||
export class MotionDetailComponent extends BaseComponent implements OnInit {
|
export class MotionDetailComponent extends BaseComponent implements OnInit {
|
||||||
/**
|
/**
|
||||||
* MatExpansionPanel for the meta info
|
* MatExpansionPanel for the meta info
|
||||||
|
* Only relevant in mobile view
|
||||||
*/
|
*/
|
||||||
@ViewChild('metaInfoPanel') public metaInfoPanel: MatExpansionPanel;
|
@ViewChild('metaInfoPanel') public metaInfoPanel: MatExpansionPanel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MatExpansionPanel for the content panel
|
* MatExpansionPanel for the content panel
|
||||||
|
* Only relevant in mobile view
|
||||||
*/
|
*/
|
||||||
@ViewChild('contentPanel') public contentPanel: MatExpansionPanel;
|
@ViewChild('contentPanel') public contentPanel: MatExpansionPanel;
|
||||||
|
|
||||||
/**
|
|
||||||
* Target motion. Might be new or old
|
|
||||||
*/
|
|
||||||
public motion: Motion;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy of the motion that the user might edit
|
|
||||||
*/
|
|
||||||
public motionCopy: Motion;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Motions meta-info
|
* Motions meta-info
|
||||||
*/
|
*/
|
||||||
@ -58,6 +50,16 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public newMotion = false;
|
public newMotion = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Target motion. Might be new or old
|
||||||
|
*/
|
||||||
|
public motion: ViewMotion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy of the motion that the user might edit
|
||||||
|
*/
|
||||||
|
public motionCopy: ViewMotion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constuct the detail view.
|
* Constuct the detail view.
|
||||||
*
|
*
|
||||||
@ -65,14 +67,14 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
|
|||||||
* @param router to navigate back to the motion list and to an existing motion
|
* @param router to navigate back to the motion list and to an existing motion
|
||||||
* @param route determine if this is a new or an existing motion
|
* @param route determine if this is a new or an existing motion
|
||||||
* @param formBuilder For reactive forms. Form Group and Form Control
|
* @param formBuilder For reactive forms. Form Group and Form Control
|
||||||
* @param dataSend To send changes of the motion
|
* @param repo: Motion Repository
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
public vp: ViewportService,
|
public vp: ViewportService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private dataSend: DataSendService
|
private repo: MotionRepositoryService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.createForm();
|
this.createForm();
|
||||||
@ -82,21 +84,14 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
|
|||||||
this.editMotion = true;
|
this.editMotion = true;
|
||||||
|
|
||||||
// Both are (temporarily) necessary until submitter and supporters are implemented
|
// Both are (temporarily) necessary until submitter and supporters are implemented
|
||||||
this.motion = new Motion();
|
// TODO new Motion and ViewMotion
|
||||||
this.motionCopy = new Motion();
|
this.motion = new ViewMotion();
|
||||||
|
this.motionCopy = new ViewMotion();
|
||||||
} else {
|
} else {
|
||||||
// load existing motion
|
// load existing motion
|
||||||
this.route.params.subscribe(params => {
|
this.route.params.subscribe(params => {
|
||||||
// has the motion of the DataStore was initialized before.
|
this.repo.getViewMotionObservable(params.id).subscribe(newViewMotion => {
|
||||||
this.motion = this.DS.get(Motion, params.id);
|
this.motion = newViewMotion;
|
||||||
|
|
||||||
// Observe motion to get the motion in the parameter and also get the changes
|
|
||||||
this.DS.changeObservable.subscribe(newModel => {
|
|
||||||
if (newModel instanceof Motion) {
|
|
||||||
if (newModel.id === +params.id) {
|
|
||||||
this.motion = newModel as Motion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -105,11 +100,11 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* Async load the values of the motion in the Form.
|
* Async load the values of the motion in the Form.
|
||||||
*/
|
*/
|
||||||
public patchForm(formMotion: Motion): void {
|
public patchForm(formMotion: ViewMotion): void {
|
||||||
this.metaInfoForm.patchValue({
|
this.metaInfoForm.patchValue({
|
||||||
category_id: formMotion.category_id,
|
category_id: formMotion.categoryId,
|
||||||
state_id: formMotion.state_id,
|
state_id: formMotion.stateId,
|
||||||
recommendation_id: formMotion.recommendation_id,
|
recommendation_id: formMotion.recommendationId,
|
||||||
identifier: formMotion.identifier,
|
identifier: formMotion.identifier,
|
||||||
origin: formMotion.origin
|
origin: formMotion.origin
|
||||||
});
|
});
|
||||||
@ -148,21 +143,22 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
|
|||||||
* in the list view automatically
|
* in the list view automatically
|
||||||
*
|
*
|
||||||
* TODO: state is not yet saved. Need a special "put" command
|
* TODO: state is not yet saved. Need a special "put" command
|
||||||
|
*
|
||||||
|
* TODO: Repo should handle
|
||||||
*/
|
*/
|
||||||
public saveMotion(): void {
|
public saveMotion(): void {
|
||||||
const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value };
|
const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value };
|
||||||
this.motionCopy.patchValues(newMotionValues);
|
if (this.newMotion) {
|
||||||
|
this.repo.saveMotion(newMotionValues).subscribe(response => {
|
||||||
// TODO: send to normal motion to verify
|
this.router.navigate(['./motions/' + response.id]);
|
||||||
this.dataSend.saveModel(this.motionCopy).subscribe(answer => {
|
});
|
||||||
if (answer && answer.id && this.newMotion) {
|
} else {
|
||||||
this.router.navigate(['./motions/' + answer.id]);
|
this.repo.saveMotion(newMotionValues, this.motionCopy).subscribe();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return all Categories.
|
* return all Categories
|
||||||
*/
|
*/
|
||||||
public getMotionCategories(): Category[] {
|
public getMotionCategories(): Category[] {
|
||||||
return this.DS.getAll(Category);
|
return this.DS.getAll(Category);
|
||||||
@ -175,10 +171,8 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
|
|||||||
this.editMotion ? (this.editMotion = false) : (this.editMotion = true);
|
this.editMotion ? (this.editMotion = false) : (this.editMotion = true);
|
||||||
if (this.editMotion) {
|
if (this.editMotion) {
|
||||||
// copy the motion
|
// copy the motion
|
||||||
this.motionCopy = new Motion();
|
this.motionCopy = this.motion.copy();
|
||||||
this.motionCopy.patchValues(this.motion);
|
|
||||||
this.patchForm(this.motionCopy);
|
this.patchForm(this.motionCopy);
|
||||||
|
|
||||||
if (this.vp.isMobile) {
|
if (this.vp.isMobile) {
|
||||||
this.metaInfoPanel.open();
|
this.metaInfoPanel.open();
|
||||||
this.contentPanel.open();
|
this.contentPanel.open();
|
||||||
@ -188,11 +182,26 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the editing process
|
||||||
|
*
|
||||||
|
* If a new motion was created, return to the list.
|
||||||
|
*/
|
||||||
|
public cancelEditMotionButton(): void {
|
||||||
|
if (this.newMotion) {
|
||||||
|
this.router.navigate(['./motions/']);
|
||||||
|
} else {
|
||||||
|
this.editMotion = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger to delete the motion
|
* Trigger to delete the motion
|
||||||
|
*
|
||||||
|
* TODO: Repo should handle
|
||||||
*/
|
*/
|
||||||
public deleteMotionButton(): void {
|
public deleteMotionButton(): void {
|
||||||
this.dataSend.delete(this.motion).subscribe(answer => {
|
this.repo.deleteMotion(this.motion).subscribe(answer => {
|
||||||
this.router.navigate(['./motions/']);
|
this.router.navigate(['./motions/']);
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -26,11 +26,11 @@
|
|||||||
<mat-header-cell *matHeaderCellDef mat-sort-header> Title </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header> Title </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let motion">
|
<mat-cell *matCellDef="let motion">
|
||||||
<div class='innerTable'>
|
<div class='innerTable'>
|
||||||
<span class='motion-list-title'>{{motion.versions[0].title}}</span>
|
<span class='motion-list-title'>{{motion.title}}</span>
|
||||||
<br>
|
<br>
|
||||||
<span class='motion-list-from'>
|
<span class='motion-list-from'>
|
||||||
<span translate>by</span>
|
<span translate>by</span>
|
||||||
{{motion.submitterAsUser}}
|
{{motion.submitters}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</mat-cell>
|
</mat-cell>
|
@ -1,12 +1,14 @@
|
|||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { Router, ActivatedRoute } from '@angular/router';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { BaseComponent } from 'app/base.component';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
import { Motion } from '../../../shared/models/motions/motion';
|
|
||||||
import { MatTable, MatPaginator, MatSort, MatTableDataSource } from '@angular/material';
|
import { MatTable, MatPaginator, MatSort, MatTableDataSource } from '@angular/material';
|
||||||
import { Workflow } from '../../../shared/models/motions/workflow';
|
|
||||||
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { BaseComponent } from '../../../../base.component';
|
||||||
|
import { MotionRepositoryService } from '../../services/motion-repository.service';
|
||||||
|
import { ViewMotion } from '../../models/view-motion';
|
||||||
|
import { WorkflowState } from '../../../../shared/models/motions/workflow-state';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays all the motions in a Table using DataSource.
|
* Component that displays all the motions in a Table using DataSource.
|
||||||
@ -17,25 +19,17 @@ import { WorkflowState } from '../../../shared/models/motions/workflow-state';
|
|||||||
styleUrls: ['./motion-list.component.scss']
|
styleUrls: ['./motion-list.component.scss']
|
||||||
})
|
})
|
||||||
export class MotionListComponent extends BaseComponent implements OnInit {
|
export class MotionListComponent extends BaseComponent implements OnInit {
|
||||||
/**
|
|
||||||
* Store motion workflows (to check the status of the motions)
|
|
||||||
*/
|
|
||||||
public workflowArray: Array<Workflow>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store the motions
|
|
||||||
*/
|
|
||||||
public motionArray: Array<Motion>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will be processed by the mat-table
|
* Will be processed by the mat-table
|
||||||
|
*
|
||||||
|
* Will represent the object that comes from the repository
|
||||||
*/
|
*/
|
||||||
public dataSource: MatTableDataSource<Motion>;
|
public dataSource: MatTableDataSource<ViewMotion>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The table itself.
|
* The table itself.
|
||||||
*/
|
*/
|
||||||
@ViewChild(MatTable) public table: MatTable<Motion>;
|
@ViewChild(MatTable) public table: MatTable<ViewMotion>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pagination. Might be turned off to all motions at once.
|
* Pagination. Might be turned off to all motions at once.
|
||||||
@ -54,6 +48,8 @@ export class MotionListComponent extends BaseComponent implements OnInit {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Use for maximal width
|
* Use for maximal width
|
||||||
|
*
|
||||||
|
* TODO: Needs vp.desktop check
|
||||||
*/
|
*/
|
||||||
public columnsToDisplayFullWidth = ['identifier', 'title', 'meta', 'state'];
|
public columnsToDisplayFullWidth = ['identifier', 'title', 'meta', 'state'];
|
||||||
|
|
||||||
@ -79,12 +75,14 @@ export class MotionListComponent extends BaseComponent implements OnInit {
|
|||||||
* @param translate Translation
|
* @param translate Translation
|
||||||
* @param router Router
|
* @param router Router
|
||||||
* @param route Current route
|
* @param route Current route
|
||||||
|
* @param repo Motion Repository
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
protected titleService: Title,
|
protected titleService: Title,
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private route: ActivatedRoute
|
private route: ActivatedRoute,
|
||||||
|
private repo: MotionRepositoryService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate);
|
super(titleService, translate);
|
||||||
}
|
}
|
||||||
@ -94,19 +92,13 @@ export class MotionListComponent extends BaseComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
super.setTitle('Motions');
|
super.setTitle('Motions');
|
||||||
this.workflowArray = this.DS.getAll(Workflow);
|
|
||||||
this.motionArray = this.DS.getAll(Motion);
|
this.dataSource = new MatTableDataSource();
|
||||||
this.dataSource = new MatTableDataSource(this.motionArray);
|
|
||||||
this.dataSource.paginator = this.paginator;
|
this.dataSource.paginator = this.paginator;
|
||||||
this.dataSource.sort = this.sort;
|
this.dataSource.sort = this.sort;
|
||||||
|
|
||||||
// Observe DataStore for motions. Initially, executes once for every motion.
|
this.repo.getViewMotionListObservable().subscribe(newMotions => {
|
||||||
// The alternative approach is to put the observable as DataSource to the table
|
this.dataSource.data = newMotions;
|
||||||
this.DS.changeObservable.subscribe(newModel => {
|
|
||||||
if (newModel instanceof Motion) {
|
|
||||||
this.motionArray = this.DS.getAll(Motion);
|
|
||||||
this.dataSource.data = this.motionArray;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,12 +107,12 @@ export class MotionListComponent extends BaseComponent implements OnInit {
|
|||||||
*
|
*
|
||||||
* @param motion The row the user clicked at
|
* @param motion The row the user clicked at
|
||||||
*/
|
*/
|
||||||
public selectMotion(motion: Motion): void {
|
public selectMotion(motion: ViewMotion): void {
|
||||||
this.router.navigate(['./' + motion.id], { relativeTo: this.route });
|
this.router.navigate(['./' + motion.id], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the icon to the coresponding Motion Status
|
* Get the icon to the corresponding Motion Status
|
||||||
* TODO Needs to be more accessible (Motion workflow needs adjustment on the server)
|
* TODO Needs to be more accessible (Motion workflow needs adjustment on the server)
|
||||||
* @param state the name of the state
|
* @param state the name of the state
|
||||||
*/
|
*/
|
||||||
@ -142,7 +134,11 @@ export class MotionListComponent extends BaseComponent implements OnInit {
|
|||||||
* @param state
|
* @param state
|
||||||
*/
|
*/
|
||||||
public isDisplayIcon(state: WorkflowState): boolean {
|
public isDisplayIcon(state: WorkflowState): boolean {
|
||||||
return state.name === 'accepted' || state.name === 'rejected' || state.name === 'not decided';
|
if (state) {
|
||||||
|
return state.name === 'accepted' || state.name === 'rejected' || state.name === 'not decided';
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
193
client/src/app/site/motions/models/view-motion.ts
Normal file
193
client/src/app/site/motions/models/view-motion.ts
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import { Motion } from '../../../shared/models/motions/motion';
|
||||||
|
import { Category } from '../../../shared/models/motions/category';
|
||||||
|
import { User } from '../../../shared/models/users/user';
|
||||||
|
import { Workflow } from '../../../shared/models/motions/workflow';
|
||||||
|
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
|
||||||
|
import { BaseModel } from '../../../shared/models/base.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Motion class for the View
|
||||||
|
*
|
||||||
|
* Stores a motion including all (implicit) references
|
||||||
|
* Provides "safe" access to variables and functions in {@link Motion}
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export class ViewMotion {
|
||||||
|
private _motion: Motion;
|
||||||
|
private _category: Category;
|
||||||
|
private _submitters: User[];
|
||||||
|
private _supporters: User[];
|
||||||
|
private _workflow: Workflow;
|
||||||
|
private _state: WorkflowState;
|
||||||
|
|
||||||
|
public get motion(): Motion {
|
||||||
|
return this._motion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): number {
|
||||||
|
if (this.motion) {
|
||||||
|
return this.motion.id;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get identifier(): string {
|
||||||
|
if (this.motion) {
|
||||||
|
return this.motion.identifier;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get title(): string {
|
||||||
|
if (this.motion) {
|
||||||
|
return this.motion.title;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
if (this.motion) {
|
||||||
|
return this.motion.text;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get reason(): string {
|
||||||
|
if (this.motion) {
|
||||||
|
return this.motion.reason;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get category(): Category {
|
||||||
|
return this._category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get categoryId(): number {
|
||||||
|
if (this._motion && this._motion.category_id) {
|
||||||
|
return this.category.id;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get submitters(): User[] {
|
||||||
|
return this._submitters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get supporters(): User[] {
|
||||||
|
return this._supporters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get workflow(): Workflow {
|
||||||
|
return this._workflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get state(): WorkflowState {
|
||||||
|
return this._state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get stateId(): number {
|
||||||
|
if (this._motion && this._motion.state_id) {
|
||||||
|
return this._motion.state_id;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get recommendationId(): number {
|
||||||
|
return this._motion.recommendation_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXME:
|
||||||
|
* name of recommender exist in a config
|
||||||
|
* previously solved using `this.DS.filter<Config>(Config)`
|
||||||
|
* and checking: motionsRecommendationsByConfig.value
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public get recommender(): string {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get recommendation(): WorkflowState {
|
||||||
|
if (this.recommendationId && this.workflow) {
|
||||||
|
return this.workflow.getStateById(this.recommendationId);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get origin(): string {
|
||||||
|
if (this.motion) {
|
||||||
|
return this.motion.origin;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get nextStates(): WorkflowState[] {
|
||||||
|
if (this.state && this.workflow) {
|
||||||
|
return this.state.getNextStates(this.workflow);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
motion?: Motion,
|
||||||
|
category?: Category,
|
||||||
|
submitters?: User[],
|
||||||
|
supporters?: User[],
|
||||||
|
workflow?: Workflow,
|
||||||
|
state?: WorkflowState
|
||||||
|
) {
|
||||||
|
this._motion = motion;
|
||||||
|
this._category = category;
|
||||||
|
this._submitters = submitters;
|
||||||
|
this._supporters = supporters;
|
||||||
|
this._workflow = workflow;
|
||||||
|
this._state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the local objects if required
|
||||||
|
* @param update
|
||||||
|
*/
|
||||||
|
public updateValues(update: BaseModel): void {
|
||||||
|
if (update instanceof Workflow) {
|
||||||
|
if (this.motion && update.id === this.motion.workflow_id) {
|
||||||
|
this._workflow = update as Workflow;
|
||||||
|
}
|
||||||
|
} else if (update instanceof Category) {
|
||||||
|
if (this.motion && update.id === this.motion.category_id) {
|
||||||
|
this._category = update as Category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: There is no way (yet) to add Submitters to a motion
|
||||||
|
// Thus, this feature could not be tested
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasSupporters(): boolean {
|
||||||
|
return !!(this.supporters && this.supporters.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicate this motion into a copy of itself
|
||||||
|
*/
|
||||||
|
public copy(): ViewMotion {
|
||||||
|
return new ViewMotion(
|
||||||
|
this._motion,
|
||||||
|
this._category,
|
||||||
|
this._submitters,
|
||||||
|
this._supporters,
|
||||||
|
this._workflow,
|
||||||
|
this._state
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
import { MotionListComponent } from './motion-list/motion-list.component';
|
import { MotionListComponent } from './components/motion-list/motion-list.component';
|
||||||
import { MotionDetailComponent } from './motion-detail/motion-detail.component';
|
import { MotionDetailComponent } from './components/motion-detail/motion-detail.component';
|
||||||
import { CategoryListComponent } from './category-list/category-list.component';
|
import { CategoryListComponent } from './components/category-list/category-list.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: MotionListComponent },
|
{ path: '', component: MotionListComponent },
|
||||||
|
@ -3,9 +3,9 @@ import { CommonModule } from '@angular/common';
|
|||||||
|
|
||||||
import { MotionsRoutingModule } from './motions-routing.module';
|
import { MotionsRoutingModule } from './motions-routing.module';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
import { MotionListComponent } from './motion-list/motion-list.component';
|
import { MotionListComponent } from './components/motion-list/motion-list.component';
|
||||||
import { MotionDetailComponent } from './motion-detail/motion-detail.component';
|
import { MotionDetailComponent } from './components/motion-detail/motion-detail.component';
|
||||||
import { CategoryListComponent } from './category-list/category-list.component';
|
import { CategoryListComponent } from './components/category-list/category-list.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, MotionsRoutingModule, SharedModule],
|
imports: [CommonModule, MotionsRoutingModule, SharedModule],
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MotionRepositoryService } from './motion-repository.service';
|
||||||
|
|
||||||
|
describe('MotionRepositoryService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [MotionRepositoryService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([MotionRepositoryService], (service: MotionRepositoryService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
@ -0,0 +1,194 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { DataSendService } from '../../../core/services/data-send.service';
|
||||||
|
import { OpenSlidesComponent } from '../../../openslides.component';
|
||||||
|
import { Motion } from '../../../shared/models/motions/motion';
|
||||||
|
import { User } from '../../../shared/models/users/user';
|
||||||
|
import { Category } from '../../../shared/models/motions/category';
|
||||||
|
import { Workflow } from '../../../shared/models/motions/workflow';
|
||||||
|
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
|
||||||
|
import { ViewMotion } from '../models/view-motion';
|
||||||
|
import { Observable, BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository Services for motions (and potentially categories)
|
||||||
|
*
|
||||||
|
* The repository is meant to process domain objects (those found under
|
||||||
|
* shared/models), so components can display them and interact with them.
|
||||||
|
*
|
||||||
|
* Rather than manipulating models directly, the repository is meant to
|
||||||
|
* inform the {@link DataSendService} about changes which will send
|
||||||
|
* them to the Server.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class MotionRepositoryService extends OpenSlidesComponent {
|
||||||
|
/**
|
||||||
|
* Stores all the viewMotion in an object
|
||||||
|
*/
|
||||||
|
private viewMotionStore: { [motionId: number]: ViewMotion } = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores subjects to viewMotions in a list
|
||||||
|
*/
|
||||||
|
private viewMotionSubjects: { [motionId: number]: BehaviorSubject<ViewMotion> } = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable subject for the whole list
|
||||||
|
*/
|
||||||
|
private viewMotionListSubject: BehaviorSubject<ViewMotion[]> = new BehaviorSubject<ViewMotion[]>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a MotionRepository
|
||||||
|
*
|
||||||
|
* Converts existing and incoming motions to ViewMotions
|
||||||
|
* Handles CRUD using an observer to the DataStore
|
||||||
|
* @param DataSend
|
||||||
|
*/
|
||||||
|
public constructor(private dataSend: DataSendService) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.populateViewMotions();
|
||||||
|
|
||||||
|
// Could be raise in error if the root injector is not known
|
||||||
|
this.DS.changeObservable.subscribe(model => {
|
||||||
|
if (model instanceof Motion) {
|
||||||
|
// Add new and updated motions to the viewMotionStore
|
||||||
|
this.AddViewMotion(model);
|
||||||
|
this.updateObservables(model.id);
|
||||||
|
} else if (model instanceof Category || model instanceof User || model instanceof Workflow) {
|
||||||
|
// if an domain object we need was added or changed, update ViewMotionStore
|
||||||
|
this.getViewMotionList().forEach(viewMotion => {
|
||||||
|
viewMotion.updateValues(model);
|
||||||
|
});
|
||||||
|
this.updateObservables(model.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch the Observables for deleting
|
||||||
|
this.DS.deletedObservable.subscribe(model => {
|
||||||
|
if (model.collection === 'motions/motion') {
|
||||||
|
delete this.viewMotionStore[model.id];
|
||||||
|
this.updateObservables(model.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called from the constructor.
|
||||||
|
*
|
||||||
|
* Populate the local viewMotionStore with ViewMotion Objects.
|
||||||
|
* Does nothing if the database was not created yet.
|
||||||
|
*/
|
||||||
|
private populateViewMotions(): void {
|
||||||
|
this.DS.getAll<Motion>(Motion).forEach(motion => {
|
||||||
|
this.AddViewMotion(motion);
|
||||||
|
this.updateViewMotionObservable(motion.id);
|
||||||
|
});
|
||||||
|
this.updateViewMotionListObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a motion to a ViewMotion and adds it to the store.
|
||||||
|
*
|
||||||
|
* Foreign references of the motion will be resolved (e.g submitters to users)
|
||||||
|
* Expandable to all (server side) changes that might occur on the motion object.
|
||||||
|
*
|
||||||
|
* @param motion blank motion domain object
|
||||||
|
*/
|
||||||
|
private AddViewMotion(motion: Motion): void {
|
||||||
|
const category = this.DS.get(Category, motion.category_id);
|
||||||
|
const submitters = this.DS.getMany(User, motion.submitterIds);
|
||||||
|
const supporters = this.DS.getMany(User, motion.supporters_id);
|
||||||
|
const workflow = this.DS.get(Workflow, motion.workflow_id);
|
||||||
|
let state: WorkflowState = null;
|
||||||
|
if (workflow) {
|
||||||
|
state = workflow.getStateById(motion.state_id);
|
||||||
|
}
|
||||||
|
this.viewMotionStore[motion.id] = new ViewMotion(motion, category, submitters, supporters, workflow, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and updates a 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 viewMotion The View Motion. If not present, a new motion will be created
|
||||||
|
*/
|
||||||
|
public saveMotion(update: any, viewMotion?: ViewMotion): Observable<any> {
|
||||||
|
let updateMotion: Motion;
|
||||||
|
|
||||||
|
if (viewMotion) {
|
||||||
|
// implies that an existing motion was updated
|
||||||
|
updateMotion = viewMotion.motion;
|
||||||
|
} else {
|
||||||
|
// implies that a new motion was created
|
||||||
|
updateMotion = new Motion();
|
||||||
|
}
|
||||||
|
updateMotion.patchValues(update);
|
||||||
|
return this.dataSend.saveModel(updateMotion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the current observable MotionView
|
||||||
|
*/
|
||||||
|
public getViewMotionObservable(id: number): Observable<ViewMotion> {
|
||||||
|
if (!this.viewMotionSubjects[id]) {
|
||||||
|
this.updateViewMotionObservable(id);
|
||||||
|
}
|
||||||
|
return this.viewMotionSubjects[id].asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the Observable of the whole store
|
||||||
|
*/
|
||||||
|
public getViewMotionListObservable(): Observable<ViewMotion[]> {
|
||||||
|
return this.viewMotionListSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deleting a motion.
|
||||||
|
*
|
||||||
|
* Extract the motion out of the motionView and delegate
|
||||||
|
* to {@link DataSendService}
|
||||||
|
* @param viewMotion
|
||||||
|
*/
|
||||||
|
public deleteMotion(viewMotion: ViewMotion): Observable<any> {
|
||||||
|
return this.dataSend.delete(viewMotion.motion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the ViewMotion observable using a ViewMotion corresponding to the id
|
||||||
|
*/
|
||||||
|
private updateViewMotionObservable(id: number): void {
|
||||||
|
if (!this.viewMotionSubjects[id]) {
|
||||||
|
this.viewMotionSubjects[id] = new BehaviorSubject<ViewMotion>(null);
|
||||||
|
}
|
||||||
|
this.viewMotionSubjects[id].next(this.viewMotionStore[id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper function to return the viewMotions as array
|
||||||
|
*/
|
||||||
|
private getViewMotionList(): ViewMotion[] {
|
||||||
|
return Object.values(this.viewMotionStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update the observable of the list
|
||||||
|
*/
|
||||||
|
private updateViewMotionListObservable(): void {
|
||||||
|
this.viewMotionListSubject.next(this.getViewMotionList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers both the observable update routines
|
||||||
|
*/
|
||||||
|
private updateObservables(id: number): void {
|
||||||
|
this.updateViewMotionListObservable();
|
||||||
|
this.updateViewMotionObservable(id);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user