diff --git a/client/src/app/site/base-repository.ts b/client/src/app/site/base-repository.ts new file mode 100644 index 000000000..07f7af5e7 --- /dev/null +++ b/client/src/app/site/base-repository.ts @@ -0,0 +1,127 @@ +import { OpenSlidesComponent } from '../openslides.component'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { BaseViewModel } from './base-view-model'; +import { BaseModel, ModelConstructor } from '../shared/models/base.model'; +import { CollectionStringModelMapperService } from '../core/services/collectionStringModelMapper.service'; + +export abstract class BaseRepository extends OpenSlidesComponent { + /** + * Stores all the viewModel in an object + */ + protected viewModelStore: { [modelId: number]: V } = {}; + + /** + * Stores subjects to viewModels in a list + */ + protected viewModelSubjects: { [modelId: number]: BehaviorSubject } = {}; + + /** + * Observable subject for the whole list + */ + protected viewModelListSubject: BehaviorSubject = new BehaviorSubject(null); + + /** + * + * @param baseModelCtor The model constructor of which this repository is about. + * @param depsModelCtors A list of constructors that are used in the view model. + * If one of those changes, the view models will be updated. + */ + public constructor( + protected baseModelCtor: ModelConstructor, + protected depsModelCtors: ModelConstructor[] + ) { + super(); + + // Populate the local viewModelStore with ViewModel Objects. + this.DS.getAll(baseModelCtor).forEach((model: M) => { + this.viewModelStore[model.id] = this.createViewModel(model); + this.updateViewModelObservable(model.id); + }); + this.updateViewModelListObservable(); + + // Could be raise in error if the root injector is not known + this.DS.changeObservable.subscribe(model => { + if (model instanceof this.baseModelCtor) { + // Add new and updated motions to the viewModelStore + this.viewModelStore[model.id] = this.createViewModel(model as M); + this.updateAllObservables(model.id); + } else { + const dependencyChanged: boolean = this.depsModelCtors.some(ctor => { + return model instanceof ctor; + }); + if (dependencyChanged) { + // if an domain object we need was added or changed, update viewModelStore + this.getViewModelList().forEach(viewModel => { + viewModel.updateValues(model); + }); + this.updateAllObservables(model.id); + } + } + }); + + // Watch the Observables for deleting + this.DS.deletedObservable.subscribe(model => { + if (model.collection === CollectionStringModelMapperService.getCollectionString(baseModelCtor)) { + delete this.viewModelStore[model.id]; + this.updateAllObservables(model.id); + } + }); + } + + protected abstract createViewModel(model: M): V; + + /** + * helper function to return one viewModel + */ + protected getViewModel(id: number): V { + return this.viewModelStore[id]; + } + + /** + * helper function to return the viewModel as array + */ + protected getViewModelList(): V[] { + return Object.values(this.viewModelStore); + } + + /** + * returns the current observable for one viewModel + */ + public getViewModelObservable(id: number): Observable { + if (!this.viewModelSubjects[id]) { + this.viewModelSubjects[id] = new BehaviorSubject(this.viewModelStore[id]); + } + return this.viewModelSubjects[id].asObservable(); + } + + /** + * return the Observable of the whole store + */ + public getViewModelListObservable(): Observable { + return this.viewModelListSubject.asObservable(); + } + + /** + * Updates the ViewModel observable using a ViewModel corresponding to the id + */ + protected updateViewModelObservable(id: number): void { + if (this.viewModelSubjects[id]) { + this.viewModelSubjects[id].next(this.viewModelStore[id]); + } + } + + /** + * update the observable of the list + */ + protected updateViewModelListObservable(): void { + this.viewModelListSubject.next(this.getViewModelList()); + } + + /** + * Triggers both the observable update routines + */ + protected updateAllObservables(id: number): void { + this.updateViewModelListObservable(); + this.updateViewModelObservable(id); + } +} diff --git a/client/src/app/site/base-view-model.ts b/client/src/app/site/base-view-model.ts new file mode 100644 index 000000000..5f1749184 --- /dev/null +++ b/client/src/app/site/base-view-model.ts @@ -0,0 +1,39 @@ +import { TranslateService } from '@ngx-translate/core'; +import { BaseModel } from '../shared/models/base.model'; + +/** + * Base class for view models. alls view models should have titles. + */ +export abstract class BaseViewModel { + public abstract updateValues(update: BaseModel): void; + + /** + * Should return the title for the detail view. + * @param translate + */ + public abstract getTitle(translate: TranslateService): string; + + /** + * Should return the title for the list view. + * @param translate + */ + public getListTitle(translate: TranslateService): string { + return this.getTitle(translate); + } + + /** + * Should return the title for the projector. + * @param translate + */ + public getProjector(translate: TranslateService): string { + return this.getTitle(translate); + } + + /** + * Should return the title for the agenda list view. + * @param translate + */ + public getAgendaTitle(translate: TranslateService): string { + return this.getTitle(translate); + } +} diff --git a/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts b/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts index a954e1fcf..16fe11839 100644 --- a/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts +++ b/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts @@ -90,7 +90,7 @@ export class MotionDetailComponent extends BaseComponent implements OnInit { } else { // load existing motion this.route.params.subscribe(params => { - this.repo.getViewMotionObservable(params.id).subscribe(newViewMotion => { + this.repo.getViewModelObservable(params.id).subscribe(newViewMotion => { this.motion = newViewMotion; }); }); diff --git a/client/src/app/site/motions/components/motion-list/motion-list.component.ts b/client/src/app/site/motions/components/motion-list/motion-list.component.ts index 3afd288a3..fdc3a2946 100644 --- a/client/src/app/site/motions/components/motion-list/motion-list.component.ts +++ b/client/src/app/site/motions/components/motion-list/motion-list.component.ts @@ -97,7 +97,7 @@ export class MotionListComponent extends BaseComponent implements OnInit { this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; - this.repo.getViewMotionListObservable().subscribe(newMotions => { + this.repo.getViewModelListObservable().subscribe(newMotions => { this.dataSource.data = newMotions; }); } diff --git a/client/src/app/site/motions/models/view-motion.ts b/client/src/app/site/motions/models/view-motion.ts index d251276be..0149b4f55 100644 --- a/client/src/app/site/motions/models/view-motion.ts +++ b/client/src/app/site/motions/models/view-motion.ts @@ -4,6 +4,8 @@ import { User } from '../../../shared/models/users/user'; import { Workflow } from '../../../shared/models/motions/workflow'; import { WorkflowState } from '../../../shared/models/motions/workflow-state'; import { BaseModel } from '../../../shared/models/base.model'; +import { BaseViewModel } from '../../base-view-model'; +import { TranslateService } from '@ngx-translate/core'; /** * Motion class for the View @@ -12,7 +14,7 @@ import { BaseModel } from '../../../shared/models/base.model'; * Provides "safe" access to variables and functions in {@link Motion} * @ignore */ -export class ViewMotion { +export class ViewMotion extends BaseViewModel { private _motion: Motion; private _category: Category; private _submitters: User[]; @@ -70,7 +72,7 @@ export class ViewMotion { public get categoryId(): number { if (this._motion && this._motion.category_id) { - return this.category.id; + return this._motion.category_id; } else { return null; } @@ -147,6 +149,8 @@ export class ViewMotion { workflow?: Workflow, state?: WorkflowState ) { + super(); + this._motion = motion; this._category = category; this._submitters = submitters; @@ -155,6 +159,10 @@ export class ViewMotion { this._state = state; } + public getTitle(translate?: TranslateService): string { + return this.title; + } + /** * Updates the local objects if required * @param update diff --git a/client/src/app/site/motions/services/motion-repository.service.ts b/client/src/app/site/motions/services/motion-repository.service.ts index b7ea78b3e..61751a6d5 100644 --- a/client/src/app/site/motions/services/motion-repository.service.ts +++ b/client/src/app/site/motions/services/motion-repository.service.ts @@ -1,14 +1,14 @@ import { Injectable } from '@angular/core'; import { DataSendService } from '../../../core/services/data-send.service'; -import { OpenSlidesComponent } from '../../../openslides.component'; import { Motion } from '../../../shared/models/motions/motion'; import { User } from '../../../shared/models/users/user'; import { Category } from '../../../shared/models/motions/category'; import { Workflow } from '../../../shared/models/motions/workflow'; import { WorkflowState } from '../../../shared/models/motions/workflow-state'; import { ViewMotion } from '../models/view-motion'; -import { Observable, BehaviorSubject } from 'rxjs'; +import { Observable } from 'rxjs'; +import { BaseRepository } from '../../base-repository'; /** * Repository Services for motions (and potentially categories) @@ -23,22 +23,7 @@ import { Observable, BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root' }) -export class MotionRepositoryService extends OpenSlidesComponent { - /** - * Stores all the viewMotion in an object - */ - private viewMotionStore: { [motionId: number]: ViewMotion } = {}; - - /** - * Stores subjects to viewMotions in a list - */ - private viewMotionSubjects: { [motionId: number]: BehaviorSubject } = {}; - - /** - * Observable subject for the whole list - */ - private viewMotionListSubject: BehaviorSubject = new BehaviorSubject(null); - +export class MotionRepositoryService extends BaseRepository { /** * Creates a MotionRepository * @@ -47,46 +32,7 @@ export class MotionRepositoryService extends OpenSlidesComponent { * @param DataSend */ public constructor(private dataSend: DataSendService) { - super(); - - this.populateViewMotions(); - - // Could be raise in error if the root injector is not known - this.DS.changeObservable.subscribe(model => { - if (model instanceof Motion) { - // Add new and updated motions to the viewMotionStore - this.AddViewMotion(model); - this.updateObservables(model.id); - } else if (model instanceof Category || model instanceof User || model instanceof Workflow) { - // if an domain object we need was added or changed, update ViewMotionStore - this.getViewMotionList().forEach(viewMotion => { - viewMotion.updateValues(model); - }); - this.updateObservables(model.id); - } - }); - - // Watch the Observables for deleting - this.DS.deletedObservable.subscribe(model => { - if (model.collection === 'motions/motion') { - delete this.viewMotionStore[model.id]; - this.updateObservables(model.id); - } - }); - } - - /** - * called from the constructor. - * - * Populate the local viewMotionStore with ViewMotion Objects. - * Does nothing if the database was not created yet. - */ - private populateViewMotions(): void { - this.DS.getAll(Motion).forEach(motion => { - this.AddViewMotion(motion); - this.updateViewMotionObservable(motion.id); - }); - this.updateViewMotionListObservable(); + super(Motion, [Category, User, Workflow]); } /** @@ -97,7 +43,7 @@ export class MotionRepositoryService extends OpenSlidesComponent { * * @param motion blank motion domain object */ - private AddViewMotion(motion: Motion): void { + protected createViewModel(motion: Motion): ViewMotion { const category = this.DS.get(Category, motion.category_id); const submitters = this.DS.getMany(User, motion.submitterIds); const supporters = this.DS.getMany(User, motion.supporters_id); @@ -106,7 +52,7 @@ export class MotionRepositoryService extends OpenSlidesComponent { if (workflow) { state = workflow.getStateById(motion.state_id); } - this.viewMotionStore[motion.id] = new ViewMotion(motion, category, submitters, supporters, workflow, state); + return new ViewMotion(motion, category, submitters, supporters, workflow, state); } /** @@ -132,23 +78,6 @@ export class MotionRepositoryService extends OpenSlidesComponent { return this.dataSend.saveModel(updateMotion); } - /** - * returns the current observable MotionView - */ - public getViewMotionObservable(id: number): Observable { - if (!this.viewMotionSubjects[id]) { - this.updateViewMotionObservable(id); - } - return this.viewMotionSubjects[id].asObservable(); - } - - /** - * return the Observable of the whole store - */ - public getViewMotionListObservable(): Observable { - return this.viewMotionListSubject.asObservable(); - } - /** * Deleting a motion. * @@ -159,36 +88,4 @@ export class MotionRepositoryService extends OpenSlidesComponent { public deleteMotion(viewMotion: ViewMotion): Observable { return this.dataSend.delete(viewMotion.motion); } - - /** - * Updates the ViewMotion observable using a ViewMotion corresponding to the id - */ - private updateViewMotionObservable(id: number): void { - if (!this.viewMotionSubjects[id]) { - this.viewMotionSubjects[id] = new BehaviorSubject(null); - } - this.viewMotionSubjects[id].next(this.viewMotionStore[id]); - } - - /** - * helper function to return the viewMotions as array - */ - private getViewMotionList(): ViewMotion[] { - return Object.values(this.viewMotionStore); - } - - /** - * update the observable of the list - */ - private updateViewMotionListObservable(): void { - this.viewMotionListSubject.next(this.getViewMotionList()); - } - - /** - * Triggers both the observable update routines - */ - private updateObservables(id: number): void { - this.updateViewMotionListObservable(); - this.updateViewMotionObservable(id); - } }