document, restructure, add relations
- models get other models from DataStore (Relations) - documentation using Compodoc - rename and restructure - http-interceptor makes all http-objections obsolete - created 'Deserializable model' interface for better mapping of JSON objects - Supports multiple nested objects - No foreign dependancies, no magic - Simple yet efficient deserialize function - arrays of nested objects - created more classes for better OOP AOP
This commit is contained in:
parent
30ac9c8e36
commit
6b09427565
1
.gitignore
vendored
1
.gitignore
vendored
@ -43,6 +43,7 @@ openslides_*
|
|||||||
client/dist
|
client/dist
|
||||||
client/tmp
|
client/tmp
|
||||||
client/out-tsc
|
client/out-tsc
|
||||||
|
client/documentation
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
client/node_modules
|
client/node_modules
|
||||||
|
@ -1,12 +1,46 @@
|
|||||||
# OpenSlides 3 Client
|
# OpenSlides 3 Client
|
||||||
|
|
||||||
Prototype application for OpenSlides 3.0 (Client)
|
Prototype application for OpenSlides 3.0 (Client).
|
||||||
|
Currently under constant heavy maintenance.
|
||||||
|
|
||||||
## Development server
|
## Development Info
|
||||||
|
|
||||||
Run `npm start` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
As an Angular project, Angular CLI is highly recommended to create components and services.
|
||||||
|
See https://angular.io/guide/quickstart for details.
|
||||||
|
|
||||||
A running OpenSlides (2.2) instance is expected on port 8000.
|
### Contribution Info
|
||||||
|
|
||||||
|
Please respect the code-style defined in `.editorconf` and `.pretierrc`.
|
||||||
|
|
||||||
|
Code alignment should be automatically corrected by the pre-commit hooks.
|
||||||
|
Adjust your editor to the `.editorconfig` to avoid surprises.
|
||||||
|
See https://editorconfig.org/ for details.
|
||||||
|
|
||||||
|
### Pre-Commit Hooks
|
||||||
|
|
||||||
|
Before commiting, new code will automatically be aligned to the definitions set in the
|
||||||
|
`.prettierrc`.
|
||||||
|
Furthermore, new code has to pass linting.
|
||||||
|
|
||||||
|
Our pre-commit hooks are:
|
||||||
|
`pretty-quick --staged` and `lint`
|
||||||
|
See `package.json` for details.
|
||||||
|
|
||||||
|
### Documentation Info
|
||||||
|
|
||||||
|
The documentation can be generated by running `npm run compodoc`.
|
||||||
|
A new web server will be started on http://localhost:8080
|
||||||
|
Once running, the documentation will be updated automatically.
|
||||||
|
|
||||||
|
Please document new code using JSDoc tags.
|
||||||
|
See https://compodoc.app/guides/jsdoc-tags.html for details.
|
||||||
|
|
||||||
|
### Development server
|
||||||
|
|
||||||
|
Run `npm start` for a development server. Navigate to `http://localhost:4200/`.
|
||||||
|
The app will automatically reload if you change any of the source files.
|
||||||
|
|
||||||
|
A running OpenSlides (2.2 or higher) instance is expected on port 8000.
|
||||||
|
|
||||||
Start OpenSlides as usual using
|
Start OpenSlides as usual using
|
||||||
`python manage.py start --no-browser --host 0.0.0.0`
|
`python manage.py start --no-browser --host 0.0.0.0`
|
||||||
|
949
client/package-lock.json
generated
949
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -3,11 +3,12 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve --proxy-config proxy.conf.json",
|
"start": "ng serve --proxy-config proxy.conf.json --host=0.0.0.0",
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
|
"compodoc": "./node_modules/.bin/compodoc -p src/tsconfig.app.json -s -w",
|
||||||
"extract": "ngx-translate-extract -i ./src -o ./src/assets/i18n/{en,de,fr}.json --clean --sort --format-indentation ' ' --format namespaced-json",
|
"extract": "ngx-translate-extract -i ./src -o ./src/assets/i18n/{en,de,fr}.json --clean --sort --format-indentation ' ' --format namespaced-json",
|
||||||
"format:fix": "pretty-quick --staged",
|
"format:fix": "pretty-quick --staged",
|
||||||
"precommit": "run-s format:fix lint"
|
"precommit": "run-s format:fix lint"
|
||||||
@ -40,6 +41,7 @@
|
|||||||
"@angular/compiler-cli": "^6.0.6",
|
"@angular/compiler-cli": "^6.0.6",
|
||||||
"@angular/language-service": "^6.0.6",
|
"@angular/language-service": "^6.0.6",
|
||||||
"@biesbjerg/ngx-translate-extract": "^2.3.4",
|
"@biesbjerg/ngx-translate-extract": "^2.3.4",
|
||||||
|
"@compodoc/compodoc": "^1.1.3",
|
||||||
"@types/jasmine": "~2.8.6",
|
"@types/jasmine": "~2.8.6",
|
||||||
"@types/jasminewd2": "~2.0.3",
|
"@types/jasminewd2": "~2.0.3",
|
||||||
"@types/node": "~8.9.4",
|
"@types/node": "~8.9.4",
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { AutoupdateService } from 'app/core/services/autoupdate.service';
|
import { AutoupdateService } from 'app/core/services/autoupdate.service';
|
||||||
// import { AuthService } from 'app/core/services/auth.service';
|
|
||||||
import { OperatorService } from 'app/core/services/operator.service';
|
import { OperatorService } from 'app/core/services/operator.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Angular's global App Component
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.css']
|
styleUrls: ['./app.component.css']
|
||||||
})
|
})
|
||||||
// export class AppComponent implements OnInit {
|
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
|
/**
|
||||||
|
* Initialises the operator, the auto update (and therefore a websocket) feature and the translation unit.
|
||||||
|
* @param operator
|
||||||
|
* @param autoupdate
|
||||||
|
* @param translate
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private operator: OperatorService,
|
private operator: OperatorService,
|
||||||
private autoupdate: AutoupdateService,
|
private autoupdate: AutoupdateService,
|
||||||
|
@ -3,7 +3,7 @@ import { BrowserModule, Title } from '@angular/platform-browser';
|
|||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { HttpClientModule, HttpClient, HttpClientXsrfModule } from '@angular/common/http';
|
import { HttpClientModule, HttpClient, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
|
|
||||||
// MaterialUI modules
|
// MaterialUI modules
|
||||||
import {
|
import {
|
||||||
@ -34,15 +34,26 @@ import { MotionsComponent } from './site/motions/motions.component';
|
|||||||
import { AgendaComponent } from './site/agenda/agenda.component';
|
import { AgendaComponent } from './site/agenda/agenda.component';
|
||||||
import { SiteComponent } from './site/site.component';
|
import { SiteComponent } from './site/site.component';
|
||||||
import { StartComponent } from './site/start/start.component';
|
import { StartComponent } from './site/start/start.component';
|
||||||
import { WebsocketService } from './core/services/websocket.service';
|
import { AddHeaderInterceptor } from './core/http-interceptor';
|
||||||
import { ProjectorContainerComponent } from './projector-container/projector-container.component';
|
import { ProjectorContainerComponent } from './projector-container/projector-container.component';
|
||||||
import { AlertComponent } from './core/directives/alert/alert.component';
|
|
||||||
|
|
||||||
//translation module. TODO: Potetially a SharedModule and own files
|
// Root Services
|
||||||
|
import { AuthGuard } from './core/services/auth-guard.service';
|
||||||
|
import { AuthService } from './core/services/auth.service';
|
||||||
|
import { AutoupdateService } from './core/services/autoupdate.service';
|
||||||
|
import { DataStoreService } from './core/services/dataStore.service';
|
||||||
|
import { OperatorService } from './core/services/operator.service';
|
||||||
|
import { WebsocketService } from './core/services/websocket.service';
|
||||||
|
|
||||||
|
// translation module.
|
||||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||||
import { PruningTranslationLoader } from './core/pruning-loader';
|
import { PruningTranslationLoader } from './core/pruning-loader';
|
||||||
import { OsPermsDirective } from './core/directives/os-perms.directive';
|
import { OsPermsDirective } from './core/directives/os-perms.directive';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For the translation module. Loads a Custom 'translation loader' and provides it as loader.
|
||||||
|
* @param http Just the HttpClient to load stuff
|
||||||
|
*/
|
||||||
export function HttpLoaderFactory(http: HttpClient) {
|
export function HttpLoaderFactory(http: HttpClient) {
|
||||||
return new PruningTranslationLoader(http);
|
return new PruningTranslationLoader(http);
|
||||||
}
|
}
|
||||||
@ -61,7 +72,6 @@ library.add(fas);
|
|||||||
SiteComponent,
|
SiteComponent,
|
||||||
StartComponent,
|
StartComponent,
|
||||||
ProjectorContainerComponent,
|
ProjectorContainerComponent,
|
||||||
AlertComponent,
|
|
||||||
OsPermsDirective
|
OsPermsDirective
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
@ -94,7 +104,20 @@ library.add(fas);
|
|||||||
}),
|
}),
|
||||||
AppRoutingModule
|
AppRoutingModule
|
||||||
],
|
],
|
||||||
providers: [Title, WebsocketService],
|
providers: [
|
||||||
|
Title,
|
||||||
|
AuthGuard,
|
||||||
|
AuthService,
|
||||||
|
AutoupdateService,
|
||||||
|
DataStoreService,
|
||||||
|
OperatorService,
|
||||||
|
WebsocketService,
|
||||||
|
{
|
||||||
|
provide: HTTP_INTERCEPTORS,
|
||||||
|
useClass: AddHeaderInterceptor,
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
@ -1,28 +1,35 @@
|
|||||||
import { Injector } from '@angular/core';
|
import { Injector } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { DataStoreService } from 'app/core/services/DS.service';
|
import { OpenSlidesComponent } from './openslides.component';
|
||||||
|
|
||||||
// provides functions that might be used by a lot of components
|
/**
|
||||||
export abstract class BaseComponent {
|
* Provides functionalities that will be used by most components
|
||||||
protected injector: Injector;
|
* currently able to set the title with the suffix ' - OpenSlides 3'
|
||||||
protected dataStore: DataStoreService;
|
*
|
||||||
|
* A BaseComponent is an OpenSlides Component.
|
||||||
|
* Components in the 'Side'- or 'projector' Folder are BaseComponents
|
||||||
|
*/
|
||||||
|
export abstract class BaseComponent extends OpenSlidesComponent {
|
||||||
|
/**
|
||||||
|
* To manipulate the browser title bar, adds the Suffix "OpenSlides 3"
|
||||||
|
*
|
||||||
|
* Might be a config variable later at some point
|
||||||
|
*/
|
||||||
private titleSuffix = ' - OpenSlides 3';
|
private titleSuffix = ' - OpenSlides 3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Child constructor that implements the titleServices and calls Super from OpenSlidesComponent
|
||||||
|
*/
|
||||||
constructor(protected titleService?: Title) {
|
constructor(protected titleService?: Title) {
|
||||||
// throws a warning even tho it is the new syntax. Ignored for now.
|
super();
|
||||||
this.injector = Injector.create([{ provide: DataStoreService, useClass: DataStoreService, deps: [] }]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setTitle(prefix: string) {
|
/**
|
||||||
|
* Set the title in web browser using angulars TitleService
|
||||||
|
* @param prefix The title prefix. Should be translated here.
|
||||||
|
* TODO Might translate the prefix here?
|
||||||
|
*/
|
||||||
|
setTitle(prefix: string): void {
|
||||||
this.titleService.setTitle(prefix + this.titleSuffix);
|
this.titleService.setTitle(prefix + this.titleSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static injection of DataStore (ds) in all child instancces of BaseComponent
|
|
||||||
// use this.DS[...]
|
|
||||||
get DS(): DataStoreService {
|
|
||||||
if (this.dataStore == null) {
|
|
||||||
this.dataStore = this.injector.get(DataStoreService);
|
|
||||||
}
|
|
||||||
return this.dataStore;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
<div *ngIf="alert" [ngClass]="cssClass(alert)" class="alert-dismissable">
|
|
||||||
{{alert.message}}
|
|
||||||
<a class="close" (click)="removeAlert(alert)">×</a>
|
|
||||||
</div>
|
|
@ -1,24 +0,0 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { AlertComponent } from './alert.component';
|
|
||||||
|
|
||||||
describe('AlertComponent', () => {
|
|
||||||
let component: AlertComponent;
|
|
||||||
let fixture: ComponentFixture<AlertComponent>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
declarations: [AlertComponent]
|
|
||||||
}).compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(AlertComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,38 +0,0 @@
|
|||||||
import { Component, OnInit, Input } from '@angular/core';
|
|
||||||
|
|
||||||
import { Alert, AlertType } from 'app/core/models/alert';
|
|
||||||
|
|
||||||
/**TODO Drafted for now. Since the UI is not done yet, this might be replaced or disappear entirely.
|
|
||||||
* Furtermore, Material UI does not support these kinds of alerts
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-alert',
|
|
||||||
templateUrl: './alert.component.html',
|
|
||||||
styleUrls: ['./alert.component.css']
|
|
||||||
})
|
|
||||||
export class AlertComponent implements OnInit {
|
|
||||||
@Input() alert: Alert;
|
|
||||||
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
ngOnInit() {}
|
|
||||||
|
|
||||||
removeAlert(alert: Alert) {
|
|
||||||
this.alert = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
cssClass(alert: Alert) {
|
|
||||||
// return css class based on alert type
|
|
||||||
switch (alert.type) {
|
|
||||||
case AlertType.Success:
|
|
||||||
return 'alert alert-success';
|
|
||||||
case AlertType.Error:
|
|
||||||
return 'alert alert-danger';
|
|
||||||
case AlertType.Info:
|
|
||||||
return 'alert alert-info';
|
|
||||||
case AlertType.Warning:
|
|
||||||
return 'alert alert-warning';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +1,40 @@
|
|||||||
import { Directive, Input, ElementRef, TemplateRef, ViewContainerRef, OnInit } from '@angular/core';
|
import { Directive, Input, ElementRef, TemplateRef, ViewContainerRef, OnInit } from '@angular/core';
|
||||||
|
|
||||||
import { OperatorService } from 'app/core/services/operator.service';
|
import { OperatorService } from 'app/core/services/operator.service';
|
||||||
import { BaseComponent } from 'app/base.component';
|
import { OpenSlidesComponent } from '../../openslides.component';
|
||||||
import { Group } from 'app/core/models/users/group';
|
import { Group } from 'app/core/models/users/group';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directive to check if the {@link OperatorService} has the correct permissions to access certain functions
|
||||||
|
*
|
||||||
|
* Successor of os-perms in OpenSlides 2.2
|
||||||
|
* @example <div *appOsPerms=".." ..> ... < /div>
|
||||||
|
*/
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[appOsPerms]'
|
selector: '[appOsPerms]'
|
||||||
})
|
})
|
||||||
export class OsPermsDirective extends BaseComponent {
|
export class OsPermsDirective extends OpenSlidesComponent {
|
||||||
|
/**
|
||||||
|
* Holds the {@link OperatorService} permissions
|
||||||
|
*/
|
||||||
private userPermissions: string[];
|
private userPermissions: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the required permissions the access a feature
|
||||||
|
*/
|
||||||
private permissions;
|
private permissions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the direcctive once. Observes the operator for it's groups so the directvice can perform changes
|
||||||
|
* dynamically
|
||||||
|
*
|
||||||
|
* @param template inner part of the HTML container
|
||||||
|
* @param viewContainer outer part of the HTML container (for example a `<div>`)
|
||||||
|
* @param operator OperatorService
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private element: ElementRef,
|
|
||||||
private template: TemplateRef<any>,
|
private template: TemplateRef<any>,
|
||||||
private viewContainer: ViewContainerRef, //TODO private operator. OperatorService
|
private viewContainer: ViewContainerRef,
|
||||||
private operator: OperatorService
|
private operator: OperatorService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@ -30,6 +51,10 @@ export class OsPermsDirective extends BaseComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comes directly from the view.
|
||||||
|
* The value defines the requires permissions.
|
||||||
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
set appOsPerms(value) {
|
set appOsPerms(value) {
|
||||||
this.permissions = value;
|
this.permissions = value;
|
||||||
@ -37,6 +62,10 @@ export class OsPermsDirective extends BaseComponent {
|
|||||||
this.updateView();
|
this.updateView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the local `userPermissions[]` by the permissions found in the operators groups
|
||||||
|
* Will just set, but not remove them.
|
||||||
|
*/
|
||||||
private readUserPermissions(): void {
|
private readUserPermissions(): void {
|
||||||
const opGroups = this.operator.getGroups();
|
const opGroups = this.operator.getGroups();
|
||||||
console.log('operator Groups: ', opGroups);
|
console.log('operator Groups: ', opGroups);
|
||||||
@ -45,7 +74,9 @@ export class OsPermsDirective extends BaseComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// hides or shows a contrainer
|
/**
|
||||||
|
* Shows or hides certain content in the view.
|
||||||
|
*/
|
||||||
private updateView(): void {
|
private updateView(): void {
|
||||||
if (this.checkPermissions()) {
|
if (this.checkPermissions()) {
|
||||||
// will just render the page normally
|
// will just render the page normally
|
||||||
@ -56,7 +87,10 @@ export class OsPermsDirective extends BaseComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks for the required permission
|
/**
|
||||||
|
* Compare the required permissions with the users permissions.
|
||||||
|
* Returns true if the users permissions fit.
|
||||||
|
*/
|
||||||
private checkPermissions(): boolean {
|
private checkPermissions(): boolean {
|
||||||
let isPermitted = false;
|
let isPermitted = false;
|
||||||
if (this.userPermissions && this.permissions) {
|
if (this.userPermissions && this.permissions) {
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
<div *ngFor="let toast of toasts" class="{{ cssClass(toast) }} alert-dismissable">
|
|
||||||
{{toast.message}}
|
|
||||||
<a class="close" (click)="removeAlert(toast)">×</a>
|
|
||||||
</div>
|
|
@ -1,24 +0,0 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { ToastComponent } from './toast.component';
|
|
||||||
|
|
||||||
describe('ToastComponent', () => {
|
|
||||||
let component: ToastComponent;
|
|
||||||
let fixture: ComponentFixture<ToastComponent>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
declarations: [ToastComponent]
|
|
||||||
}).compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(ToastComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,54 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
|
|
||||||
import { Alert, AlertType } from 'app/core/models/alert';
|
|
||||||
import { ToastService } from 'app/core/services/toast.service';
|
|
||||||
|
|
||||||
/**TODO Drafted for now. Since the UI is not done yet, this might be replaced or disappear entirely.
|
|
||||||
* Furtermore, Material UI does not support these kinds of alerts
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-toast',
|
|
||||||
templateUrl: './toast.component.html',
|
|
||||||
styleUrls: ['./toast.component.css']
|
|
||||||
})
|
|
||||||
export class ToastComponent implements OnInit {
|
|
||||||
toasts: Alert[] = [];
|
|
||||||
|
|
||||||
constructor(private toastService: ToastService) {}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.toastService.getToast().subscribe((alert: Alert) => {
|
|
||||||
if (!alert) {
|
|
||||||
// clear alerts when an empty alert is received
|
|
||||||
this.toasts = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add alert to array
|
|
||||||
this.toasts.push(alert);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
removeAlert(alert: Alert) {
|
|
||||||
this.toasts = this.toasts.filter(x => x !== alert);
|
|
||||||
}
|
|
||||||
|
|
||||||
cssClass(alert: Alert) {
|
|
||||||
if (!alert) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// return css class based on alert type
|
|
||||||
switch (alert.type) {
|
|
||||||
case AlertType.Success:
|
|
||||||
return 'alert alert-success';
|
|
||||||
case AlertType.Error:
|
|
||||||
return 'alert alert-danger';
|
|
||||||
case AlertType.Info:
|
|
||||||
return 'alert alert-info';
|
|
||||||
case AlertType.Warning:
|
|
||||||
return 'alert alert-warning';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* custom exception that indicated that a collectionString is invalid.
|
||||||
|
*/
|
||||||
export class ImproperlyConfiguredError extends Error {
|
export class ImproperlyConfiguredError extends Error {
|
||||||
|
/**
|
||||||
|
* Default Constructor for Errors
|
||||||
|
* @param m The Error Message
|
||||||
|
*/
|
||||||
constructor(m: string) {
|
constructor(m: string) {
|
||||||
super(m);
|
super(m);
|
||||||
}
|
}
|
||||||
|
24
client/src/app/core/http-interceptor.ts
Normal file
24
client/src/app/core/http-interceptor.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interceptor class for HTTP requests. Replaces all 'httpOptions' in all http.get or http.post requests.
|
||||||
|
*
|
||||||
|
* Should not need further adjustment.
|
||||||
|
*/
|
||||||
|
export class AddHeaderInterceptor implements HttpInterceptor {
|
||||||
|
/**
|
||||||
|
* Normal HttpInterceptor usage
|
||||||
|
*
|
||||||
|
* @param req Will clone the request and intercept it with our desired headers
|
||||||
|
* @param next HttpHandler will catch the response and forwards it to the original instance
|
||||||
|
*/
|
||||||
|
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||||
|
const clonedRequest = req.clone({
|
||||||
|
withCredentials: true,
|
||||||
|
headers: req.headers.set('Content-Type', 'application/json')
|
||||||
|
});
|
||||||
|
|
||||||
|
return next.handle(clonedRequest);
|
||||||
|
}
|
||||||
|
}
|
29
client/src/app/core/models/agenda/content-object.ts
Normal file
29
client/src/app/core/models/agenda/content-object.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// import { Serializable } from 'app/core/models/serializable';
|
||||||
|
import { Deserializable } from 'app/core/models/deserializable.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of the content object in agenda item
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export class ContentObject implements Deserializable {
|
||||||
|
/**
|
||||||
|
* Is the same with dataStores collectionString
|
||||||
|
*/
|
||||||
|
collection: string;
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needs to be completely optional because agenda has (yet) the optional parameter 'speaker'
|
||||||
|
* @param collection
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
constructor(collection?: string, id?: number) {
|
||||||
|
this.collection = collection;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize(input: any): this {
|
||||||
|
Object.assign(this, input);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -1,54 +1,84 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
import { Speaker } from './speaker';
|
||||||
|
import { ContentObject } from './content-object';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representations of agenda Item
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class Item extends BaseModel {
|
export class Item extends BaseModel {
|
||||||
static collectionString = 'agenda/item';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
closed: boolean;
|
|
||||||
comment: string;
|
|
||||||
content_object: Object;
|
|
||||||
duration: number; //time?
|
|
||||||
is_hidden: boolean;
|
|
||||||
item_number: string;
|
item_number: string;
|
||||||
list_view_title: string;
|
|
||||||
parent_id: number;
|
|
||||||
speaker_list_closed: boolean;
|
|
||||||
speakers: BaseModel[]; //we should not know users just yet
|
|
||||||
title: string;
|
title: string;
|
||||||
|
list_view_title: string;
|
||||||
|
comment: string;
|
||||||
|
closed: boolean;
|
||||||
type: number;
|
type: number;
|
||||||
|
is_hidden: boolean;
|
||||||
|
duration: number;
|
||||||
|
speakers: Speaker[];
|
||||||
|
speaker_list_closed: boolean;
|
||||||
|
content_object: ContentObject;
|
||||||
weight: number;
|
weight: number;
|
||||||
|
parent_id: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id?: number,
|
||||||
closed?: boolean,
|
|
||||||
comment?: string,
|
|
||||||
content_object?: Object,
|
|
||||||
duration?: number,
|
|
||||||
is_hidden?: boolean,
|
|
||||||
item_number?: string,
|
item_number?: string,
|
||||||
list_view_title?: string,
|
|
||||||
parent_id?: number,
|
|
||||||
speaker_list_closed?: boolean,
|
|
||||||
speakers?: BaseModel[],
|
|
||||||
title?: string,
|
title?: string,
|
||||||
|
list_view_title?: string,
|
||||||
|
comment?: string,
|
||||||
|
closed?: boolean,
|
||||||
type?: number,
|
type?: number,
|
||||||
weight?: number
|
is_hidden?: boolean,
|
||||||
|
duration?: number,
|
||||||
|
speakers?: Speaker[],
|
||||||
|
speaker_list_closed?: boolean,
|
||||||
|
content_object?: ContentObject,
|
||||||
|
weight?: number,
|
||||||
|
parent_id?: number
|
||||||
) {
|
) {
|
||||||
super(id);
|
super();
|
||||||
this.comment = comment;
|
this._collectionString = 'agenda/item';
|
||||||
this.content_object = content_object;
|
this.id = id;
|
||||||
this.duration = duration;
|
|
||||||
this.is_hidden = is_hidden;
|
|
||||||
this.item_number = item_number;
|
this.item_number = item_number;
|
||||||
this.list_view_title = list_view_title;
|
|
||||||
this.parent_id = parent_id;
|
|
||||||
this.speaker_list_closed = speaker_list_closed;
|
|
||||||
this.speakers = speakers;
|
|
||||||
this.title = title;
|
this.title = title;
|
||||||
|
this.list_view_title = list_view_title;
|
||||||
|
this.comment = comment;
|
||||||
|
this.closed = closed;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.is_hidden = is_hidden;
|
||||||
|
this.duration = duration;
|
||||||
|
this.speakers = speakers;
|
||||||
|
this.speaker_list_closed = speaker_list_closed;
|
||||||
|
this.content_object = content_object;
|
||||||
this.weight = weight;
|
this.weight = weight;
|
||||||
|
this.parent_id = parent_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
getSpeakersAsUser(): BaseModel | BaseModel[] {
|
||||||
return Item.collectionString;
|
const speakerIds = [];
|
||||||
|
this.speakers.forEach(speaker => {
|
||||||
|
speakerIds.push(speaker.user_id);
|
||||||
|
});
|
||||||
|
return this.DS.get('users/user', ...speakerIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
getContentObject(): BaseModel | BaseModel[] {
|
||||||
|
return this.DS.get(this.content_object.collection, this.content_object.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize(input: any): this {
|
||||||
|
Object.assign(this, input);
|
||||||
|
this.content_object = new ContentObject().deserialize(input.content_object);
|
||||||
|
|
||||||
|
if (input.speakers instanceof Array) {
|
||||||
|
this.speakers = [];
|
||||||
|
input.speakers.forEach(speakerData => {
|
||||||
|
this.speakers.push(new Speaker().deserialize(speakerData));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
50
client/src/app/core/models/agenda/speaker.ts
Normal file
50
client/src/app/core/models/agenda/speaker.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { Deserializable } from 'app/core/models/deserializable.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a speaker in an agenda item
|
||||||
|
*
|
||||||
|
* Part of the 'speakers' list.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export class Speaker implements Deserializable {
|
||||||
|
id: number;
|
||||||
|
user_id: number;
|
||||||
|
begin_time: string; //TODO this is a time object
|
||||||
|
end_time: string; // TODO this is a time object
|
||||||
|
weight: number;
|
||||||
|
marked: boolean;
|
||||||
|
item_id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needs to be completely optional because agenda has (yet) the optional parameter 'speaker'
|
||||||
|
* @param id
|
||||||
|
* @param user_id
|
||||||
|
* @param begin_time
|
||||||
|
* @param end_time
|
||||||
|
* @param weight
|
||||||
|
* @param marked
|
||||||
|
* @param item_id
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
id?: number,
|
||||||
|
user_id?: number,
|
||||||
|
begin_time?: string,
|
||||||
|
end_time?: string,
|
||||||
|
weight?: number,
|
||||||
|
marked?: boolean,
|
||||||
|
item_id?: number
|
||||||
|
) {
|
||||||
|
this.id = id;
|
||||||
|
this.user_id = user_id;
|
||||||
|
this.begin_time = begin_time;
|
||||||
|
this.end_time = end_time;
|
||||||
|
this.weight = weight;
|
||||||
|
this.marked = marked;
|
||||||
|
this.item_id = item_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize(input: any): this {
|
||||||
|
Object.assign(this, input);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
export class Alert {
|
|
||||||
type: AlertType;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AlertType {
|
|
||||||
Success,
|
|
||||||
Error,
|
|
||||||
Info,
|
|
||||||
Warning
|
|
||||||
}
|
|
34
client/src/app/core/models/assignments/assignment-user.ts
Normal file
34
client/src/app/core/models/assignments/assignment-user.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Deserializable } from '../deserializable.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content of the 'assignment_related_users' property
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export class AssignmentUser implements Deserializable {
|
||||||
|
id: number;
|
||||||
|
user_id: number;
|
||||||
|
elected: boolean;
|
||||||
|
assignment_id: number;
|
||||||
|
weight: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needs to be completely optional because assignment has (yet) the optional parameter 'assignment_related_users'
|
||||||
|
* @param id
|
||||||
|
* @param user_id
|
||||||
|
* @param elected
|
||||||
|
* @param assignment_id
|
||||||
|
* @param weight
|
||||||
|
*/
|
||||||
|
constructor(id?: number, user_id?: number, elected?: boolean, assignment_id?: number, weight?: number) {
|
||||||
|
this.id = id;
|
||||||
|
this.user_id = user_id;
|
||||||
|
this.elected = elected;
|
||||||
|
this.assignment_id = assignment_id;
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize(input: any): this {
|
||||||
|
Object.assign(this, input);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -1,41 +1,78 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from '../base-model';
|
||||||
|
import { AssignmentUser } from './assignment-user';
|
||||||
|
import { Poll } from './poll';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of an assignment.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class Assignment extends BaseModel {
|
export class Assignment extends BaseModel {
|
||||||
static collectionString = 'assignments/assignment';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
agenda_item_id: number;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
open_posts: number;
|
open_posts: number;
|
||||||
phase: number;
|
phase: number;
|
||||||
|
assignment_related_users: AssignmentUser[];
|
||||||
poll_description_default: number;
|
poll_description_default: number;
|
||||||
polls: Object[];
|
polls: Poll[];
|
||||||
|
agenda_item_id: number;
|
||||||
tags_id: number[];
|
tags_id: number[];
|
||||||
title: string;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id?: number,
|
||||||
agenda_item_id?: number,
|
title?: string,
|
||||||
description?: string,
|
description?: string,
|
||||||
open_posts?: number,
|
open_posts?: number,
|
||||||
phase?: number,
|
phase?: number,
|
||||||
|
assignment_related_users?: AssignmentUser[],
|
||||||
poll_description_default?: number,
|
poll_description_default?: number,
|
||||||
polls?: Object[],
|
polls?: Poll[],
|
||||||
tags_id?: number[],
|
agenda_item_id?: number,
|
||||||
title?: string
|
tags_id?: number[]
|
||||||
) {
|
) {
|
||||||
super(id);
|
super();
|
||||||
|
this._collectionString = 'assignments/assignment';
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.agenda_item_id = agenda_item_id;
|
this.title = title;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.open_posts = open_posts;
|
this.open_posts = open_posts;
|
||||||
this.phase = phase;
|
this.phase = phase;
|
||||||
|
this.assignment_related_users = assignment_related_users || []; //TODO Array
|
||||||
this.poll_description_default = poll_description_default;
|
this.poll_description_default = poll_description_default;
|
||||||
this.polls = polls;
|
this.polls = polls || Array(); // TODO Array
|
||||||
|
this.agenda_item_id = agenda_item_id;
|
||||||
this.tags_id = tags_id;
|
this.tags_id = tags_id;
|
||||||
this.title = title;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
getAssignmentReleatedUsers(): BaseModel | BaseModel[] {
|
||||||
return Assignment.collectionString;
|
const userIds = [];
|
||||||
|
this.assignment_related_users.forEach(user => {
|
||||||
|
userIds.push(user.user_id);
|
||||||
|
});
|
||||||
|
return this.DS.get('users/user', ...userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTags(): BaseModel | BaseModel[] {
|
||||||
|
return this.DS.get('core/tag', ...this.tags_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize(input: any): this {
|
||||||
|
Object.assign(this, input);
|
||||||
|
|
||||||
|
if (input.assignment_related_users instanceof Array) {
|
||||||
|
this.assignment_related_users = [];
|
||||||
|
input.assignment_related_users.forEach(assignmentUserData => {
|
||||||
|
this.assignment_related_users.push(new AssignmentUser().deserialize(assignmentUserData));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.polls instanceof Array) {
|
||||||
|
this.polls = [];
|
||||||
|
input.polls.forEach(pollData => {
|
||||||
|
this.polls.push(new Poll().deserialize(pollData));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
46
client/src/app/core/models/assignments/poll-option.ts
Normal file
46
client/src/app/core/models/assignments/poll-option.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Deserializable } from '../deserializable.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a poll option
|
||||||
|
*
|
||||||
|
* part of the 'polls-options'-array in poll
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export class PollOption implements Deserializable {
|
||||||
|
id: number;
|
||||||
|
candidate_id: number;
|
||||||
|
is_elected: boolean;
|
||||||
|
votes: number[];
|
||||||
|
poll_id: number;
|
||||||
|
weight: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needs to be completely optional because poll has (yet) the optional parameter 'poll-options'
|
||||||
|
* @param id
|
||||||
|
* @param candidate_id
|
||||||
|
* @param is_elected
|
||||||
|
* @param votes
|
||||||
|
* @param poll_id
|
||||||
|
* @param weight
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
id?: number,
|
||||||
|
candidate_id?: number,
|
||||||
|
is_elected?: boolean,
|
||||||
|
votes?: number[],
|
||||||
|
poll_id?: number,
|
||||||
|
weight?: number
|
||||||
|
) {
|
||||||
|
this.id = id;
|
||||||
|
this.candidate_id = candidate_id;
|
||||||
|
this.is_elected = is_elected;
|
||||||
|
this.votes = votes;
|
||||||
|
this.poll_id = poll_id;
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize(input: any): this {
|
||||||
|
Object.assign(this, input);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
68
client/src/app/core/models/assignments/poll.ts
Normal file
68
client/src/app/core/models/assignments/poll.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { PollOption } from './poll-option';
|
||||||
|
import { Deserializable } from '../deserializable.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content of the 'polls' property of assignments
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export class Poll implements Deserializable {
|
||||||
|
id: number;
|
||||||
|
pollmethod: string;
|
||||||
|
description: string;
|
||||||
|
published: boolean;
|
||||||
|
options: PollOption[];
|
||||||
|
votesvalid: number;
|
||||||
|
votesinvalid: number;
|
||||||
|
votescast: number;
|
||||||
|
has_votes: boolean;
|
||||||
|
assignment_id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needs to be completely optional because assignment has (yet) the optional parameter 'polls'
|
||||||
|
* @param id
|
||||||
|
* @param pollmethod
|
||||||
|
* @param description
|
||||||
|
* @param published
|
||||||
|
* @param options
|
||||||
|
* @param votesvalid
|
||||||
|
* @param votesinvalid
|
||||||
|
* @param votescast
|
||||||
|
* @param has_votes
|
||||||
|
* @param assignment_id
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
id?: number,
|
||||||
|
pollmethod?: string,
|
||||||
|
description?: string,
|
||||||
|
published?: boolean,
|
||||||
|
options?: PollOption[],
|
||||||
|
votesvalid?: number,
|
||||||
|
votesinvalid?: number,
|
||||||
|
votescast?: number,
|
||||||
|
has_votes?: boolean,
|
||||||
|
assignment_id?: number
|
||||||
|
) {
|
||||||
|
this.id = id;
|
||||||
|
this.pollmethod = pollmethod;
|
||||||
|
this.description = description;
|
||||||
|
this.published = published;
|
||||||
|
this.options = options || Array(new PollOption()); //TODO Array
|
||||||
|
this.votesvalid = votesvalid;
|
||||||
|
this.votesinvalid = votesinvalid;
|
||||||
|
this.votescast = votescast;
|
||||||
|
this.has_votes = has_votes;
|
||||||
|
this.assignment_id = assignment_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize(input: any): this {
|
||||||
|
Object.assign(this, input);
|
||||||
|
|
||||||
|
if (input.options instanceof Array) {
|
||||||
|
this.options = [];
|
||||||
|
input.options.forEach(pollOptionData => {
|
||||||
|
this.options.push(new PollOption().deserialize(pollOptionData));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
49
client/src/app/core/models/base-model.ts
Normal file
49
client/src/app/core/models/base-model.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { OpenSlidesComponent } from 'app/openslides.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define that an ID might be a number or a string.
|
||||||
|
*/
|
||||||
|
export type ModelId = number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract parent class to set rules and functions for all models.
|
||||||
|
*/
|
||||||
|
export abstract class BaseModel extends OpenSlidesComponent {
|
||||||
|
/**
|
||||||
|
* force children of BaseModel to have a collectionString.
|
||||||
|
*
|
||||||
|
* Has a getter but no setter.
|
||||||
|
*/
|
||||||
|
protected abstract _collectionString: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* force children of BaseModel to have an `id`
|
||||||
|
*/
|
||||||
|
abstract id: ModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* constructor that calls super from parent class
|
||||||
|
*/
|
||||||
|
protected constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the collectionString.
|
||||||
|
*
|
||||||
|
* The server and the dataStore use it to identify the collection.
|
||||||
|
*/
|
||||||
|
get collectionString(): string {
|
||||||
|
return this._collectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Most simple and most commonly used deserialize function.
|
||||||
|
* Inherited to children, can be overwritten for special use cases
|
||||||
|
* @param input JSON data for deserialization.
|
||||||
|
*/
|
||||||
|
deserialize(input: any): this {
|
||||||
|
Object.assign(this, input);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -1,42 +0,0 @@
|
|||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
// import { DS } from 'app/core/services/DS.service';
|
|
||||||
import { ImproperlyConfiguredError } from 'app/core/exceptions';
|
|
||||||
|
|
||||||
const INVALID_COLLECTION_STRING = 'invalid-collection-string';
|
|
||||||
|
|
||||||
export type ModelId = number | string;
|
|
||||||
|
|
||||||
export abstract class BaseModel {
|
|
||||||
static collectionString = INVALID_COLLECTION_STRING;
|
|
||||||
id: ModelId;
|
|
||||||
|
|
||||||
constructor(id: ModelId) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert an serialized version of the model to an instance of the class
|
|
||||||
// jsonString is usually the server respince
|
|
||||||
// T is the target model, User, Motion, Whatever
|
|
||||||
// demands full functionening Models with constructors
|
|
||||||
static fromJSON(jsonString: {}, Type): BaseModel {
|
|
||||||
// create an instance of the User class
|
|
||||||
const model = Object.create(Type.prototype);
|
|
||||||
// copy all the fields from the json object
|
|
||||||
return Object.assign(model, jsonString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCollectionString(): string {
|
|
||||||
return BaseModel.collectionString;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO document this function.
|
|
||||||
// public getCheckedCollectionString(): string {
|
|
||||||
// if (this.collectionString === INVALID_COLLECTION_STRING) {
|
|
||||||
// throw new ImproperlyConfiguredError(
|
|
||||||
// 'Invalid collection string: Please override the static getCollectionString method!'
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// return collectionString;
|
|
||||||
// }
|
|
||||||
}
|
|
@ -1,19 +1,26 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from '../base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of chat messages.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class ChatMessage extends BaseModel {
|
export class ChatMessage extends BaseModel {
|
||||||
static collectionString = 'core/chat-message';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
message: string;
|
message: string;
|
||||||
timestamp: string; // TODO: Type for timestamp
|
timestamp: string; // TODO: Type for timestamp
|
||||||
user_id: number;
|
user_id: number;
|
||||||
|
|
||||||
constructor(id: number, message?: string, timestamp?: string, user_id?: number) {
|
constructor(id?: number, message?: string, timestamp?: string, user_id?: number) {
|
||||||
super(id);
|
super();
|
||||||
|
this._collectionString = 'core/chat-message';
|
||||||
|
this.id = id;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
|
this.user_id = user_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
getUser(): BaseModel | BaseModel[] {
|
||||||
return ChatMessage.collectionString;
|
return this.DS.get('users/user', this.user_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from '../base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a config variable
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class Config extends BaseModel {
|
export class Config extends BaseModel {
|
||||||
static collectionString = 'core/config';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
key: string;
|
key: string;
|
||||||
value: Object;
|
value: Object;
|
||||||
|
|
||||||
constructor(id: number, key?: string, value?: Object) {
|
constructor(id?: number, key?: string, value?: Object) {
|
||||||
super(id);
|
super();
|
||||||
|
this._collectionString = 'core/config';
|
||||||
|
this.id = id;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
|
||||||
return Config.collectionString;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,24 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a countdown
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class Countdown extends BaseModel {
|
export class Countdown extends BaseModel {
|
||||||
static collectionString = 'core/countdown';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
countdown_time: number;
|
|
||||||
default_time: number;
|
|
||||||
description: string;
|
description: string;
|
||||||
|
default_time: number;
|
||||||
|
countdown_time: number;
|
||||||
|
running: boolean;
|
||||||
|
|
||||||
constructor(id: number, countdown_time?: number, default_time?: number, description?: string) {
|
constructor(id?: number, countdown_time?: number, default_time?: number, description?: string, running?: boolean) {
|
||||||
super(id);
|
super();
|
||||||
|
this._collectionString = 'core/countdown';
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.countdown_time = countdown_time;
|
|
||||||
this.default_time = default_time;
|
|
||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
this.default_time = default_time;
|
||||||
|
this.countdown_time = countdown_time;
|
||||||
public getCollectionString(): string {
|
this.running = running;
|
||||||
return Countdown.collectionString;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a projector message.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class ProjectorMessage extends BaseModel {
|
export class ProjectorMessage extends BaseModel {
|
||||||
static collectionString = 'core/projector-message';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
message: string;
|
message: string;
|
||||||
|
|
||||||
constructor(id: number, message?: string) {
|
constructor(id?: number, message?: string) {
|
||||||
super(id);
|
super();
|
||||||
|
this._collectionString = 'core/projector-message';
|
||||||
|
this.id = id;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
|
||||||
return ProjectorMessage.collectionString;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,42 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a projector. Has the nested property "projectiondefaults"
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class Projector extends BaseModel {
|
export class Projector extends BaseModel {
|
||||||
static collectionString = 'core/projector';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
blank: boolean;
|
|
||||||
elements: Object;
|
elements: Object;
|
||||||
height: number;
|
|
||||||
name: string;
|
|
||||||
projectiondefaults: BaseModel[];
|
|
||||||
scale: number;
|
scale: number;
|
||||||
scroll: number;
|
scroll: number;
|
||||||
|
name: string;
|
||||||
|
blank: boolean;
|
||||||
width: number;
|
width: number;
|
||||||
|
height: number;
|
||||||
|
projectiondefaults: Object[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id?: number,
|
||||||
blank?: boolean,
|
|
||||||
elements?: Object,
|
elements?: Object,
|
||||||
height?: number,
|
|
||||||
name?: string,
|
|
||||||
projectiondefaults?: BaseModel[],
|
|
||||||
scale?: number,
|
scale?: number,
|
||||||
scroll?: number,
|
scroll?: number,
|
||||||
width?: number
|
name?: string,
|
||||||
|
blank?: boolean,
|
||||||
|
width?: number,
|
||||||
|
height?: number,
|
||||||
|
projectiondefaults?: Object[]
|
||||||
) {
|
) {
|
||||||
super(id);
|
super();
|
||||||
this.blank = blank;
|
this._collectionString = 'core/projector';
|
||||||
|
this.id = id;
|
||||||
this.elements = elements;
|
this.elements = elements;
|
||||||
this.height = height;
|
|
||||||
this.name = name;
|
|
||||||
this.projectiondefaults = projectiondefaults;
|
|
||||||
this.scale = scale;
|
this.scale = scale;
|
||||||
this.scroll = scroll;
|
this.scroll = scroll;
|
||||||
|
this.name = name;
|
||||||
|
this.blank = blank;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
}
|
this.height = height;
|
||||||
|
this.projectiondefaults = projectiondefaults;
|
||||||
public getCollectionString(): string {
|
|
||||||
return Projector.collectionString;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a tag.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class Tag extends BaseModel {
|
export class Tag extends BaseModel {
|
||||||
static collectionString = 'core/tag';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
constructor(id: number, name?: string) {
|
constructor(id?: number, name?: string) {
|
||||||
super(id);
|
super();
|
||||||
|
this._collectionString = 'core/tag';
|
||||||
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
|
||||||
return Tag.collectionString;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
13
client/src/app/core/models/deserializable.model.ts
Normal file
13
client/src/app/core/models/deserializable.model.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Interface tells models to offer a 'deserialize' function
|
||||||
|
*
|
||||||
|
* Also nested objects and arrays have have to be handled.
|
||||||
|
* @example const myUser = new User().deserialize(jsonData)
|
||||||
|
*/
|
||||||
|
export interface Deserializable {
|
||||||
|
/**
|
||||||
|
* should be used to assign JSON values to the object itself.
|
||||||
|
* @param input
|
||||||
|
*/
|
||||||
|
deserialize(input: any): this;
|
||||||
|
}
|
25
client/src/app/core/models/mediafiles/file.ts
Normal file
25
client/src/app/core/models/mediafiles/file.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Deserializable } from '../deserializable.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name and the type of a mediaFile.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export class File implements Deserializable {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needs to be fully optional, because the 'mediafile'-property in the mediaFile class is optional as well
|
||||||
|
* @param name The name of the file
|
||||||
|
* @param type The tape (jpg, png, pdf)
|
||||||
|
*/
|
||||||
|
constructor(name?: string, type?: string) {
|
||||||
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize(input: any): this {
|
||||||
|
Object.assign(this, input);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -1,37 +1,50 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
import { File } from './file';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of MediaFile. Has the nested property "File"
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class Mediafile extends BaseModel {
|
export class Mediafile extends BaseModel {
|
||||||
static collectionString = 'mediafiles/mediafile';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
|
title: string;
|
||||||
|
mediafile: File;
|
||||||
|
media_url_prefix: string;
|
||||||
|
uploader_id: number;
|
||||||
filesize: string;
|
filesize: string;
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
media_url_prefix: string;
|
|
||||||
mediafile: Object;
|
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
title: string;
|
|
||||||
uploader_id: number;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id?: number,
|
||||||
|
title?: string,
|
||||||
|
mediafile?: File,
|
||||||
|
media_url_prefix?: string,
|
||||||
|
uploader_id?: number,
|
||||||
filesize?: string,
|
filesize?: string,
|
||||||
hidden?: boolean,
|
hidden?: boolean,
|
||||||
media_url_prefix?: string,
|
timestamp?: string
|
||||||
mediafile?: Object,
|
|
||||||
timestamp?: string,
|
|
||||||
title?: string,
|
|
||||||
uploader_id?: number
|
|
||||||
) {
|
) {
|
||||||
super(id);
|
super();
|
||||||
|
this._collectionString = 'mediafiles/mediafile';
|
||||||
|
this.id = id;
|
||||||
|
this.title = title;
|
||||||
|
this.mediafile = mediafile;
|
||||||
|
this.media_url_prefix = media_url_prefix;
|
||||||
|
this.uploader_id = uploader_id;
|
||||||
this.filesize = filesize;
|
this.filesize = filesize;
|
||||||
this.hidden = hidden;
|
this.hidden = hidden;
|
||||||
this.media_url_prefix = media_url_prefix;
|
|
||||||
this.mediafile = mediafile;
|
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.title = title;
|
|
||||||
this.uploader_id = uploader_id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
deserialize(input: any): this {
|
||||||
return Mediafile.collectionString;
|
Object.assign(this, input);
|
||||||
|
this.mediafile = new File().deserialize(input.mediafile);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUploader(): BaseModel | BaseModel[] {
|
||||||
|
return this.DS.get('users/user', this.uploader_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a motion category. Has the nested property "File"
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class Category extends BaseModel {
|
export class Category extends BaseModel {
|
||||||
static collectionString = 'motions/category';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
prefix: string;
|
prefix: string;
|
||||||
|
|
||||||
constructor(id: number, name?: string, prefix?: string) {
|
constructor(id?: number, name?: string, prefix?: string) {
|
||||||
super(id);
|
super();
|
||||||
|
this._collectionString = 'motions/category';
|
||||||
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.prefix = prefix;
|
this.prefix = prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
|
||||||
return Category.collectionString;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a motion block.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class MotionBlock extends BaseModel {
|
export class MotionBlock extends BaseModel {
|
||||||
static collectionString = 'motions/motion-block';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
agenda_item_id: number;
|
|
||||||
title: string;
|
title: string;
|
||||||
|
agenda_item_id: number;
|
||||||
|
|
||||||
constructor(id: number, agenda_item_id?: number, title?: string) {
|
constructor(id?: number, title?: string, agenda_item_id?: number) {
|
||||||
super(id);
|
super();
|
||||||
this.agenda_item_id = agenda_item_id;
|
this._collectionString = 'motions/motion-block';
|
||||||
|
this.id = id;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
|
this.agenda_item_id = agenda_item_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
getAgenda(): BaseModel | BaseModel[] {
|
||||||
return MotionBlock.collectionString;
|
return this.DS.get('agenda/item', this.agenda_item_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,42 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a motion change recommendation.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class MotionChangeReco extends BaseModel {
|
export class MotionChangeReco extends BaseModel {
|
||||||
static collectionString = 'motions/motion-change-recommendation';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
creation_time: string;
|
motion_version_id: number;
|
||||||
|
rejected: boolean;
|
||||||
|
type: number;
|
||||||
|
other_description: string;
|
||||||
line_from: number;
|
line_from: number;
|
||||||
line_to: number;
|
line_to: number;
|
||||||
motion_version_id: number;
|
|
||||||
other_description: string;
|
|
||||||
rejected: boolean;
|
|
||||||
text: string;
|
text: string;
|
||||||
type: number;
|
creation_time: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id?: number,
|
||||||
creation_time?: string,
|
motion_version_id?: number,
|
||||||
|
rejected?: boolean,
|
||||||
|
type?: number,
|
||||||
|
other_description?: string,
|
||||||
line_from?: number,
|
line_from?: number,
|
||||||
line_to?: number,
|
line_to?: number,
|
||||||
motion_version_id?: number,
|
|
||||||
other_description?: string,
|
|
||||||
rejected?: boolean,
|
|
||||||
text?: string,
|
text?: string,
|
||||||
type?: number
|
creation_time?: string
|
||||||
) {
|
) {
|
||||||
super(id);
|
super();
|
||||||
this.creation_time = creation_time;
|
this._collectionString = 'motions/motion-change-recommendation';
|
||||||
|
this.id = id;
|
||||||
|
this.motion_version_id = motion_version_id;
|
||||||
|
this.rejected = rejected;
|
||||||
|
this.type = type;
|
||||||
|
this.other_description = other_description;
|
||||||
this.line_from = line_from;
|
this.line_from = line_from;
|
||||||
this.line_to = line_to;
|
this.line_to = line_to;
|
||||||
this.motion_version_id = motion_version_id;
|
|
||||||
this.other_description = other_description;
|
|
||||||
this.rejected = rejected;
|
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.type = type;
|
this.creation_time = creation_time;
|
||||||
}
|
|
||||||
|
|
||||||
public getCollectionString(): string {
|
|
||||||
return MotionChangeReco.collectionString;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,70 +1,75 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of Motion.
|
||||||
|
*
|
||||||
|
* Untouched for now because of heavy maintainance on server side
|
||||||
|
*
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class Motion extends BaseModel {
|
export class Motion extends BaseModel {
|
||||||
static collectionString = 'motions/motion';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
active_version: number;
|
|
||||||
agenda_item_id: number;
|
|
||||||
attachments_id: number[];
|
|
||||||
category_id: number;
|
|
||||||
comments: Object;
|
|
||||||
identifier: string;
|
identifier: string;
|
||||||
log_messages: Object[];
|
versions: Object[];
|
||||||
|
active_version: number;
|
||||||
|
parent_id: number;
|
||||||
|
category_id: number;
|
||||||
motion_block_id: number;
|
motion_block_id: number;
|
||||||
origin: string;
|
origin: string;
|
||||||
parent_id: number;
|
|
||||||
polls: BaseModel[];
|
|
||||||
recommendation_id: number;
|
|
||||||
state_id: number;
|
|
||||||
state_required_permission_to_see: string;
|
|
||||||
submitters: Object[];
|
submitters: Object[];
|
||||||
supporters_id: number[];
|
supporters_id: number[];
|
||||||
|
comments: Object;
|
||||||
|
state_id: number;
|
||||||
|
state_required_permission_to_see: string;
|
||||||
|
recommendation_id: number;
|
||||||
tags_id: number[];
|
tags_id: number[];
|
||||||
versions: Object[];
|
attachments_id: number[];
|
||||||
|
polls: BaseModel[];
|
||||||
|
agenda_item_id: number;
|
||||||
|
log_messages: Object[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id?: number,
|
||||||
active_version?: number,
|
|
||||||
agenda_item_id?: number,
|
|
||||||
attachments_id?: number[],
|
|
||||||
category_id?: number,
|
|
||||||
comments?: Object,
|
|
||||||
identifier?: string,
|
identifier?: string,
|
||||||
log_messages?: Object[],
|
versions?: Object[],
|
||||||
|
active_version?: number,
|
||||||
|
parent_id?: number,
|
||||||
|
category_id?: number,
|
||||||
motion_block_id?: number,
|
motion_block_id?: number,
|
||||||
origin?: string,
|
origin?: string,
|
||||||
parent_id?: number,
|
|
||||||
polls?: BaseModel[],
|
|
||||||
recommendation_id?: number,
|
|
||||||
state_id?: number,
|
|
||||||
state_required_permission_to_see?: string,
|
|
||||||
submitters?: Object[],
|
submitters?: Object[],
|
||||||
supporters_id?: number[],
|
supporters_id?: number[],
|
||||||
|
comments?: Object,
|
||||||
|
state_id?: number,
|
||||||
|
state_required_permission_to_see?: string,
|
||||||
|
recommendation_id?: number,
|
||||||
tags_id?: number[],
|
tags_id?: number[],
|
||||||
versions?: Object[]
|
attachments_id?: number[],
|
||||||
|
polls?: BaseModel[],
|
||||||
|
agenda_item_id?: number,
|
||||||
|
log_messages?: Object[]
|
||||||
) {
|
) {
|
||||||
super(id);
|
super();
|
||||||
this.active_version = active_version;
|
this._collectionString = 'motions/motion';
|
||||||
this.agenda_item_id = agenda_item_id;
|
this.id = id;
|
||||||
this.attachments_id = attachments_id;
|
|
||||||
this.category_id = category_id;
|
|
||||||
this.comments = comments;
|
|
||||||
this.identifier = identifier;
|
this.identifier = identifier;
|
||||||
this.log_messages = log_messages;
|
this.versions = versions;
|
||||||
|
this.active_version = active_version;
|
||||||
|
this.parent_id = parent_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.parent_id = parent_id;
|
|
||||||
this.polls = polls;
|
|
||||||
this.recommendation_id = recommendation_id;
|
|
||||||
this.state_id = state_id;
|
|
||||||
this.state_required_permission_to_see = state_required_permission_to_see;
|
|
||||||
this.submitters = submitters;
|
this.submitters = submitters;
|
||||||
this.supporters_id = supporters_id;
|
this.supporters_id = supporters_id;
|
||||||
|
this.comments = comments;
|
||||||
|
this.state_id = state_id;
|
||||||
|
this.state_required_permission_to_see = state_required_permission_to_see;
|
||||||
|
this.recommendation_id = recommendation_id;
|
||||||
this.tags_id = tags_id;
|
this.tags_id = tags_id;
|
||||||
this.versions = versions;
|
this.attachments_id = attachments_id;
|
||||||
}
|
this.polls = polls;
|
||||||
|
this.agenda_item_id = agenda_item_id;
|
||||||
public getCollectionString(): string {
|
this.log_messages = log_messages;
|
||||||
return Motion.collectionString;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
86
client/src/app/core/models/motions/workflow-state.ts
Normal file
86
client/src/app/core/models/motions/workflow-state.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { Deserializable } from '../deserializable.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a workflow state
|
||||||
|
*
|
||||||
|
* Part of the 'states'-array in motion/workflow
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export class WorkflowState implements Deserializable {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
action_word: string;
|
||||||
|
recommendation_label: string;
|
||||||
|
css_class: string;
|
||||||
|
required_permission_to_see: string;
|
||||||
|
allow_support: boolean;
|
||||||
|
allow_create_poll: boolean;
|
||||||
|
allow_submitter_edit: boolean;
|
||||||
|
versioning: boolean;
|
||||||
|
leave_old_version_active: boolean;
|
||||||
|
dont_set_identifier: boolean;
|
||||||
|
show_state_extension_field: boolean;
|
||||||
|
show_recommendation_extension_field: boolean;
|
||||||
|
next_states_id: number[];
|
||||||
|
workflow_id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needs to be completely optional because Workflow has (yet) the optional parameter 'states'
|
||||||
|
* @param id
|
||||||
|
* @param name
|
||||||
|
* @param action_word
|
||||||
|
* @param recommendation_label
|
||||||
|
* @param css_class
|
||||||
|
* @param required_permission_to_see
|
||||||
|
* @param allow_support
|
||||||
|
* @param allow_create_poll
|
||||||
|
* @param allow_submitter_edit
|
||||||
|
* @param versioning
|
||||||
|
* @param leave_old_version_active
|
||||||
|
* @param dont_set_identifier
|
||||||
|
* @param show_state_extension_field
|
||||||
|
* @param show_recommendation_extension_field
|
||||||
|
* @param next_states_id
|
||||||
|
* @param workflow_id
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
id?: number,
|
||||||
|
name?: string,
|
||||||
|
action_word?: string,
|
||||||
|
recommendation_label?: string,
|
||||||
|
css_class?: string,
|
||||||
|
required_permission_to_see?: string,
|
||||||
|
allow_support?: boolean,
|
||||||
|
allow_create_poll?: boolean,
|
||||||
|
allow_submitter_edit?: boolean,
|
||||||
|
versioning?: boolean,
|
||||||
|
leave_old_version_active?: boolean,
|
||||||
|
dont_set_identifier?: boolean,
|
||||||
|
show_state_extension_field?: boolean,
|
||||||
|
show_recommendation_extension_field?: boolean,
|
||||||
|
next_states_id?: number[],
|
||||||
|
workflow_id?: number
|
||||||
|
) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.action_word = action_word;
|
||||||
|
this.recommendation_label = recommendation_label;
|
||||||
|
this.css_class = css_class;
|
||||||
|
this.required_permission_to_see = required_permission_to_see;
|
||||||
|
this.allow_support = allow_support;
|
||||||
|
this.allow_create_poll = allow_create_poll;
|
||||||
|
this.allow_submitter_edit = allow_submitter_edit;
|
||||||
|
this.versioning = versioning;
|
||||||
|
this.leave_old_version_active = leave_old_version_active;
|
||||||
|
this.dont_set_identifier = dont_set_identifier;
|
||||||
|
this.show_state_extension_field = show_state_extension_field;
|
||||||
|
this.show_recommendation_extension_field = show_recommendation_extension_field;
|
||||||
|
this.next_states_id = next_states_id;
|
||||||
|
this.workflow_id = workflow_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize(input: any): this {
|
||||||
|
Object.assign(this, input);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,34 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
import { WorkflowState } from './workflow-state';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a motion workflow. Has the nested property 'states'
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class Workflow extends BaseModel {
|
export class Workflow extends BaseModel {
|
||||||
static collectionString = 'motions/workflow';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
first_state: number;
|
|
||||||
name: string;
|
name: string;
|
||||||
states: Object[];
|
states: WorkflowState[];
|
||||||
|
first_state: number;
|
||||||
|
|
||||||
constructor(id: number, first_state?, name?, states?) {
|
constructor(id?: number, name?: string, states?: WorkflowState[], first_state?: number) {
|
||||||
super(id);
|
super();
|
||||||
this.first_state = first_state;
|
this._collectionString = 'motions/workflow';
|
||||||
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.states = states;
|
this.states = states;
|
||||||
|
this.first_state = first_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
deserialize(input: any): this {
|
||||||
return Workflow.collectionString;
|
Object.assign(this, input);
|
||||||
|
if (input.states instanceof Array) {
|
||||||
|
this.states = [];
|
||||||
|
input.states.forEach(workflowStateData => {
|
||||||
|
this.states.push(new WorkflowState().deserialize(workflowStateData));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,32 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a topic.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class Topic extends BaseModel {
|
export class Topic extends BaseModel {
|
||||||
static collectionString = 'topics/topic';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
agenda_item_id: number;
|
|
||||||
attachments_id: number[];
|
|
||||||
text: string;
|
|
||||||
title: string;
|
title: string;
|
||||||
|
text: string;
|
||||||
|
attachments_id: number[];
|
||||||
|
agenda_item_id: number;
|
||||||
|
|
||||||
constructor(id: number, agenda_item_id?: number, attachments_id?: number[], text?: string, title?: string) {
|
constructor(id?: number, title?: string, text?: string, attachments_id?: number[], agenda_item_id?: number) {
|
||||||
super(id);
|
super();
|
||||||
this.agenda_item_id = agenda_item_id;
|
this._collectionString = 'topics/topic';
|
||||||
this.attachments_id = attachments_id;
|
this.id = id;
|
||||||
this.text = text;
|
|
||||||
this.title = title;
|
this.title = title;
|
||||||
|
this.text = text;
|
||||||
|
this.attachments_id = attachments_id;
|
||||||
|
this.agenda_item_id = agenda_item_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
getAttachments(): BaseModel | BaseModel[] {
|
||||||
return Topic.collectionString;
|
return this.DS.get('mediafiles/mediafile', ...this.attachments_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAgenda(): BaseModel | BaseModel[] {
|
||||||
|
return this.DS.get('agenda/item', this.agenda_item_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of user group.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class Group extends BaseModel {
|
export class Group extends BaseModel {
|
||||||
static collectionString = 'users/group';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
permissions: string[]; //TODO permissions could be an own model?
|
permissions: string[];
|
||||||
|
|
||||||
constructor(id: number, name?: string, permissions?: string[]) {
|
constructor(id?: number, name?: string, permissions?: string[]) {
|
||||||
super(id);
|
super();
|
||||||
|
this._collectionString = 'users/group';
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.permissions = permissions;
|
this.permissions = permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
|
||||||
return Group.collectionString;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of users personal note.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class PersonalNote extends BaseModel {
|
export class PersonalNote extends BaseModel {
|
||||||
static collectionString = 'users/personal-note';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
notes: Object;
|
|
||||||
user_id: number;
|
user_id: number;
|
||||||
|
notes: Object;
|
||||||
|
|
||||||
constructor(id: number, notes?: Object, user_id?: number) {
|
constructor(id?: number, user_id?: number, notes?: Object) {
|
||||||
super(id);
|
super();
|
||||||
this.notes = notes;
|
this._collectionString = 'users/personal-note';
|
||||||
|
this.id = id;
|
||||||
this.user_id = user_id;
|
this.user_id = user_id;
|
||||||
|
this.notes = notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
getUser(): BaseModel | BaseModel[] {
|
||||||
return PersonalNote.collectionString;
|
return this.DS.get('users/user', this.user_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { BaseModel } from 'app/core/models/base-model';
|
||||||
|
|
||||||
// import { DS } from 'app/core/services/DS.service';
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a user in contrast to the operator.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
export class User extends BaseModel {
|
export class User extends BaseModel {
|
||||||
static collectionString = 'users/user';
|
protected _collectionString: string;
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
title: string;
|
title: string;
|
||||||
@ -21,9 +23,8 @@ export class User extends BaseModel {
|
|||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
default_password: string;
|
default_password: string;
|
||||||
|
|
||||||
//default constructer with every possible optinal parameter for conventient usage
|
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id?: number,
|
||||||
username?: string,
|
username?: string,
|
||||||
title?: string,
|
title?: string,
|
||||||
first_name?: string,
|
first_name?: string,
|
||||||
@ -40,7 +41,9 @@ export class User extends BaseModel {
|
|||||||
is_active?: boolean,
|
is_active?: boolean,
|
||||||
default_password?: string
|
default_password?: string
|
||||||
) {
|
) {
|
||||||
super(id);
|
super();
|
||||||
|
this._collectionString = 'users/user';
|
||||||
|
this.id = id;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.first_name = first_name;
|
this.first_name = first_name;
|
||||||
@ -58,7 +61,7 @@ export class User extends BaseModel {
|
|||||||
this.default_password = default_password;
|
this.default_password = default_password;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCollectionString(): string {
|
getGroups(): BaseModel | BaseModel[] {
|
||||||
return User.collectionString;
|
return this.DS.get('users/group', ...this.groups_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,28 @@ import { map } from 'rxjs/operators/';
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export class PruningTranslationLoader implements TranslateLoader {
|
export class PruningTranslationLoader implements TranslateLoader {
|
||||||
|
/**
|
||||||
|
* Constructor to load the HttpClient
|
||||||
|
*
|
||||||
|
* @param http httpClient to load the translation files.
|
||||||
|
* @param prefix Path to the language files. Can be adjusted of needed
|
||||||
|
* @param suffix Suffix of the translation files. Usually '.json'.
|
||||||
|
*/
|
||||||
constructor(private http: HttpClient, private prefix: string = '/assets/i18n/', private suffix: string = '.json') {}
|
constructor(private http: HttpClient, private prefix: string = '/assets/i18n/', private suffix: string = '.json') {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a language file, stores the content, give it to the process function.
|
||||||
|
* @param lang language string (en, fr, de, ...)
|
||||||
|
*/
|
||||||
public getTranslation(lang: string): any {
|
public getTranslation(lang: string): any {
|
||||||
return this.http.get(`${this.prefix}${lang}${this.suffix}`).pipe(map((res: Object) => this.process(res)));
|
return this.http.get(`${this.prefix}${lang}${this.suffix}`).pipe(map((res: Object) => this.process(res)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent to display empty strings as a translation.
|
||||||
|
* Falls back to the default language or simply copy the content of the key.
|
||||||
|
* @param any the content of any language file.
|
||||||
|
*/
|
||||||
private process(object: any) {
|
private process(object: any) {
|
||||||
const newObject = {};
|
const newObject = {};
|
||||||
for (const key in object) {
|
for (const key in object) {
|
||||||
|
@ -1,145 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HttpClient, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
|
||||||
import { Observable, of, BehaviorSubject } from 'rxjs';
|
|
||||||
import { tap, map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { ImproperlyConfiguredError } from 'app/core/exceptions';
|
|
||||||
import { BaseModel, ModelId } from 'app/core/models/baseModel';
|
|
||||||
|
|
||||||
interface Collection {
|
|
||||||
[id: number]: BaseModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Storrage {
|
|
||||||
[collectionString: string]: Collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Todo: DRY. This is a copy from /authService. probably repository service necessary
|
|
||||||
const httpOptions = {
|
|
||||||
withCredentials: true,
|
|
||||||
headers: new HttpHeaders({
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class DataStoreService {
|
|
||||||
// needs to be static cause becauseusing dependency injection, services are unique for a scope.
|
|
||||||
private static store: Storrage = {};
|
|
||||||
// observe datastore to enable dynamic changes in models and view
|
|
||||||
private static dataStoreSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
|
||||||
|
|
||||||
// read one, multiple or all ID's from DataStore
|
|
||||||
// example: this.DS.get(User) || (User, 1) || (User, 1, 2) || (User, ...[1,2,3,4,5])
|
|
||||||
get(Type, ...ids: ModelId[]): BaseModel[] | BaseModel {
|
|
||||||
const collection: Collection = DataStoreService.store[Type.collectionString];
|
|
||||||
const models = [];
|
|
||||||
|
|
||||||
if (!collection) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ids.length === 0) {
|
|
||||||
return Object.values(collection);
|
|
||||||
} else {
|
|
||||||
ids.forEach(id => {
|
|
||||||
const model: BaseModel = collection[id];
|
|
||||||
models.push(model);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return models.length === 1 ? models[0] : models;
|
|
||||||
}
|
|
||||||
|
|
||||||
// print the whole store for debug purposes
|
|
||||||
printWhole(): void {
|
|
||||||
console.log('Everythin in DataStore: ', DataStoreService.store);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: type for callback function
|
|
||||||
// example: this.DS.filter(User, myUser => myUser.first_name === "Max")
|
|
||||||
filter(Type, callback): BaseModel[] {
|
|
||||||
let filterCollection = [];
|
|
||||||
const typeCollection = this.get(Type);
|
|
||||||
|
|
||||||
if (Array.isArray(typeCollection)) {
|
|
||||||
filterCollection = [...filterCollection, ...typeCollection];
|
|
||||||
} else {
|
|
||||||
filterCollection.push(typeCollection);
|
|
||||||
}
|
|
||||||
return filterCollection.filter(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add one or moultiple models to DataStore
|
|
||||||
// use spread operator ("...") for arrays
|
|
||||||
// example: this.DS.add(new User(1)) || (new User(2), new User(3)) || (arrayWithUsers)
|
|
||||||
add(...models: BaseModel[]): void {
|
|
||||||
models.forEach(model => {
|
|
||||||
const collectionString = model.getCollectionString();
|
|
||||||
if (!model.id) {
|
|
||||||
throw new ImproperlyConfiguredError('The model must have an id!');
|
|
||||||
} else if (collectionString === 'invalid-collection-string') {
|
|
||||||
throw new ImproperlyConfiguredError('Cannot save a BaseModel');
|
|
||||||
}
|
|
||||||
if (typeof DataStoreService.store[collectionString] === 'undefined') {
|
|
||||||
DataStoreService.store[collectionString] = {};
|
|
||||||
}
|
|
||||||
DataStoreService.store[collectionString][model.id] = model;
|
|
||||||
this.setObservable(model);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// removes one or moultiple models from DataStore
|
|
||||||
// use spread operator ("...") for arrays
|
|
||||||
// Type should be any BaseModel
|
|
||||||
// example: this.DS.remove(User, 1) || this.DS.remove(User, myUser.id, 3, 4)
|
|
||||||
remove(Type, ...ids: ModelId[]): void {
|
|
||||||
ids.forEach(id => {
|
|
||||||
if (DataStoreService.store[Type.collectionString]) {
|
|
||||||
delete DataStoreService.store[Type.collectionString][id];
|
|
||||||
console.log(`did remove "${id}" from Datastore "${Type.collectionString}"`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO remove the any there and in 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.getCollectionString() + '/', model, httpOptions).pipe(
|
|
||||||
tap(response => {
|
|
||||||
console.log('the response: ', response);
|
|
||||||
this.add(model);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// send a http request to the server to delete the given model
|
|
||||||
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.getCollectionString() + '/', model, httpOptions).pipe(
|
|
||||||
tap(response => {
|
|
||||||
console.log('the response: ', response);
|
|
||||||
this.remove(model, model.id);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getObservable(): Observable<any> {
|
|
||||||
return DataStoreService.dataStoreSubject.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private setObservable(value) {
|
|
||||||
DataStoreService.dataStoreSubject.next(value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
import { TestBed, inject } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { AlertService } from './alert.service';
|
|
||||||
|
|
||||||
describe('AlertService', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
providers: [AlertService]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', inject([AlertService], (service: AlertService) => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
}));
|
|
||||||
});
|
|
@ -1,38 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
import { Alert, AlertType } from 'app/core/models/alert';
|
|
||||||
|
|
||||||
/**TODO Drafted for now. Since the UI is not done yet, this might be replaced or disappear entirely.
|
|
||||||
* Furtermore, Material UI does not support these kinds of alerts
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class AlertService {
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
success(message: string): Alert {
|
|
||||||
return this.alert(AlertType.Success, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
error(message: string): Alert {
|
|
||||||
// console.log('this.alert : ', this.alert());
|
|
||||||
// this.alert(AlertType.Error, message);
|
|
||||||
return this.alert(AlertType.Error, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
info(message: string): Alert {
|
|
||||||
return this.alert(AlertType.Info, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
warn(message: string): Alert {
|
|
||||||
return this.alert(AlertType.Warning, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
alert(type: AlertType, message: string): Alert {
|
|
||||||
return <Alert>{ type: type, message: message };
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO fromHttpError() to generate alerts form http errors
|
|
||||||
}
|
|
@ -4,12 +4,32 @@ import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from
|
|||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { OperatorService } from './operator.service';
|
import { OperatorService } from './operator.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classical Auth-Guard. Checks if the user has to correct permissions to enter a page, and forwards to login if not.
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AuthGuard implements CanActivate {
|
export class AuthGuard implements CanActivate {
|
||||||
|
/**
|
||||||
|
* Initialises the authentication, the operator and the Router
|
||||||
|
* @param authService
|
||||||
|
* @param operator
|
||||||
|
* @param router
|
||||||
|
*/
|
||||||
constructor(private authService: AuthService, private operator: OperatorService, private router: Router) {}
|
constructor(private authService: AuthService, private operator: OperatorService, private router: Router) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks of the operator has n id.
|
||||||
|
* If so, forward to the desired target.
|
||||||
|
*
|
||||||
|
* If not, forward to login.
|
||||||
|
*
|
||||||
|
* TODO: Test if this works for guests and on Projector
|
||||||
|
*
|
||||||
|
* @param route required by `canActivate()`
|
||||||
|
* @param state the state (URL) that the user want to access
|
||||||
|
*/
|
||||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
|
||||||
const url: string = state.url;
|
const url: string = state.url;
|
||||||
|
|
||||||
|
@ -4,47 +4,62 @@ import { Observable, of, throwError } from 'rxjs';
|
|||||||
import { catchError, tap } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { OperatorService } from 'app/core/services/operator.service';
|
import { OperatorService } from 'app/core/services/operator.service';
|
||||||
|
import { OpenSlidesComponent } from '../../openslides.component';
|
||||||
|
|
||||||
const httpOptions = {
|
/**
|
||||||
withCredentials: true,
|
* Authenticates an OpenSlides user with username and password
|
||||||
headers: new HttpHeaders({
|
*/
|
||||||
'Content-Type': 'application/json'
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService extends OpenSlidesComponent {
|
||||||
|
/**
|
||||||
|
* if the user tries to access a certain URL without being authenticated, the URL will be stored here
|
||||||
|
*/
|
||||||
redirectUrl: string;
|
redirectUrl: string;
|
||||||
|
|
||||||
constructor(private http: HttpClient, private operator: OperatorService) {}
|
/**
|
||||||
|
* Initializes the httpClient and the {@link OperatorService}.
|
||||||
|
*
|
||||||
|
* Calls `super()` from the parent class.
|
||||||
|
* @param http HttpClient
|
||||||
|
* @param operator who is using OpenSlides
|
||||||
|
*/
|
||||||
|
constructor(private http: HttpClient, private operator: OperatorService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
//loggins a users. expects a user model
|
/**
|
||||||
|
* Try to log in a user.
|
||||||
|
*
|
||||||
|
* Returns an observable 'user' with the correct login information or an error.
|
||||||
|
* The user will then be stored in the {@link OperatorService},
|
||||||
|
* errors will be forwarded to the parents error function.
|
||||||
|
*
|
||||||
|
* @param username
|
||||||
|
* @param password
|
||||||
|
*/
|
||||||
login(username: string, password: string): Observable<any> {
|
login(username: string, password: string): Observable<any> {
|
||||||
const user: any = {
|
const user: any = {
|
||||||
username: username,
|
username: username,
|
||||||
password: password
|
password: password
|
||||||
};
|
};
|
||||||
return this.http.post<any>('/users/login/', user, httpOptions).pipe(
|
return this.http.post<any>('/users/login/', user).pipe(
|
||||||
tap(resp => this.operator.storeUser(resp.user)),
|
tap(resp => this.operator.storeUser(resp.user)),
|
||||||
catchError(this.handleError())
|
catchError(this.handleError())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout function for both the client and the server.
|
||||||
|
*
|
||||||
|
* Will clear the current {@link OperatorService} and
|
||||||
|
* send a `post`-requiest to `/users/logout/'`
|
||||||
|
*/
|
||||||
//logout the user
|
//logout the user
|
||||||
//TODO not yet used
|
//TODO not yet used
|
||||||
logout(): Observable<any> {
|
logout(): Observable<any> {
|
||||||
this.operator.clear();
|
this.operator.clear();
|
||||||
return this.http.post<any>('/users/logout/', {}, httpOptions);
|
return this.http.post<any>('/users/logout/', {});
|
||||||
}
|
|
||||||
|
|
||||||
//very generic error handling function.
|
|
||||||
//implicitly returns an observable that will display an error message
|
|
||||||
private handleError<T>() {
|
|
||||||
return (error: any): Observable<T> => {
|
|
||||||
console.error(error);
|
|
||||||
return of(error);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { BaseComponent } from 'app/base.component';
|
|
||||||
import { WebsocketService } from './websocket.service';
|
|
||||||
|
|
||||||
import { BaseModel } from 'app/core/models/baseModel';
|
import { OpenSlidesComponent } from 'app/openslides.component';
|
||||||
|
import { WebsocketService } from './websocket.service';
|
||||||
|
// the Models
|
||||||
import { Item } from 'app/core/models/agenda/item';
|
import { Item } from 'app/core/models/agenda/item';
|
||||||
import { Assignment } from 'app/core/models/assignments/assignment';
|
import { Assignment } from 'app/core/models/assignments/assignment';
|
||||||
import { ChatMessage } from 'app/core/models/core/chat-message';
|
import { ChatMessage } from 'app/core/models/core/chat-message';
|
||||||
@ -23,109 +23,118 @@ import { PersonalNote } from 'app/core/models/users/personal-note';
|
|||||||
import { User } from 'app/core/models/users/user';
|
import { User } from 'app/core/models/users/user';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basically handles the inital update and all automatic updates.
|
* Handles the initial update and automatic updates using the {@link WebsocketService}
|
||||||
|
* Incoming objects, usually BaseModels, will be saved in the dataStore (`this.DS`)
|
||||||
|
* This service usually creates all models
|
||||||
|
*
|
||||||
|
* The dataStore will injected over the parent class: {@link OpenSlidesComponent}.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AutoupdateService extends BaseComponent {
|
export class AutoupdateService extends OpenSlidesComponent {
|
||||||
|
/**
|
||||||
|
* Stores the to create the socket created using {@link WebsocketService}.
|
||||||
|
*/
|
||||||
private socket;
|
private socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor to create the AutoupdateService. Calls the constructor of the parent class.
|
||||||
|
* @param websocketService
|
||||||
|
*/
|
||||||
constructor(private websocketService: WebsocketService) {
|
constructor(private websocketService: WebsocketService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialte autpupdate Service
|
/**
|
||||||
|
* Function to start the automatic update process
|
||||||
|
* will build up a websocket connection using {@link WebsocketService}
|
||||||
|
*/
|
||||||
startAutoupdate(): void {
|
startAutoupdate(): void {
|
||||||
console.log('start autoupdate');
|
|
||||||
this.socket = this.websocketService.connect();
|
this.socket = this.websocketService.connect();
|
||||||
this.socket.subscribe(response => {
|
this.socket.subscribe(response => {
|
||||||
this.storeResponse(response);
|
this.storeResponse(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// create models out of socket answer
|
/**
|
||||||
|
* Handle the answer of incoming data via {@link WebsocketService}.
|
||||||
|
*
|
||||||
|
* Detects the Class of an incomming model, creates a new empty object and assigns
|
||||||
|
* the data to it using the deserialize function.
|
||||||
|
*
|
||||||
|
* Saves models in DataStore.
|
||||||
|
*/
|
||||||
storeResponse(socketResponse): void {
|
storeResponse(socketResponse): void {
|
||||||
socketResponse.forEach(model => {
|
socketResponse.forEach(jsonObj => {
|
||||||
switch (model.collection) {
|
const targetClass = this.getClassFromCollectionString(jsonObj.collection);
|
||||||
case 'core/projector': {
|
this.DS.add(new targetClass().deserialize(jsonObj.data));
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, Projector));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'core/chat-message': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, ChatMessage));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'core/tag': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, Tag));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'core/projector-message': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, ProjectorMessage));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'core/countdown': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, Countdown));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'core/config': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, Config));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'users/user': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, User));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'users/group': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, Group));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'users/personal-note': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, PersonalNote));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'agenda/item': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, Item));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'topics/topic': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, Topic));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'motions/category': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, Category));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'motions/motion': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, Motion));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'motions/motion-block': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, MotionBlock));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'motions/workflow': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, Workflow));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'motions/motion-change-recommendation': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, MotionChangeReco));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'assignments/assignment': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, Assignment));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'mediafiles/mediafile': {
|
|
||||||
this.DS.add(BaseModel.fromJSON(model.data, Mediafile));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
console.error('No rule for ', model.collection, '\n object was: ', model);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper function to return the correct class from a collection string
|
||||||
|
*/
|
||||||
|
getClassFromCollectionString(collection: string): any {
|
||||||
|
switch (collection) {
|
||||||
|
case 'core/projector': {
|
||||||
|
return Projector;
|
||||||
|
}
|
||||||
|
case 'core/chat-message': {
|
||||||
|
return ChatMessage;
|
||||||
|
}
|
||||||
|
case 'core/tag': {
|
||||||
|
return Tag;
|
||||||
|
}
|
||||||
|
case 'core/projector-message': {
|
||||||
|
return ProjectorMessage;
|
||||||
|
}
|
||||||
|
case 'core/countdown': {
|
||||||
|
return Countdown;
|
||||||
|
}
|
||||||
|
case 'core/config': {
|
||||||
|
return Config;
|
||||||
|
}
|
||||||
|
case 'users/user': {
|
||||||
|
return User;
|
||||||
|
}
|
||||||
|
case 'users/group': {
|
||||||
|
return Group;
|
||||||
|
}
|
||||||
|
case 'users/personal-note': {
|
||||||
|
return PersonalNote;
|
||||||
|
}
|
||||||
|
case 'agenda/item': {
|
||||||
|
return Item;
|
||||||
|
}
|
||||||
|
case 'topics/topic': {
|
||||||
|
return Topic;
|
||||||
|
}
|
||||||
|
case 'motions/category': {
|
||||||
|
return Category;
|
||||||
|
}
|
||||||
|
case 'motions/motion': {
|
||||||
|
return Motion;
|
||||||
|
}
|
||||||
|
case 'motions/motion-block': {
|
||||||
|
return MotionBlock;
|
||||||
|
}
|
||||||
|
case 'motions/workflow': {
|
||||||
|
return Workflow;
|
||||||
|
}
|
||||||
|
case 'motions/motion-change-recommendation': {
|
||||||
|
return MotionChangeReco;
|
||||||
|
}
|
||||||
|
case 'assignments/assignment': {
|
||||||
|
return Assignment;
|
||||||
|
}
|
||||||
|
case 'mediafiles/mediafile': {
|
||||||
|
return Mediafile;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
console.error('No rule for ', collection);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { TestBed, inject } from '@angular/core/testing';
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
import { DataStoreService } from './DS.service';
|
import { DataStoreService } from './dataStore.service';
|
||||||
|
|
||||||
describe('DS', () => {
|
describe('DS', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -8,9 +8,4 @@ describe('DS', () => {
|
|||||||
providers: [DataStoreService]
|
providers: [DataStoreService]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/*it('should be created', inject([DSService], (DS: DSService) => {
|
|
||||||
expect(DS).toBeTruthy();
|
|
||||||
}));*/
|
|
||||||
// just a static use
|
|
||||||
});
|
});
|
211
client/src/app/core/services/dataStore.service.ts
Normal file
211
client/src/app/core/services/dataStore.service.ts
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
||||||
|
import { Observable, of, BehaviorSubject } from 'rxjs';
|
||||||
|
import { tap, map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { ImproperlyConfiguredError } from 'app/core/exceptions';
|
||||||
|
import { BaseModel, ModelId } from 'app/core/models/base-model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represents a collection on the Django server, uses an ID to access a {@link BaseModel}.
|
||||||
|
*
|
||||||
|
* Part of {@link DataStoreService}
|
||||||
|
*/
|
||||||
|
interface Collection {
|
||||||
|
[id: number]: BaseModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual storage that stores collections, accessible by strings.
|
||||||
|
*
|
||||||
|
* {@link DataStoreService}
|
||||||
|
*/
|
||||||
|
interface Storage {
|
||||||
|
[collectionString: string]: Collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All mighty DataStore that comes with all OpenSlides components.
|
||||||
|
* Use this.DS in an OpenSlides Component to Access the store.
|
||||||
|
* Used by a lot of components, classes and services.
|
||||||
|
* Changes can be observed
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class DataStoreService {
|
||||||
|
/**
|
||||||
|
* Dependency injection, services are singletons 'per scope' and not per app anymore.
|
||||||
|
* There will be multiple DataStores, all of them should share the same storage object
|
||||||
|
*/
|
||||||
|
private static store: Storage = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable subject with changes to enable dynamic changes in models and views
|
||||||
|
*/
|
||||||
|
private static dataStoreSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty constructor for dataStore
|
||||||
|
* @param http use HttpClient to send models back to the server
|
||||||
|
*/
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read one, multiple or all ID's from dataStore
|
||||||
|
* @param collectionType The desired BaseModel or collectionString to be read from the dataStore
|
||||||
|
* @param ids An or multiple IDs or a list of IDs of BaseModel
|
||||||
|
* @return The BaseModel-list corresponding to the given ID(s)
|
||||||
|
* @example: this.DS.get(User) returns all users
|
||||||
|
* @example: this.DS.get(User, 1)
|
||||||
|
* @example: this.DS.get(User, ...[1,2,3,4,5])
|
||||||
|
* @example: this.DS.get(/core/countdown, 1)
|
||||||
|
*/
|
||||||
|
get(collectionType, ...ids: ModelId[]): BaseModel[] | BaseModel {
|
||||||
|
let collectionString: string;
|
||||||
|
if (typeof collectionType === 'string') {
|
||||||
|
collectionString = collectionType;
|
||||||
|
} else {
|
||||||
|
//get the collection string by making an empty object
|
||||||
|
const tempObject = new collectionType();
|
||||||
|
collectionString = tempObject.collectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collection: Collection = DataStoreService.store[collectionString];
|
||||||
|
|
||||||
|
const models = [];
|
||||||
|
if (!collection) {
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ids.length === 0) {
|
||||||
|
return Object.values(collection);
|
||||||
|
} else {
|
||||||
|
ids.forEach(id => {
|
||||||
|
const model: BaseModel = collection[id];
|
||||||
|
models.push(model);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return models.length === 1 ? models[0] : models;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints the whole dataStore
|
||||||
|
*/
|
||||||
|
printWhole(): void {
|
||||||
|
console.log('Everything in DataStore: ', DataStoreService.store);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the dataStore by type
|
||||||
|
* @param Type The desired BaseModel type to be read from the dataStore
|
||||||
|
* @param callback a filter function
|
||||||
|
* @return The BaseModel-list corresponding to the filter function
|
||||||
|
* @example this.DS.filter(User, myUser => myUser.first_name === "Max")
|
||||||
|
*/
|
||||||
|
filter(Type, callback): BaseModel[] {
|
||||||
|
// TODO: type for callback function
|
||||||
|
let filterCollection = [];
|
||||||
|
const typeCollection = this.get(Type);
|
||||||
|
|
||||||
|
if (Array.isArray(typeCollection)) {
|
||||||
|
filterCollection = [...filterCollection, ...typeCollection];
|
||||||
|
} else {
|
||||||
|
filterCollection.push(typeCollection);
|
||||||
|
}
|
||||||
|
return filterCollection.filter(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add one or multiple models to dataStore
|
||||||
|
* @param ...models The model(s) that shall be add use spread operator ("...")
|
||||||
|
* @example this.DS.add(new User(1))
|
||||||
|
* @example this.DS.add((new User(2), new User(3)))
|
||||||
|
* @example this.DS.add(...arrayWithUsers)
|
||||||
|
*/
|
||||||
|
add(...models: BaseModel[]): void {
|
||||||
|
models.forEach(model => {
|
||||||
|
const collectionString = model.collectionString;
|
||||||
|
if (!model.id) {
|
||||||
|
throw new ImproperlyConfiguredError('The model must have an id!');
|
||||||
|
} else if (collectionString === 'invalid-collection-string') {
|
||||||
|
throw new ImproperlyConfiguredError('Cannot save a BaseModel');
|
||||||
|
}
|
||||||
|
if (typeof DataStoreService.store[collectionString] === 'undefined') {
|
||||||
|
DataStoreService.store[collectionString] = {};
|
||||||
|
}
|
||||||
|
DataStoreService.store[collectionString][model.id] = model;
|
||||||
|
this.setObservable(model);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* removes one or multiple models from dataStore
|
||||||
|
* @param Type The desired BaseModel type to be read from the dataStore
|
||||||
|
* @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)
|
||||||
|
*/
|
||||||
|
remove(Type, ...ids: ModelId[]): void {
|
||||||
|
ids.forEach(id => {
|
||||||
|
const tempObject = new Type();
|
||||||
|
if (DataStoreService.store[tempObject.collectionString]) {
|
||||||
|
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.
|
||||||
|
* @return an observable behaviorSubject
|
||||||
|
*/
|
||||||
|
public getObservable(): Observable<any> {
|
||||||
|
return DataStoreService.dataStoreSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the observers for changes
|
||||||
|
* @param value the change that have been made
|
||||||
|
*/
|
||||||
|
private setObservable(value): void {
|
||||||
|
DataStoreService.dataStoreSubject.next(value);
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +0,0 @@
|
|||||||
import { TestBed, inject } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { OpenslidesService } from './openslides.service';
|
|
||||||
|
|
||||||
describe('OpenslidesService', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
providers: [OpenslidesService]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', inject([OpenslidesService], (service: OpenslidesService) => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
}));
|
|
||||||
});
|
|
@ -1,24 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { AuthService } from './auth.service';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class OpenslidesService {
|
|
||||||
constructor(private auth: AuthService, private router: Router) {}
|
|
||||||
|
|
||||||
// now in authService
|
|
||||||
// bootup() {
|
|
||||||
// // TODO Lock the interface..
|
|
||||||
// this.auth.init().subscribe(whoami => {
|
|
||||||
// console.log(whoami);
|
|
||||||
// if (!whoami.user && !whoami.guest_enabled) {
|
|
||||||
// this.router.navigate(['/login']);
|
|
||||||
// } else {
|
|
||||||
// // It's ok!
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
|
@ -1,23 +1,25 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable, of, BehaviorSubject } from 'rxjs';
|
import { Observable, BehaviorSubject } from 'rxjs';
|
||||||
import { HttpClient, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { tap, catchError, share } from 'rxjs/operators';
|
import { tap, catchError, share } from 'rxjs/operators';
|
||||||
import { BaseComponent } from 'app/base.component';
|
import { OpenSlidesComponent } from 'app/openslides.component';
|
||||||
import { Group } from 'app/core/models/users/group';
|
import { Group } from 'app/core/models/users/group';
|
||||||
|
|
||||||
// TODO: Dry
|
/**
|
||||||
const httpOptions = {
|
* The operator represents the user who is using OpenSlides.
|
||||||
withCredentials: true,
|
*
|
||||||
headers: new HttpHeaders({
|
* Information is mostly redundant to user but has different purposes.
|
||||||
'Content-Type': 'application/json'
|
* Changes in operator can be observed, directives do so on order to show
|
||||||
})
|
* or hide certain information.
|
||||||
};
|
*
|
||||||
|
* Could extend User?
|
||||||
|
*
|
||||||
|
* The operator is an {@link OpenSlidesComponent}.
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class OperatorService extends BaseComponent {
|
export class OperatorService extends OpenSlidesComponent {
|
||||||
// default variables
|
|
||||||
about_me: string;
|
about_me: string;
|
||||||
comment: string;
|
comment: string;
|
||||||
default_password: string;
|
default_password: string;
|
||||||
@ -35,11 +37,23 @@ export class OperatorService extends BaseComponent {
|
|||||||
title: string;
|
title: string;
|
||||||
username: string;
|
username: string;
|
||||||
logged_in: boolean;
|
logged_in: boolean;
|
||||||
// subject
|
|
||||||
|
/**
|
||||||
|
* The subject that can be observed by other instances using observing functions.
|
||||||
|
*/
|
||||||
private operatorSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
private operatorSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
||||||
// real groups, once they arrived in datastore
|
|
||||||
|
/**
|
||||||
|
* Representation of the {@link Group}s that the operator has (in contrast the the `groups_id`-Array)
|
||||||
|
*
|
||||||
|
* The operator observes the dataStore (compare {@link OpenSlidesComponent} in Order to know it's groups)
|
||||||
|
*/
|
||||||
private groups: Group[] = new Array();
|
private groups: Group[] = new Array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreates the operator from localStorage if it's found and starts to observe the dataStore.
|
||||||
|
* @param http HttpClient
|
||||||
|
*/
|
||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@ -56,9 +70,11 @@ export class OperatorService extends BaseComponent {
|
|||||||
this.observeDataStore();
|
this.observeDataStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
// calls 'whoami' to find out the operator
|
/**
|
||||||
|
* calls `/users/whoami` to find out the real operator
|
||||||
|
*/
|
||||||
public whoAmI(): Observable<any> {
|
public whoAmI(): Observable<any> {
|
||||||
return this.http.get<any>('/users/whoami/', httpOptions).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);
|
||||||
@ -68,6 +84,10 @@ export class OperatorService extends BaseComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the user Information in the operator, the localStorage and update the Observable
|
||||||
|
* @param user usually a http response that represents a user.
|
||||||
|
*/
|
||||||
public storeUser(user: any): void {
|
public storeUser(user: any): void {
|
||||||
// store in file
|
// store in file
|
||||||
this.about_me = user.about_me;
|
this.about_me = user.about_me;
|
||||||
@ -87,11 +107,18 @@ export class OperatorService extends BaseComponent {
|
|||||||
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.updateLocalStorrage();
|
this.updateLocalStorage();
|
||||||
// update mode to inform observers
|
// update mode to inform observers
|
||||||
this.updateMode();
|
this.setObservable(this.getUpdateObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all stored information about the Operator.
|
||||||
|
*
|
||||||
|
* The Opposite of StoreUser. Usually a `logout()`-function.
|
||||||
|
* Also removes the operator from localStorrage and
|
||||||
|
* updates the observable.
|
||||||
|
*/
|
||||||
public clear() {
|
public clear() {
|
||||||
this.about_me = null;
|
this.about_me = null;
|
||||||
this.comment = null;
|
this.comment = null;
|
||||||
@ -109,19 +136,25 @@ export class OperatorService extends BaseComponent {
|
|||||||
this.structure_level = null;
|
this.structure_level = null;
|
||||||
this.title = null;
|
this.title = null;
|
||||||
this.username = null;
|
this.username = null;
|
||||||
this.updateMode();
|
this.setObservable(this.getUpdateObject());
|
||||||
localStorage.removeItem('operator');
|
localStorage.removeItem('operator');
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateLocalStorrage(): void {
|
/**
|
||||||
|
* Saves the operator in the localStorage for easier and faster re-login
|
||||||
|
*
|
||||||
|
* This is a mere comfort feature, even if the operator can be recreated
|
||||||
|
* it has to pass `this.whoAmI()` during page access.
|
||||||
|
*/
|
||||||
|
private updateLocalStorage(): void {
|
||||||
localStorage.setItem('operator', JSON.stringify(this.getUpdateObject()));
|
localStorage.setItem('operator', JSON.stringify(this.getUpdateObject()));
|
||||||
console.log('update local storrage: groups: ', this.groups_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateMode(): void {
|
|
||||||
this.setObservable(this.getUpdateObject());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current operator.
|
||||||
|
*
|
||||||
|
* Used to save the operator in localStorage or inform observers.
|
||||||
|
*/
|
||||||
private getUpdateObject(): any {
|
private getUpdateObject(): any {
|
||||||
return {
|
return {
|
||||||
about_me: this.about_me,
|
about_me: this.about_me,
|
||||||
@ -144,10 +177,12 @@ export class OperatorService extends BaseComponent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// observe dataStore to set groups once they are there
|
/**
|
||||||
// TODO logic to remove groups / user from certain groups
|
* Observe dataStore to set groups once they are loaded.
|
||||||
|
*
|
||||||
|
* TODO logic to remove groups / user from certain groups. Currently is is only set and was never removed
|
||||||
|
*/
|
||||||
private observeDataStore(): void {
|
private observeDataStore(): void {
|
||||||
console.log('Operator observes DataStore');
|
|
||||||
this.DS.getObservable().subscribe(newModel => {
|
this.DS.getObservable().subscribe(newModel => {
|
||||||
if (newModel instanceof Group) {
|
if (newModel instanceof Group) {
|
||||||
this.addGroup(newModel);
|
this.addGroup(newModel);
|
||||||
@ -155,9 +190,15 @@ export class OperatorService extends BaseComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// read out the Groups from the DataStore by the operators 'groups_id'
|
/**
|
||||||
// requires that the DataStore has been setup (websocket.service)
|
* Read out the Groups from the DataStore by the operators 'groups_id'
|
||||||
// requires that the whoAmI did return a valid operator
|
*
|
||||||
|
* requires that the DataStore has been setup (websocket.service)
|
||||||
|
* requires that the whoAmI did return a valid operator
|
||||||
|
*
|
||||||
|
* This is the normal behavior after a fresh login, everythin else can
|
||||||
|
* be done by observers.
|
||||||
|
*/
|
||||||
public readGroupsFromStore(): void {
|
public readGroupsFromStore(): void {
|
||||||
this.DS.filter(Group, myGroup => {
|
this.DS.filter(Group, myGroup => {
|
||||||
if (this.groups_id.includes(myGroup.id)) {
|
if (this.groups_id.includes(myGroup.id)) {
|
||||||
@ -166,33 +207,40 @@ export class OperatorService extends BaseComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the behaviorSubject as an observable.
|
||||||
|
*
|
||||||
|
* Services an components can use it to get informed when something changes in
|
||||||
|
* the operator
|
||||||
|
*/
|
||||||
public getObservable() {
|
public getObservable() {
|
||||||
return this.operatorSubject.asObservable();
|
return this.operatorSubject.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform all observers about changes
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
private setObservable(value) {
|
private setObservable(value) {
|
||||||
this.operatorSubject.next(value);
|
this.operatorSubject.next(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the (real) {@link Group}s
|
||||||
|
*/
|
||||||
public getGroups() {
|
public getGroups() {
|
||||||
return this.groups;
|
return this.groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the operator has the corresponding ID, set the group
|
/**
|
||||||
|
* if the operator has the corresponding ID, set the group
|
||||||
|
* @param newGroup potential group that the operator has.
|
||||||
|
*/
|
||||||
private addGroup(newGroup: Group): void {
|
private addGroup(newGroup: Group): void {
|
||||||
if (this.groups_id.includes(newGroup.id)) {
|
if (this.groups_id.includes(newGroup.id as number)) {
|
||||||
this.groups.push(newGroup);
|
this.groups.push(newGroup);
|
||||||
// inform the observers about new groups (appOsPerms)
|
// inform the observers about new groups (appOsPerms)
|
||||||
console.log('pushed a group into operator');
|
|
||||||
this.setObservable(newGroup);
|
this.setObservable(newGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Dry
|
|
||||||
private handleError<T>() {
|
|
||||||
return (error: any): Observable<T> => {
|
|
||||||
console.error(error);
|
|
||||||
return of(error);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
import { TestBed, inject } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { ToastService } from './toast.service';
|
|
||||||
|
|
||||||
describe('ToastService', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
providers: [ToastService]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', inject([ToastService], (service: ToastService) => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
}));
|
|
||||||
});
|
|
@ -1,46 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Observable, Subject } from 'rxjs';
|
|
||||||
|
|
||||||
import { Alert, AlertType } from 'app/core/models/alert';
|
|
||||||
|
|
||||||
/**TODO Drafted for now. Since the UI is not done yet, this might be replaced or disappear entirely.
|
|
||||||
* Furtermore, Material UI does not support these kinds of alerts
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class ToastService {
|
|
||||||
private subject = new Subject<Alert>();
|
|
||||||
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
getToast(): Observable<any> {
|
|
||||||
return this.subject.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
success(message: string) {
|
|
||||||
this.alert(AlertType.Success, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
error(message: string) {
|
|
||||||
this.alert(AlertType.Error, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
info(message: string) {
|
|
||||||
this.alert(AlertType.Info, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
warn(message: string) {
|
|
||||||
this.alert(AlertType.Warning, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
alert(type: AlertType, message: string) {
|
|
||||||
this.subject.next(<Alert>{ type: type, message: message });
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
// clear alerts
|
|
||||||
this.subject.next();
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,17 +2,33 @@ import { Injectable } from '@angular/core';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that handles WebSocket connections.
|
||||||
|
*
|
||||||
|
* Creates or returns already created WebSockets.
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class WebsocketService {
|
export class WebsocketService {
|
||||||
|
/**
|
||||||
|
* Constructor that handles the router
|
||||||
|
* @param router the URL Router
|
||||||
|
*/
|
||||||
constructor(private router: Router) {}
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
//might be any for simplicity or MessageEvent or something different
|
/**
|
||||||
|
* Observable subject that might be `any` for simplicity, `MessageEvent` or something appropriate
|
||||||
|
*/
|
||||||
private subject: WebSocketSubject<any>;
|
private subject: WebSocketSubject<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new WebSocket connection as WebSocketSubject
|
||||||
|
*
|
||||||
|
* Can return old Subjects to prevent multiple WebSocket connections.
|
||||||
|
*/
|
||||||
public connect(): WebSocketSubject<any> {
|
public connect(): WebSocketSubject<any> {
|
||||||
const socketProtocol = this.getWebSocketProtocoll();
|
const socketProtocol = this.getWebSocketProtocol();
|
||||||
const socketPath = this.getWebSocketPath();
|
const socketPath = this.getWebSocketPath();
|
||||||
const socketServer = window.location.hostname + ':' + window.location.port;
|
const socketServer = window.location.hostname + ':' + window.location.port;
|
||||||
if (!this.subject) {
|
if (!this.subject) {
|
||||||
@ -21,7 +37,9 @@ export class WebsocketService {
|
|||||||
return this.subject;
|
return this.subject;
|
||||||
}
|
}
|
||||||
|
|
||||||
// delegates to websockets for either the side or projector websocket
|
/**
|
||||||
|
* Delegates to socket-path for either the side or projector websocket.
|
||||||
|
*/
|
||||||
private getWebSocketPath(): string {
|
private getWebSocketPath(): string {
|
||||||
//currentRoute does not end with '/'
|
//currentRoute does not end with '/'
|
||||||
const currentRoute = this.router.url;
|
const currentRoute = this.router.url;
|
||||||
@ -32,8 +50,12 @@ export class WebsocketService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the websocket protocoll
|
/**
|
||||||
private getWebSocketProtocoll(): string {
|
* returns the desired websocket protocol
|
||||||
|
*
|
||||||
|
* TODO: HTTPS is not yet tested
|
||||||
|
*/
|
||||||
|
private getWebSocketProtocol(): string {
|
||||||
if (location.protocol === 'https') {
|
if (location.protocol === 'https') {
|
||||||
return 'wss://';
|
return 'wss://';
|
||||||
} else {
|
} else {
|
||||||
|
54
client/src/app/openslides.component.ts
Normal file
54
client/src/app/openslides.component.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { Injector } from '@angular/core';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
|
||||||
|
import { DataStoreService } from 'app/core/services/dataStore.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* injects the {@link DataStoreService} to all its children and provides a generic function to catch errors
|
||||||
|
* should be abstract and a mere parent to all {@link DataStoreService} accessors
|
||||||
|
*/
|
||||||
|
export abstract class OpenSlidesComponent {
|
||||||
|
/**
|
||||||
|
* To inject the {@link DataStoreService} into the children of OpenSlidesComponent
|
||||||
|
*/
|
||||||
|
protected injector: Injector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dataStore Service
|
||||||
|
*/
|
||||||
|
protected dataStore: DataStoreService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty constructor
|
||||||
|
*
|
||||||
|
* Static injection of {@link DataStoreService} in all child instances of OpenSlidesComponent
|
||||||
|
* Throws a warning even tho it is the new syntax. Ignored for now.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.injector = Injector.create([{ provide: DataStoreService, useClass: DataStoreService, deps: [] }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getter to access the {@link DataStoreService}
|
||||||
|
* @example this.DS.get(User)
|
||||||
|
* @return access to dataStoreService
|
||||||
|
*/
|
||||||
|
get DS(): DataStoreService {
|
||||||
|
if (this.dataStore == null) {
|
||||||
|
this.dataStore = this.injector.get(DataStoreService);
|
||||||
|
}
|
||||||
|
return this.dataStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic error handling for everything that makes HTTP Calls
|
||||||
|
* TODO: could have more features
|
||||||
|
* @return an observable error
|
||||||
|
*/
|
||||||
|
handleError<T>() {
|
||||||
|
return (error: any): Observable<T> => {
|
||||||
|
console.error(error);
|
||||||
|
return of(error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
3608
package-lock.json
generated
3608
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user