commit
ad6d05639d
28
client/package-lock.json
generated
28
client/package-lock.json
generated
@ -4645,12 +4645,14 @@
|
|||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@ -4665,17 +4667,20 @@
|
|||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@ -4792,7 +4797,8 @@
|
|||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
@ -4804,6 +4810,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
@ -4818,6 +4825,7 @@
|
|||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
@ -4825,12 +4833,14 @@
|
|||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.1",
|
"safe-buffer": "^5.1.1",
|
||||||
"yallist": "^3.0.0"
|
"yallist": "^3.0.0"
|
||||||
@ -4849,6 +4859,7 @@
|
|||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
@ -4929,7 +4940,8 @@
|
|||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
@ -4941,6 +4953,7 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
@ -5062,6 +5075,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
|
@ -7,10 +7,11 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
|||||||
import { AuthGuard } from './services/auth-guard.service';
|
import { AuthGuard } from './services/auth-guard.service';
|
||||||
import { AuthService } from './services/auth.service';
|
import { AuthService } from './services/auth.service';
|
||||||
import { AutoupdateService } from './services/autoupdate.service';
|
import { AutoupdateService } from './services/autoupdate.service';
|
||||||
import { DataStoreService } from './services/dataStore.service';
|
import { DataStoreService } from './services/data-store.service';
|
||||||
import { OperatorService } from './services/operator.service';
|
import { OperatorService } from './services/operator.service';
|
||||||
import { WebsocketService } from './services/websocket.service';
|
import { WebsocketService } from './services/websocket.service';
|
||||||
import { AddHeaderInterceptor } from './http-interceptor';
|
import { AddHeaderInterceptor } from './http-interceptor';
|
||||||
|
import { DataSendService } from './services/data-send.service';
|
||||||
|
|
||||||
/** Global Core Module. Contains all global (singleton) services
|
/** Global Core Module. Contains all global (singleton) services
|
||||||
*
|
*
|
||||||
@ -23,6 +24,7 @@ import { AddHeaderInterceptor } from './http-interceptor';
|
|||||||
AuthService,
|
AuthService,
|
||||||
AutoupdateService,
|
AutoupdateService,
|
||||||
DataStoreService,
|
DataStoreService,
|
||||||
|
DataSendService,
|
||||||
OperatorService,
|
OperatorService,
|
||||||
WebsocketService,
|
WebsocketService,
|
||||||
{
|
{
|
||||||
|
@ -68,7 +68,13 @@ export class AutoupdateService extends OpenSlidesComponent {
|
|||||||
storeResponse(socketResponse): void {
|
storeResponse(socketResponse): void {
|
||||||
socketResponse.forEach(jsonObj => {
|
socketResponse.forEach(jsonObj => {
|
||||||
const targetClass = this.getClassFromCollectionString(jsonObj.collection);
|
const targetClass = this.getClassFromCollectionString(jsonObj.collection);
|
||||||
this.DS.add(new targetClass().deserialize(jsonObj.data));
|
if (jsonObj.action === 'deleted') {
|
||||||
|
console.log('storeResponse detect delete');
|
||||||
|
|
||||||
|
this.DS.remove(jsonObj.collection, jsonObj.id);
|
||||||
|
} else {
|
||||||
|
this.DS.add(new targetClass().deserialize(jsonObj.data));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
15
client/src/app/core/services/data-send.service.spec.ts
Normal file
15
client/src/app/core/services/data-send.service.spec.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DataSendService } from './data-send.service';
|
||||||
|
|
||||||
|
describe('DataSendService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [DataSendService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([DataSendService], (service: DataSendService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
74
client/src/app/core/services/data-send.service.ts
Normal file
74
client/src/app/core/services/data-send.service.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { BaseModel } from '../../shared/models/base.model';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send data back to server
|
||||||
|
*
|
||||||
|
* Contrast to dataStore service
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class DataSendService {
|
||||||
|
/**
|
||||||
|
* Construct a DataSendService
|
||||||
|
*
|
||||||
|
* @param http The HTTP Client
|
||||||
|
*/
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save motion in the server
|
||||||
|
*
|
||||||
|
* @return Observable from
|
||||||
|
*/
|
||||||
|
saveModel(model: BaseModel): Observable<BaseModel> {
|
||||||
|
if (!model.id) {
|
||||||
|
return this.http.post<BaseModel>('rest/' + model.collectionString + '/', model).pipe(
|
||||||
|
tap(
|
||||||
|
response => {
|
||||||
|
// TODO: Message, Notify, Etc
|
||||||
|
console.log('New Model added. Response : ', response);
|
||||||
|
},
|
||||||
|
error => console.log('error. ', error)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return this.http.put<BaseModel>('rest/' + model.collectionString + '/' + model.id, model).pipe(
|
||||||
|
tap(
|
||||||
|
response => {
|
||||||
|
console.log('Update model. Response : ', response);
|
||||||
|
},
|
||||||
|
error => console.log('error. ', error)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given model on the server
|
||||||
|
*
|
||||||
|
* @param model the BaseModel that shall be removed
|
||||||
|
* @return Observable of BaseModel
|
||||||
|
*
|
||||||
|
* TODO Not tested
|
||||||
|
*/
|
||||||
|
delete(model: BaseModel): Observable<BaseModel> {
|
||||||
|
if (model.id) {
|
||||||
|
return this.http.delete<BaseModel>('rest/' + model.collectionString + '/' + model.id).pipe(
|
||||||
|
tap(
|
||||||
|
response => {
|
||||||
|
// TODO: Message, Notify, Etc
|
||||||
|
console.log('the response: ', response);
|
||||||
|
},
|
||||||
|
error => console.error('error during delete: ', error)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error('No model ID to delete');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { TestBed, inject } from '@angular/core/testing';
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
import { DataStoreService } from './dataStore.service';
|
import { DataStoreService } from './data-store.service';
|
||||||
|
|
||||||
describe('DS', () => {
|
describe('DS', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
@ -1,7 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
import { Observable, BehaviorSubject } from 'rxjs';
|
||||||
import { Observable, of, BehaviorSubject } from 'rxjs';
|
|
||||||
import { tap, map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { ImproperlyConfiguredError } from 'app/core/exceptions';
|
import { ImproperlyConfiguredError } from 'app/core/exceptions';
|
||||||
import { BaseModel, ModelId } from 'app/shared/models/base.model';
|
import { BaseModel, ModelId } from 'app/shared/models/base.model';
|
||||||
@ -29,6 +27,12 @@ interface Storage {
|
|||||||
* Use this.DS in an OpenSlides Component to Access the store.
|
* Use this.DS in an OpenSlides Component to Access the store.
|
||||||
* Used by a lot of components, classes and services.
|
* Used by a lot of components, classes and services.
|
||||||
* Changes can be observed
|
* Changes can be observed
|
||||||
|
*
|
||||||
|
* FIXME: The injector does not init the HttpClient Service.
|
||||||
|
* Either remove it from DataStore and make an own Service
|
||||||
|
* fix it somehow
|
||||||
|
* or just do-not let the OpenSlidesComponent inject DataStore to it's
|
||||||
|
* children.
|
||||||
*/
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -49,7 +53,7 @@ export class DataStoreService {
|
|||||||
* Empty constructor for dataStore
|
* Empty constructor for dataStore
|
||||||
* @param http use HttpClient to send models back to the server
|
* @param http use HttpClient to send models back to the server
|
||||||
*/
|
*/
|
||||||
constructor(private http: HttpClient) {}
|
constructor() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read one, multiple or all ID's from dataStore
|
* Read one, multiple or all ID's from dataStore
|
||||||
@ -145,54 +149,25 @@ export class DataStoreService {
|
|||||||
* @param ...ids An or multiple IDs or a list of IDs of BaseModels. use spread operator ("...") for arrays
|
* @param ...ids An or multiple IDs or a list of IDs of BaseModels. use spread operator ("...") for arrays
|
||||||
* @example this.DS.remove(User, myUser.id, 3, 4)
|
* @example this.DS.remove(User, myUser.id, 3, 4)
|
||||||
*/
|
*/
|
||||||
remove(Type, ...ids: ModelId[]): void {
|
remove(collectionType, ...ids: ModelId[]): void {
|
||||||
|
console.log('remove from DS: collection', collectionType);
|
||||||
|
console.log('remove from DS: collection', ids);
|
||||||
|
|
||||||
|
let collectionString: string;
|
||||||
|
if (typeof collectionType === 'string') {
|
||||||
|
collectionString = collectionType;
|
||||||
|
} else {
|
||||||
|
const tempObject = new collectionType();
|
||||||
|
collectionString = tempObject.collectionString;
|
||||||
|
}
|
||||||
|
|
||||||
ids.forEach(id => {
|
ids.forEach(id => {
|
||||||
const tempObject = new Type();
|
if (DataStoreService.store[collectionString]) {
|
||||||
if (DataStoreService.store[tempObject.collectionString]) {
|
delete DataStoreService.store[collectionString][id];
|
||||||
delete DataStoreService.store[tempObject.collectionString][id];
|
|
||||||
console.log(`did remove "${id}" from Datastore "${tempObject.collectionString}"`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the given model on the server
|
|
||||||
* @param model the BaseModel that shall be removed
|
|
||||||
* @return Observable of BaseModel
|
|
||||||
*/
|
|
||||||
save(model: BaseModel): Observable<BaseModel> {
|
|
||||||
if (!model.id) {
|
|
||||||
throw new ImproperlyConfiguredError('The model must have an id!');
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO not tested
|
|
||||||
return this.http.post<BaseModel>(model.collectionString + '/', model).pipe(
|
|
||||||
tap(response => {
|
|
||||||
console.log('the response: ', response);
|
|
||||||
this.add(model);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the given model on the server
|
|
||||||
* @param model the BaseModel that shall be removed
|
|
||||||
* @return Observable of BaseModel
|
|
||||||
*/
|
|
||||||
delete(model: BaseModel): Observable<BaseModel> {
|
|
||||||
if (!model.id) {
|
|
||||||
throw new ImproperlyConfiguredError('The model must have an id!');
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO not tested
|
|
||||||
return this.http.post<BaseModel>(model.collectionString + '/', model).pipe(
|
|
||||||
tap(response => {
|
|
||||||
console.log('the response: ', response);
|
|
||||||
this.remove(model, model.id);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observe the dataStore for changes.
|
* Observe the dataStore for changes.
|
||||||
* @return an observable behaviorSubject
|
* @return an observable behaviorSubject
|
@ -4,6 +4,7 @@ import { HttpClient } from '@angular/common/http';
|
|||||||
import { tap, catchError, share } from 'rxjs/operators';
|
import { tap, catchError, share } from 'rxjs/operators';
|
||||||
import { OpenSlidesComponent } from 'app/openslides.component';
|
import { OpenSlidesComponent } from 'app/openslides.component';
|
||||||
import { Group } from 'app/shared/models/users/group';
|
import { Group } from 'app/shared/models/users/group';
|
||||||
|
import { User } from '../../shared/models/users/user';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The operator represents the user who is using OpenSlides.
|
* The operator represents the user who is using OpenSlides.
|
||||||
@ -38,6 +39,8 @@ export class OperatorService extends OpenSlidesComponent {
|
|||||||
username: string;
|
username: string;
|
||||||
logged_in: boolean;
|
logged_in: boolean;
|
||||||
|
|
||||||
|
private _user: User;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The subject that can be observed by other instances using observing functions.
|
* The subject that can be observed by other instances using observing functions.
|
||||||
*/
|
*/
|
||||||
@ -65,7 +68,7 @@ export class OperatorService extends OpenSlidesComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// observe the datastore now to avoid race conditions. Ensures to
|
// observe the DataStore now to avoid race conditions. Ensures to
|
||||||
// find the groups in time
|
// find the groups in time
|
||||||
this.observeDataStore();
|
this.observeDataStore();
|
||||||
}
|
}
|
||||||
@ -77,7 +80,7 @@ export class OperatorService extends OpenSlidesComponent {
|
|||||||
return this.http.get<any>('/users/whoami/').pipe(
|
return this.http.get<any>('/users/whoami/').pipe(
|
||||||
tap(whoami => {
|
tap(whoami => {
|
||||||
if (whoami && whoami.user) {
|
if (whoami && whoami.user) {
|
||||||
this.storeUser(whoami.user);
|
this.storeUser(whoami.user as User);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
catchError(this.handleError())
|
catchError(this.handleError())
|
||||||
@ -87,8 +90,11 @@ export class OperatorService extends OpenSlidesComponent {
|
|||||||
/**
|
/**
|
||||||
* Store the user Information in the operator, the localStorage and update the Observable
|
* Store the user Information in the operator, the localStorage and update the Observable
|
||||||
* @param user usually a http response that represents a user.
|
* @param user usually a http response that represents a user.
|
||||||
|
*
|
||||||
|
* Todo: Could be refractored to use the actual User Object.
|
||||||
|
* Operator is older than user, so this is still a traditional JS way
|
||||||
*/
|
*/
|
||||||
public storeUser(user: any): void {
|
public storeUser(user: User): void {
|
||||||
// store in file
|
// store in file
|
||||||
this.about_me = user.about_me;
|
this.about_me = user.about_me;
|
||||||
this.comment = user.comment;
|
this.comment = user.comment;
|
||||||
@ -106,6 +112,7 @@ export class OperatorService extends OpenSlidesComponent {
|
|||||||
this.structure_level = user.structure_level;
|
this.structure_level = user.structure_level;
|
||||||
this.title = user.title;
|
this.title = user.title;
|
||||||
this.username = user.username;
|
this.username = user.username;
|
||||||
|
|
||||||
// also store in localstorrage
|
// also store in localstorrage
|
||||||
this.updateLocalStorage();
|
this.updateLocalStorage();
|
||||||
// update mode to inform observers
|
// update mode to inform observers
|
||||||
@ -187,6 +194,10 @@ export class OperatorService extends OpenSlidesComponent {
|
|||||||
if (newModel instanceof Group) {
|
if (newModel instanceof Group) {
|
||||||
this.addGroup(newModel);
|
this.addGroup(newModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newModel instanceof User && this.id === newModel.id) {
|
||||||
|
this._user = newModel;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,4 +254,11 @@ export class OperatorService extends OpenSlidesComponent {
|
|||||||
this.setObservable(newGroup);
|
this.setObservable(newGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the user that corresponds to operator.
|
||||||
|
*/
|
||||||
|
get user(): User {
|
||||||
|
return this._user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Injector } from '@angular/core';
|
import { Injector } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
|
|
||||||
import { DataStoreService } from 'app/core/services/dataStore.service';
|
import { DataStoreService } from './core/services/data-store.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* injects the {@link DataStoreService} to all its children and provides a generic function to catch errors
|
* injects the {@link DataStoreService} to all its children and provides a generic function to catch errors
|
||||||
|
@ -78,7 +78,6 @@ export class OsPermsDirective extends OpenSlidesComponent {
|
|||||||
private updateView(): void {
|
private updateView(): void {
|
||||||
if (this.checkPermissions()) {
|
if (this.checkPermissions()) {
|
||||||
// will just render the page normally
|
// will just render the page normally
|
||||||
console.log('do show: ', this.template, ' - ', this.viewContainer);
|
|
||||||
this.viewContainer.createEmbeddedView(this.template);
|
this.viewContainer.createEmbeddedView(this.template);
|
||||||
} else {
|
} else {
|
||||||
// will remove the content of the container
|
// will remove the content of the container
|
||||||
|
@ -26,10 +26,10 @@ export class MotionVersion implements Deserializable {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
this.version_number = version_number;
|
this.version_number = version_number;
|
||||||
this.creation_time = creation_time;
|
this.creation_time = creation_time;
|
||||||
this.title = title;
|
this.title = title || '';
|
||||||
this.text = text;
|
this.text = text || '';
|
||||||
this.amendment_paragraphs = amendment_paragraphs;
|
this.amendment_paragraphs = amendment_paragraphs || '';
|
||||||
this.reason = reason;
|
this.reason = reason || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialize(input: any): this {
|
deserialize(input: any): this {
|
||||||
|
@ -6,6 +6,7 @@ import { Config } from '../core/config';
|
|||||||
import { Workflow } from './workflow';
|
import { Workflow } from './workflow';
|
||||||
import { User } from '../users/user';
|
import { User } from '../users/user';
|
||||||
import { Category } from './category';
|
import { Category } from './category';
|
||||||
|
import { WorkflowState } from './workflow-state';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of Motion.
|
* Representation of Motion.
|
||||||
@ -36,6 +37,15 @@ export class Motion extends BaseModel {
|
|||||||
agenda_item_id: number;
|
agenda_item_id: number;
|
||||||
log_messages: MotionLog[];
|
log_messages: MotionLog[];
|
||||||
|
|
||||||
|
// read from config
|
||||||
|
workflow_id: number;
|
||||||
|
// by the config above
|
||||||
|
workflow: Workflow;
|
||||||
|
|
||||||
|
// for request
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
id?: number,
|
id?: number,
|
||||||
identifier?: string,
|
identifier?: string,
|
||||||
@ -60,49 +70,116 @@ export class Motion extends BaseModel {
|
|||||||
super();
|
super();
|
||||||
this._collectionString = 'motions/motion';
|
this._collectionString = 'motions/motion';
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.identifier = identifier;
|
this.identifier = identifier || '';
|
||||||
this.versions = versions;
|
this.versions = versions || [new MotionVersion()];
|
||||||
this.active_version = active_version;
|
this.active_version = active_version;
|
||||||
this.parent_id = parent_id;
|
this.parent_id = parent_id;
|
||||||
this.category_id = category_id;
|
this.category_id = category_id;
|
||||||
this.motion_block_id = motion_block_id;
|
this.motion_block_id = motion_block_id;
|
||||||
this.origin = origin;
|
this.origin = origin || '';
|
||||||
this.submitters = submitters;
|
this.submitters = submitters || [];
|
||||||
this.supporters_id = supporters_id;
|
this.supporters_id = supporters_id;
|
||||||
this.comments = comments;
|
this.comments = comments;
|
||||||
this.state_id = state_id;
|
this.state_id = state_id;
|
||||||
this.state_required_permission_to_see = state_required_permission_to_see;
|
this.state_required_permission_to_see = state_required_permission_to_see || '';
|
||||||
this.recommendation_id = recommendation_id;
|
this.recommendation_id = recommendation_id;
|
||||||
this.tags_id = tags_id;
|
this.tags_id = tags_id;
|
||||||
this.attachments_id = attachments_id;
|
this.attachments_id = attachments_id;
|
||||||
this.polls = polls;
|
this.polls = polls;
|
||||||
this.agenda_item_id = agenda_item_id;
|
this.agenda_item_id = agenda_item_id;
|
||||||
this.log_messages = log_messages;
|
this.log_messages = log_messages || [];
|
||||||
|
|
||||||
|
this.initDataStoreValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update the values of the motion with new values
|
||||||
|
*/
|
||||||
|
patchValues(update: object) {
|
||||||
|
Object.assign(this, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sets the workflow_id and the workflow
|
||||||
|
*/
|
||||||
|
initDataStoreValues() {
|
||||||
|
const motionsWorkflowConfig = this.DS.filter(Config, config => config.key === 'motions_workflow')[0] as Config;
|
||||||
|
if (motionsWorkflowConfig) {
|
||||||
|
this.workflow_id = +motionsWorkflowConfig.value;
|
||||||
|
} else {
|
||||||
|
this.DS.getObservable().subscribe(newConfig => {
|
||||||
|
if (newConfig instanceof Config && newConfig.key === 'motions_workflow') {
|
||||||
|
this.workflow_id = +newConfig.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.workflow = this.DS.get(Workflow, this.workflow_id) as Workflow;
|
||||||
|
if (!this.workflow.id) {
|
||||||
|
this.DS.getObservable().subscribe(newModel => {
|
||||||
|
if (newModel instanceof Workflow && newModel.id === this.workflow_id) {
|
||||||
|
this.workflow = newModel;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** add a new motionSubmitter from user-object */
|
||||||
|
addSubmitter(user: User) {
|
||||||
|
const newSubmitter = new MotionSubmitter(null, user.id);
|
||||||
|
this.submitters.push(newSubmitter);
|
||||||
|
console.log('did addSubmitter. this.submitters: ', this.submitters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns the most current title from versions
|
* returns the most current title from versions
|
||||||
*/
|
*/
|
||||||
get currentTitle() {
|
get currentTitle(): string {
|
||||||
if (this.versions[0]) {
|
if (this.versions && this.versions[0]) {
|
||||||
return this.versions[0].title;
|
return this.versions[0].title;
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set currentTitle(newTitle: string) {
|
||||||
|
if (this.versions[0]) {
|
||||||
|
this.versions[0].title = newTitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns the most current motion text from versions
|
* returns the most current motion text from versions
|
||||||
*/
|
*/
|
||||||
get currentText() {
|
get currentText() {
|
||||||
return this.versions[0].text;
|
if (this.versions) {
|
||||||
|
return this.versions[0].text;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set currentText(newText: string) {
|
||||||
|
this.versions[0].text = newText;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns the most current motion reason text from versions
|
* returns the most current motion reason text from versions
|
||||||
*/
|
*/
|
||||||
get currentReason() {
|
get currentReason() {
|
||||||
return this.versions[0].reason;
|
if (this.versions) {
|
||||||
|
return this.versions[0].reason;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current reason.
|
||||||
|
* TODO: ignores motion versions. Should make a new one.
|
||||||
|
*/
|
||||||
|
set currentReason(newReason: string) {
|
||||||
|
this.versions[0].reason = newReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,76 +187,64 @@ export class Motion extends BaseModel {
|
|||||||
*/
|
*/
|
||||||
get submitterAsUser() {
|
get submitterAsUser() {
|
||||||
const submitterIds = [];
|
const submitterIds = [];
|
||||||
this.submitters.forEach(submitter => {
|
if (this.submitters && this.submitters.length > 0) {
|
||||||
submitterIds.push(submitter.user_id);
|
this.submitters.forEach(submitter => {
|
||||||
});
|
submitterIds.push(submitter.user_id);
|
||||||
const users = this.DS.get(User, ...submitterIds);
|
});
|
||||||
return users;
|
const users = this.DS.get(User, ...submitterIds);
|
||||||
}
|
return users;
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the name of the first submitter
|
|
||||||
*/
|
|
||||||
get submitterName() {
|
|
||||||
const mainSubmitter = this.DS.get(User, this.submitters[0].user_id) as User;
|
|
||||||
if (mainSubmitter) {
|
|
||||||
return mainSubmitter.username;
|
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the category of a motion as object
|
* get the category of a motion as object
|
||||||
*/
|
*/
|
||||||
get category() {
|
get category(): any {
|
||||||
if (this.category_id) {
|
if (this.category_id) {
|
||||||
const motionCategory = this.DS.get(Category, this.category_id);
|
const motionCategory = this.DS.get(Category, this.category_id);
|
||||||
return motionCategory;
|
return motionCategory as Category;
|
||||||
} else {
|
|
||||||
return 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* return the workflow state
|
|
||||||
*
|
|
||||||
* Right now only the default workflow is assumed
|
|
||||||
* TODO: Motion workflow needs to be specific on the server
|
|
||||||
*/
|
|
||||||
get stateName() {
|
|
||||||
//get the default workflow
|
|
||||||
const motionsWorkflowConfig = this.DS.filter(Config, config => config.key === 'motions_workflow')[0] as Config;
|
|
||||||
//make sure this is a number
|
|
||||||
const workflowId = +motionsWorkflowConfig.value;
|
|
||||||
//get the workflow for out motion
|
|
||||||
const selectedWorkflow = this.DS.get(Workflow, workflowId) as Workflow;
|
|
||||||
const stateName = selectedWorkflow.getStateNameById(this.state_id);
|
|
||||||
if (stateName !== 'NULL') {
|
|
||||||
return stateName;
|
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get possibleStates() {
|
/**
|
||||||
return '';
|
* Set the category in the motion
|
||||||
|
*/
|
||||||
|
set category(newCategory: any) {
|
||||||
|
this.category_id = newCategory.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the workflow state
|
||||||
|
*/
|
||||||
|
get state(): any {
|
||||||
|
if (this.state_id && this.workflow && this.workflow.id) {
|
||||||
|
const state = this.workflow.state_by_id(this.state_id);
|
||||||
|
return state;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns possible states for the motion
|
||||||
|
*/
|
||||||
|
get possible_states(): WorkflowState[] {
|
||||||
|
return this.workflow.states;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the recommendation.
|
* Returns the name of the recommendation.
|
||||||
*
|
*
|
||||||
* Right now only the default workflow is assumed
|
|
||||||
* TODO: Motion workflow needs to be specific on the server
|
* TODO: Motion workflow needs to be specific on the server
|
||||||
*/
|
*/
|
||||||
get recommendation() {
|
get recommendation(): any {
|
||||||
//get the default workflow
|
if (this.recommendation_id && this.workflow && this.workflow.id) {
|
||||||
const motionsWorkflowConfig = this.DS.filter(Config, config => config.key === 'motions_workflow')[0] as Config;
|
const state = this.workflow.state_by_id(this.recommendation_id);
|
||||||
const workflowId = +motionsWorkflowConfig.value;
|
return state;
|
||||||
const selectedWorkflow = this.DS.get(Workflow, workflowId) as Workflow;
|
|
||||||
const stateName = selectedWorkflow.getStateNameById(this.recommendation_id);
|
|
||||||
if (stateName !== 'NULL') {
|
|
||||||
return stateName;
|
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -193,8 +258,13 @@ export class Motion extends BaseModel {
|
|||||||
Config,
|
Config,
|
||||||
config => config.key === 'motions_recommendations_by'
|
config => config.key === 'motions_recommendations_by'
|
||||||
)[0] as Config;
|
)[0] as Config;
|
||||||
const recomByString = motionsRecommendationsByConfig.value;
|
|
||||||
return recomByString;
|
if (motionsRecommendationsByConfig) {
|
||||||
|
const recomByString = motionsRecommendationsByConfig.value;
|
||||||
|
return recomByString;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialize(input: any): this {
|
deserialize(input: any): this {
|
||||||
|
@ -83,4 +83,8 @@ export class WorkflowState implements Deserializable {
|
|||||||
Object.assign(this, input);
|
Object.assign(this, input);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public toString = (): string => {
|
||||||
|
return this.name;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,14 @@ export class Workflow extends BaseModel {
|
|||||||
this.first_state = first_state;
|
this.first_state = first_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
getStateNameById(id: number): string {
|
state_by_id(id: number): WorkflowState {
|
||||||
let stateName = 'NULL';
|
let targetState;
|
||||||
this.states.forEach(state => {
|
this.states.forEach(state => {
|
||||||
if (id === state.id) {
|
if (id === state.id) {
|
||||||
stateName = state.name;
|
targetState = state;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return stateName;
|
return targetState as WorkflowState;
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialize(input: any): this {
|
deserialize(input: any): this {
|
||||||
|
@ -64,4 +64,39 @@ export class User extends BaseModel {
|
|||||||
getGroups(): BaseModel | BaseModel[] {
|
getGroups(): BaseModel | BaseModel[] {
|
||||||
return this.DS.get('users/group', ...this.groups_id);
|
return this.DS.get('users/group', ...this.groups_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO get full_name
|
||||||
|
|
||||||
|
// TODO read config values for "users_sort_by"
|
||||||
|
get short_name(): string {
|
||||||
|
const title = this.title.trim();
|
||||||
|
const firstName = this.first_name.trim();
|
||||||
|
const lastName = this.last_name.trim();
|
||||||
|
let shortName = '';
|
||||||
|
|
||||||
|
// TODO need DS adjustment first first
|
||||||
|
// if (this.DS.getConfig('users_sort_by').value === 'last_name') {
|
||||||
|
// if (lastName && firstName) {
|
||||||
|
// shortName += `${lastName}, ${firstName}`;
|
||||||
|
// } else {
|
||||||
|
// shortName += lastName || firstName;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
shortName += `${firstName} ${lastName}`;
|
||||||
|
|
||||||
|
if (shortName.trim() === '') {
|
||||||
|
shortName = this.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
shortName = `${title} ${shortName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shortName.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString = (): string => {
|
||||||
|
return this.short_name;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ import { MatDialogModule } from '@angular/material/dialog';
|
|||||||
import { MatListModule } from '@angular/material/list';
|
import { MatListModule } from '@angular/material/list';
|
||||||
import { MatExpansionModule } from '@angular/material/expansion';
|
import { MatExpansionModule } from '@angular/material/expansion';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
|
||||||
// FontAwesome modules
|
// FontAwesome modules
|
||||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||||
@ -48,6 +50,8 @@ library.add(fas);
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatSelectModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatCheckboxModule,
|
MatCheckboxModule,
|
||||||
@ -68,6 +72,8 @@ library.add(fas);
|
|||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatSelectModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatCheckboxModule,
|
MatCheckboxModule,
|
||||||
|
@ -1,90 +1,148 @@
|
|||||||
<mat-toolbar color='primary'>
|
<mat-toolbar color='primary'>
|
||||||
|
|
||||||
<button class='generic-mini-button on-transition-fade' mat-mini-fab>
|
<button (click)='editMotionButton()' [ngClass]="{'save-button': editMotion}" class='generic-mini-button on-transition-fade'
|
||||||
<fa-icon icon='pen'></fa-icon>
|
mat-mini-fab>
|
||||||
|
<fa-icon *ngIf="!editMotion" icon='pen'></fa-icon>
|
||||||
|
<fa-icon *ngIf="editMotion" icon='check'></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- motion.identifier -->
|
|
||||||
<!-- motion.versions[0].title -->
|
|
||||||
<div class='motion-title on-transition-fade'>
|
<div class='motion-title on-transition-fade'>
|
||||||
<span translate>Motion</span> {{motion.identifier}}: {{motion.currentTitle}}
|
<span *ngIf="newMotion">New </span>
|
||||||
|
<span translate>Motion</span>
|
||||||
|
<span *ngIf="motion && !editMotion"> {{motion.identifier}}</span>
|
||||||
|
<span *ngIf="editMotion && !newMotion"> {{metaInfoForm.get('identifier').value}}</span>
|
||||||
|
<span>:</span>
|
||||||
|
<span *ngIf="motion && !editMotion"> {{motion.currentTitle}}</span>
|
||||||
|
<span *ngIf="editMotion"> {{contentForm.get('currentTitle').value}}</span>
|
||||||
<br>
|
<br>
|
||||||
<div class='motion-submitter'>
|
<div *ngIf="motion" class='motion-submitter'>
|
||||||
<span translate>by</span> {{motion.submitterName}}
|
<span translate>by</span> {{motion.submitterAsUser}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span class='spacer'></span>
|
||||||
|
|
||||||
|
<button class='on-transition-fade' mat-icon-button [matMenuTriggerFor]="motionExtraMenu">
|
||||||
|
<fa-icon icon='ellipsis-v'></fa-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #motionExtraMenu="matMenu">
|
||||||
|
<!-- TODO the functions for the buttons -->
|
||||||
|
<button mat-menu-item translate>Export As...</button>
|
||||||
|
<button mat-menu-item translate>Project</button>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<button mat-menu-item class='deleteMotionButton' (click)='deleteMotionButton()' translate>DeleteMotion</button>
|
||||||
|
</mat-menu>
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
|
|
||||||
<mat-accordion multi='true' class='on-transition-fade'>
|
<mat-accordion multi='true' class='on-transition-fade'>
|
||||||
<!-- <mat-expansion-panel [expanded]='true' class='meta-info-panel'> -->
|
|
||||||
<mat-expansion-panel class='meta-info-panel'>
|
<!-- MetaInfo -->
|
||||||
|
<!-- <mat-expansion-panel #metaInfoPanel [expanded]='true' class='meta-info-panel'> -->
|
||||||
|
<mat-expansion-panel #metaInfoPanel [expanded]='this.editMotion && this.newMotion' class='meta-info-panel'>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title>
|
<mat-panel-title>
|
||||||
<fa-icon icon='info-circle' [fixedWidth]="true"></fa-icon>
|
<fa-icon icon='info-circle' [fixedWidth]="true"></fa-icon>
|
||||||
<span translate>Meta information</span>
|
<span translate>Meta information</span>
|
||||||
</mat-panel-title>
|
</mat-panel-title>
|
||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
<div class='meta-info-panel-body'>
|
|
||||||
|
|
||||||
<div *ngIf="motion.submitterName">
|
<!-- Meta info -->
|
||||||
<h3 translate>Submitters</h3>
|
<form [formGroup]='metaInfoForm' class='expansion-panel-custom-body' (ngSubmit)='saveMotion()'>
|
||||||
{{motion.submitterName}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf='motion.supporters_id && motion.supporters_id.length > 0'>
|
<!-- Identifier -->
|
||||||
<h3>translate>Supporters</h3>
|
<div *ngIf="editMotion && !newMotion">
|
||||||
<!-- print all motion supporters -->
|
<!-- <div *ngIf="editMotion"> -->
|
||||||
</div>
|
<div *ngIf='!editMotion'>
|
||||||
|
<h3 translate>Identifier</h3>
|
||||||
<div *ngIf='motion.state_id'>
|
{{motion.identifier}}
|
||||||
<h3>
|
</div>
|
||||||
<a [matMenuTriggerFor]="stateMenu">
|
<mat-form-field *ngIf="editMotion">
|
||||||
<span translate>Status</span>
|
<input matInput placeholder='Identifier' formControlName='identifier' [value]='motion.identifier'>
|
||||||
<fa-icon icon='cog'></fa-icon>
|
|
||||||
</a>
|
|
||||||
</h3>
|
|
||||||
{{motion.stateName}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf='motion.recommendation_id'>
|
|
||||||
<h3>
|
|
||||||
<a [matMenuTriggerFor]="stateMenu">
|
|
||||||
<span>{{motion.recomBy}}</span>
|
|
||||||
<fa-icon icon='cog'></fa-icon>
|
|
||||||
</a>
|
|
||||||
</h3>
|
|
||||||
{{motion.recommendation}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="motion.category_id">
|
|
||||||
<h3>
|
|
||||||
<a [matMenuTriggerFor]="categoryMenu">
|
|
||||||
<span translate>Category</span>
|
|
||||||
<fa-icon icon='cog'></fa-icon>
|
|
||||||
</a>
|
|
||||||
</h3>
|
|
||||||
{{motion.category}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="motion.origin">
|
|
||||||
<h3>
|
|
||||||
<span translate>Origin</span>
|
|
||||||
<fa-icon icon='cog'></fa-icon>
|
|
||||||
</h3>
|
|
||||||
<span>{{motion.origin}}</span>
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<mat-form-field>
|
|
||||||
<input matInput #origin placeholder='Origin' value={{motion.origin}}>
|
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf='motion.polls && motion.polls.length > 0'>
|
<!-- Submitter -->
|
||||||
<h3 translate>Voting</h3>
|
<div *ngIf="motion && motion.submitters || editMotion">
|
||||||
|
<h3 translate>Submitters</h3>
|
||||||
|
{{motion.submitterAsUser}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<!-- Supporter -->
|
||||||
|
<div *ngIf='motion && motion.supporters_id && motion.supporters_id.length > 0 || editMotion'>
|
||||||
|
<h3 translate>Supporters</h3>
|
||||||
|
<!-- print all motion supporters -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- State -->
|
||||||
|
<div *ngIf='motion && motion.state_id || editMotion'>
|
||||||
|
<div *ngIf='!editMotion'>
|
||||||
|
<h3 translate>State</h3>
|
||||||
|
{{motion.state.name}}
|
||||||
|
</div>
|
||||||
|
<mat-form-field *ngIf="editMotion">
|
||||||
|
<mat-select placeholder='State' formControlName='state_id'>
|
||||||
|
<mat-option *ngFor="let state of motion.possible_states" [value]="state.id">{{state}}</mat-option>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<mat-option>
|
||||||
|
<fa-icon icon='exclamation-triangle'></fa-icon>
|
||||||
|
<span translate>Reset State</span>
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recommendation -->
|
||||||
|
<!-- 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='!editMotion'>
|
||||||
|
<h3>{{motion.recomBy}}</h3>
|
||||||
|
{{motion.recommendation.name}}
|
||||||
|
</div>
|
||||||
|
<mat-form-field *ngIf="motion && editMotion">
|
||||||
|
<mat-select placeholder='Recommendation' formControlName='recommendation_id'>
|
||||||
|
<mat-option *ngFor="let state of motion.possible_states" [value]="state.id">{{state}}</mat-option>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<mat-option>
|
||||||
|
<fa-icon icon='exclamation-triangle'></fa-icon>
|
||||||
|
<span translate>Reset recommendation</span>
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Category -->
|
||||||
|
<div *ngIf="motion && motion.category_id || editMotion">
|
||||||
|
<div *ngIf='!editMotion'>
|
||||||
|
<h3 translate> Category</h3>
|
||||||
|
{{motion.category}}
|
||||||
|
</div>
|
||||||
|
<mat-form-field *ngIf="motion && editMotion">
|
||||||
|
<mat-select placeholder='Category' formControlName='category_id'>
|
||||||
|
<mat-option>None</mat-option>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<mat-option *ngFor="let cat of getMotionCategories()" [value]="cat.id">{{cat}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Origin -->
|
||||||
|
<div *ngIf="motion && motion.origin || editMotion">
|
||||||
|
<div *ngIf='!editMotion'>
|
||||||
|
<h3 translate> Origin</h3>
|
||||||
|
{{motion.origin}}
|
||||||
|
</div>
|
||||||
|
<mat-form-field *ngIf="editMotion">
|
||||||
|
<input matInput placeholder='Origin' formControlName='origin' [value]='motion.origin'>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Voting -->
|
||||||
|
<!-- <div *ngIf='motion.polls && motion.polls.length > 0 || editMotion'>
|
||||||
|
<h3 translate>Voting</h3>
|
||||||
|
</div> -->
|
||||||
|
</form>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
<!-- <mat-expansion-panel [expanded]='true'> -->
|
|
||||||
|
<!-- Personal Note -->
|
||||||
<mat-expansion-panel>
|
<mat-expansion-panel>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title>
|
<mat-panel-title>
|
||||||
@ -94,7 +152,9 @@
|
|||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
TEST
|
TEST
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
<mat-expansion-panel [expanded]='true' class='content-panel'>
|
|
||||||
|
<!-- Content -->
|
||||||
|
<mat-expansion-panel #contentPanel [expanded]='true' class='content-panel'>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title>
|
<mat-panel-title>
|
||||||
<fa-icon icon='align-left' [fixedWidth]="true"></fa-icon>
|
<fa-icon icon='align-left' [fixedWidth]="true"></fa-icon>
|
||||||
@ -102,23 +162,39 @@
|
|||||||
</mat-panel-title>
|
</mat-panel-title>
|
||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
<h3 translate>The assembly may decide:</h3>
|
<form [formGroup]='contentForm' class='expansion-panel-custom-body' (ngSubmit)='saveMotion()'>
|
||||||
<div [innerHtml]='motion.currentText'></div>
|
|
||||||
|
|
||||||
<h4 *ngIf='motion.currentReason' translate class='motion-reason-label'>Reason</h4>
|
<!-- Title -->
|
||||||
<div [innerHtml]='motion.currentReason'></div>
|
<div *ngIf="motion && motion.currentTitle || editMotion">
|
||||||
|
<div *ngIf='!editMotion'>
|
||||||
|
<h2>{{motion.currentTitle}}</h2>
|
||||||
|
</div>
|
||||||
|
<mat-form-field *ngIf="editMotion" class='wide-text'>
|
||||||
|
<input matInput placeholder='Title' formControlName='currentTitle' [value]='motion.currentTitle'>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Text -->
|
||||||
|
<!-- TODO: this is a config variable. Read it out -->
|
||||||
|
<h3 translate>The assembly may decide:</h3>
|
||||||
|
<div *ngIf='motion && !editMotion'>
|
||||||
|
<div [innerHtml]='motion.currentText'></div>
|
||||||
|
</div>
|
||||||
|
<mat-form-field *ngIf="motion && editMotion" class='wide-text'>
|
||||||
|
<textarea matInput placeholder='Motion Text' formControlName='currentText' [value]='motion.currentText'></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Reason -->
|
||||||
|
<div *ngIf="motion && motion.currentReason || editMotion">
|
||||||
|
<div *ngIf='!editMotion'>
|
||||||
|
<h4 translate>Reason</h4>
|
||||||
|
<div [innerHtml]='motion.currentReason'></div>
|
||||||
|
</div>
|
||||||
|
<mat-form-field *ngIf="editMotion" class='wide-text'>
|
||||||
|
<textarea matInput placeholder="Reason" formControlName='currentReason' [value]='motion.currentReason'></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
</mat-accordion>
|
</mat-accordion>
|
||||||
|
|
||||||
<!-- dummy values -->
|
|
||||||
<mat-menu #stateMenu="matMenu">
|
|
||||||
<button mat-menu-item translate>Accept</button>
|
|
||||||
<button mat-menu-item translate>Reject</button>
|
|
||||||
<button mat-menu-item translate>Do not decice</button>
|
|
||||||
</mat-menu>
|
|
||||||
|
|
||||||
<mat-menu #categoryMenu="matMenu">
|
|
||||||
<button mat-menu-item translate>B - Bildung</button>
|
|
||||||
<button mat-menu-item translate>T - Test</button>
|
|
||||||
</mat-menu>
|
|
@ -2,6 +2,14 @@ span {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
background-color: rgb(77, 243, 86);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteMotionButton {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
.motion-title {
|
.motion-title {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
line-height: 100%;
|
line-height: 100%;
|
||||||
@ -52,17 +60,33 @@ mat-panel-title {
|
|||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.meta-info-panel-body {
|
.wide-text {
|
||||||
|
width: 95%;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-expansion-panel {
|
||||||
|
.expansion-panel-custom-body {
|
||||||
padding-left: 55px;
|
padding-left: 55px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-panel {
|
.content-panel {
|
||||||
|
h2 {
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 120%;
|
||||||
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
|
display: block;
|
||||||
font-weight: initial;
|
font-weight: initial;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
|
@ -1,45 +1,187 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { OpenSlidesComponent } from '../../../openslides.component';
|
|
||||||
import { BaseComponent } from '../../../base.component';
|
import { BaseComponent } from '../../../base.component';
|
||||||
import { Motion } from '../../../shared/models/motions/motion';
|
import { Motion } from '../../../shared/models/motions/motion';
|
||||||
|
import { Category } from '../../../shared/models/motions/category';
|
||||||
|
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||||
|
import { MatExpansionPanel } from '@angular/material';
|
||||||
|
import { DataSendService } from '../../../core/services/data-send.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for the motion detail view
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-motion-detail',
|
selector: 'app-motion-detail',
|
||||||
templateUrl: './motion-detail.component.html',
|
templateUrl: './motion-detail.component.html',
|
||||||
styleUrls: ['./motion-detail.component.scss']
|
styleUrls: ['./motion-detail.component.scss']
|
||||||
})
|
})
|
||||||
export class MotionDetailComponent extends BaseComponent implements OnInit {
|
export class MotionDetailComponent extends BaseComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* MatExpansionPanel for the meta info
|
||||||
|
*/
|
||||||
|
@ViewChild('metaInfoPanel') metaInfoPanel: MatExpansionPanel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MatExpansionPanel for the content panel
|
||||||
|
*/
|
||||||
|
@ViewChild('contentPanel') contentPanel: MatExpansionPanel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Target motion. Might be new or old
|
||||||
|
*/
|
||||||
motion: Motion;
|
motion: Motion;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute) {
|
/**
|
||||||
|
* Motions meta-info
|
||||||
|
*/
|
||||||
|
metaInfoForm: FormGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Motion content. Can be a new version
|
||||||
|
*/
|
||||||
|
contentForm: FormGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the motion is edited
|
||||||
|
*/
|
||||||
|
editMotion = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the motion is new
|
||||||
|
*/
|
||||||
|
newMotion = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constuct the detail view.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param route determine if this is a new or an existing motion
|
||||||
|
* @param formBuilder For reactive forms. Form Group and Form Control
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private router: Router,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private dataSend: DataSendService
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.route.params.subscribe(params => {
|
this.createForm();
|
||||||
console.log(params.id);
|
|
||||||
|
|
||||||
// has the motion of the DataStore was initialized before.
|
if (route.snapshot.url[0].path === 'new') {
|
||||||
// Otherwise we need to observe DS
|
this.newMotion = true;
|
||||||
this.motion = this.DS.get(Motion, params.id) as Motion;
|
this.editMotion = true;
|
||||||
|
this.motion = new Motion();
|
||||||
|
} else {
|
||||||
|
// load existing motion
|
||||||
|
this.route.params.subscribe(params => {
|
||||||
|
// has the motion of the DataStore was initialized before.
|
||||||
|
this.motion = this.DS.get(Motion, params.id) as Motion;
|
||||||
|
|
||||||
// Observe motion to get the motion in the parameter and also get the changes
|
// Observe motion to get the motion in the parameter and also get the changes
|
||||||
this.DS.getObservable().subscribe(newModel => {
|
this.DS.getObservable().subscribe(newModel => {
|
||||||
if (newModel instanceof Motion) {
|
if (newModel instanceof Motion) {
|
||||||
if (newModel.id === +params.id) {
|
if (newModel.id === +params.id) {
|
||||||
this.motion = newModel as Motion;
|
this.motion = newModel as Motion;
|
||||||
console.log('this.motion = ', this.motion);
|
}
|
||||||
// console.log('motion state name: ', this.motion.stateName);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async load the values of the motion in the Form.
|
||||||
|
*/
|
||||||
|
patchForm() {
|
||||||
|
this.metaInfoForm.patchValue({
|
||||||
|
category_id: this.motion.category.id,
|
||||||
|
state_id: this.motion.state.id,
|
||||||
|
recommendation_id: this.motion.recommendation.id,
|
||||||
|
identifier: this.motion.identifier,
|
||||||
|
origin: this.motion.origin
|
||||||
|
});
|
||||||
|
this.contentForm.patchValue({
|
||||||
|
currentTitle: this.motion.currentTitle,
|
||||||
|
currentText: this.motion.currentText,
|
||||||
|
currentReason: this.motion.currentReason
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
/**
|
||||||
console.log('(init)the motion: ', this.motion);
|
* Creates the forms for the Motion and the MotionVersion
|
||||||
console.log('motion state name: ', this.motion.stateName);
|
*
|
||||||
|
* TODO: Build a custom form validator
|
||||||
|
*/
|
||||||
|
createForm() {
|
||||||
|
this.metaInfoForm = this.formBuilder.group({
|
||||||
|
identifier: [''],
|
||||||
|
category_id: [''],
|
||||||
|
state_id: [''],
|
||||||
|
recommendation_id: [''],
|
||||||
|
origin: ['']
|
||||||
|
});
|
||||||
|
this.contentForm = this.formBuilder.group({
|
||||||
|
currentTitle: [''],
|
||||||
|
currentText: [''],
|
||||||
|
currentReason: ['']
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadSingleMotionButton() {
|
/**
|
||||||
console.log('Download this motion');
|
* Save a motion. Calls the "patchValues" function in the MotionObject
|
||||||
|
*
|
||||||
|
* http:post the motion to the server.
|
||||||
|
* The AutoUpdate-Service should see a change once it arrives and show it
|
||||||
|
* in the list view automatically
|
||||||
|
*/
|
||||||
|
saveMotion() {
|
||||||
|
const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value };
|
||||||
|
this.motion.patchValues(newMotionValues);
|
||||||
|
|
||||||
|
// TODO: This is DRAFT. Reads out Motion version directly. Potentially insecure.
|
||||||
|
this.motion.title = this.motion.currentTitle;
|
||||||
|
this.motion.text = this.motion.currentText;
|
||||||
|
|
||||||
|
this.dataSend.saveModel(this.motion).subscribe(answer => {
|
||||||
|
if (answer && answer.id && this.newMotion) {
|
||||||
|
this.router.navigate(['./motions/' + answer.id]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return all Categories.
|
||||||
|
*/
|
||||||
|
getMotionCategories(): Category[] {
|
||||||
|
const categories = this.DS.get(Category);
|
||||||
|
return categories as Category[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click on the edit button (pen-symbol)
|
||||||
|
*/
|
||||||
|
editMotionButton() {
|
||||||
|
this.editMotion ? (this.editMotion = false) : (this.editMotion = true);
|
||||||
|
if (this.editMotion) {
|
||||||
|
this.patchForm();
|
||||||
|
this.metaInfoPanel.open();
|
||||||
|
this.contentPanel.open();
|
||||||
|
} else {
|
||||||
|
this.saveMotion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger to delete the motion
|
||||||
|
*/
|
||||||
|
deleteMotionButton() {
|
||||||
|
this.dataSend.delete(this.motion).subscribe(answer => {
|
||||||
|
this.router.navigate(['./motions/']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init. Does nothing here.
|
||||||
|
*/
|
||||||
|
ngOnInit() {}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<mat-toolbar color='primary'>
|
<mat-toolbar color='primary'>
|
||||||
|
|
||||||
<button class='generic-plus-button on-transition-fade' mat-fab>
|
<button class='generic-plus-button on-transition-fade' routerLink='new' mat-fab>
|
||||||
<fa-icon icon='plus'></fa-icon>
|
<fa-icon icon='plus'></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -43,7 +43,7 @@
|
|||||||
<br>
|
<br>
|
||||||
<span class='motion-list-from'>
|
<span class='motion-list-from'>
|
||||||
<span translate>by</span>
|
<span translate>by</span>
|
||||||
{{motion.submitterAsUser.username}}
|
{{motion.submitterAsUser}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
@ -53,8 +53,8 @@
|
|||||||
<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">
|
||||||
<div *ngIf='motion.stateName !== "submitted"' class='innerTable'>
|
<div *ngIf='motion.state && motion.state.name !== "submitted"' class='innerTable'>
|
||||||
<fa-icon icon={{getStateIcon(motion.stateName)}}></fa-icon>
|
<fa-icon icon={{getStateIcon(motion.state)}}></fa-icon>
|
||||||
</div>
|
</div>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -7,6 +7,9 @@ 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 { Workflow } from '../../../shared/models/motions/workflow';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays all the motions in a Table using DataSource.
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-motion-list',
|
selector: 'app-motion-list',
|
||||||
templateUrl: './motion-list.component.html',
|
templateUrl: './motion-list.component.html',
|
||||||
@ -28,14 +31,26 @@ export class MotionListComponent extends BaseComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
dataSource: MatTableDataSource<Motion>;
|
dataSource: MatTableDataSource<Motion>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The table itself.
|
||||||
|
*/
|
||||||
@ViewChild(MatTable) table: MatTable<Motion>;
|
@ViewChild(MatTable) table: MatTable<Motion>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination. Might be turned off to all motions at once.
|
||||||
|
*/
|
||||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the Table
|
||||||
|
*/
|
||||||
@ViewChild(MatSort) sort: MatSort;
|
@ViewChild(MatSort) sort: MatSort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use for minimal width
|
* Use for minimal width
|
||||||
*/
|
*/
|
||||||
columnsToDisplayMinWidth = ['identifier', 'title', 'state'];
|
columnsToDisplayMinWidth = ['identifier', 'title', 'state'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use for maximal width
|
* Use for maximal width
|
||||||
*/
|
*/
|
||||||
@ -43,13 +58,16 @@ export class MotionListComponent extends BaseComponent implements OnInit {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor implements title and translation Module.
|
* Constructor implements title and translation Module.
|
||||||
* @param titleService
|
*
|
||||||
* @param translate
|
* @param titleService Title
|
||||||
|
* @param translate Translation
|
||||||
|
* @param router Router
|
||||||
|
* @param route Current route
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
public router: Router,
|
protected titleService: Title,
|
||||||
titleService: Title,
|
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
|
private router: Router,
|
||||||
private route: ActivatedRoute
|
private route: ActivatedRoute
|
||||||
) {
|
) {
|
||||||
super(titleService, translate);
|
super(titleService, translate);
|
||||||
@ -70,35 +88,43 @@ export class MotionListComponent extends BaseComponent implements OnInit {
|
|||||||
// The alternative approach is to put the observable as DataSource to the table
|
// The alternative approach is to put the observable as DataSource to the table
|
||||||
this.DS.getObservable().subscribe(newModel => {
|
this.DS.getObservable().subscribe(newModel => {
|
||||||
if (newModel instanceof Motion) {
|
if (newModel instanceof Motion) {
|
||||||
this.motionArray.push(newModel as Motion);
|
this.motionArray = this.DS.get(Motion) as Motion[];
|
||||||
this.dataSource.data = this.motionArray;
|
this.dataSource.data = this.motionArray;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a motion from list. Executed via click.
|
||||||
|
*
|
||||||
|
* @param motion The row the user clicked at
|
||||||
|
*/
|
||||||
selectMotion(motion) {
|
selectMotion(motion) {
|
||||||
console.log('clicked a row, :', motion);
|
|
||||||
|
|
||||||
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 coresponding 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 stateName the name of the state
|
* @param state the name of the state
|
||||||
*/
|
*/
|
||||||
getStateIcon(stateName) {
|
getStateIcon(state) {
|
||||||
|
const stateName = state.name;
|
||||||
if (stateName === 'accepted') {
|
if (stateName === 'accepted') {
|
||||||
return 'thumbs-up';
|
return 'thumbs-up';
|
||||||
} else if (stateName === 'rejected') {
|
} else if (stateName === 'rejected') {
|
||||||
return 'thumbs-down';
|
return 'thumbs-down';
|
||||||
} else if (stateName === 'not decided') {
|
} else if (stateName === 'not decided') {
|
||||||
return 'question';
|
return 'question';
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download all motions As PDF and DocX
|
* Download all motions As PDF and DocX
|
||||||
|
*
|
||||||
|
* TODO: Currently does nothing
|
||||||
*/
|
*/
|
||||||
downloadMotionsButton() {
|
downloadMotionsButton() {
|
||||||
console.log('Download Motions Button');
|
console.log('Download Motions Button');
|
||||||
|
@ -5,7 +5,7 @@ import { MotionDetailComponent } from './motion-detail/motion-detail.component';
|
|||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: MotionListComponent },
|
{ path: '', component: MotionListComponent },
|
||||||
{ path: 'dummy', component: MotionDetailComponent },
|
{ path: 'new', component: MotionDetailComponent },
|
||||||
{ path: ':id', component: MotionDetailComponent }
|
{ path: ':id', component: MotionDetailComponent }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -65,12 +65,12 @@ export class SiteComponent extends BaseComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//get a translation via code: use the translation service
|
// get a translation via code: use the translation service
|
||||||
this.translate.get('Motions').subscribe((res: string) => {
|
// this.translate.get('Motions').subscribe((res: string) => {
|
||||||
console.log('translation of motions in the target language: ' + res);
|
// console.log('translation of motions in the target language: ' + res);
|
||||||
});
|
// });
|
||||||
|
|
||||||
//start autoupdate if the user is logged in:
|
// start autoupdate if the user is logged in:
|
||||||
this.operator.whoAmI().subscribe(resp => {
|
this.operator.whoAmI().subscribe(resp => {
|
||||||
if (resp.user) {
|
if (resp.user) {
|
||||||
this.autoupdateService.startAutoupdate();
|
this.autoupdateService.startAutoupdate();
|
||||||
|
@ -64,7 +64,6 @@ export class StartComponent extends BaseComponent implements OnInit {
|
|||||||
if (welcomeTextConfig) {
|
if (welcomeTextConfig) {
|
||||||
this.welcomeText = welcomeTextConfig.value as string;
|
this.welcomeText = welcomeTextConfig.value as string;
|
||||||
}
|
}
|
||||||
console.log(this.DS.filter(Config, config => config.key === 'general_event_welcome_title'));
|
|
||||||
|
|
||||||
// observe title and text in DS
|
// observe title and text in DS
|
||||||
this.DS.getObservable().subscribe(newModel => {
|
this.DS.getObservable().subscribe(newModel => {
|
||||||
|
Loading…
Reference in New Issue
Block a user