diff --git a/client/src/app/core/services/autoupdate.service.ts b/client/src/app/core/services/autoupdate.service.ts index 261ea6e80..2496d9bfb 100644 --- a/client/src/app/core/services/autoupdate.service.ts +++ b/client/src/app/core/services/autoupdate.service.ts @@ -60,7 +60,7 @@ export class AutoupdateService extends OpenSlidesComponent { // Add the objects to the DataStore. Object.keys(autoupdate.changed).forEach(collection => { - const targetClass = CollectionStringModelMapperService.getCollectionStringType(collection); + const targetClass = CollectionStringModelMapperService.getModelConstructor(collection); if (!targetClass) { // TODO: throw an error later.. /*throw new Error*/ console.log(`Unregistered resource ${collection}`); diff --git a/client/src/app/core/services/collectionStringModelMapper.service.ts b/client/src/app/core/services/collectionStringModelMapper.service.ts index c8a8624a0..8a26d5a43 100644 --- a/client/src/app/core/services/collectionStringModelMapper.service.ts +++ b/client/src/app/core/services/collectionStringModelMapper.service.ts @@ -23,10 +23,22 @@ export class CollectionStringModelMapperService { * Returns the constructor of the requested collection or undefined, if it is not registered. * @param collectionString the requested collection */ - public static getCollectionStringType(collectionString: string): ModelConstructor { + public static getModelConstructor(collectionString: string): ModelConstructor { return CollectionStringModelMapperService.collectionStringsTypeMapping[collectionString]; } + /** + * Returns the collection string of a given ModelConstructor or undefined, if it is not registered. + * @param ctor + */ + public static getCollectionString(ctor: ModelConstructor): string { + return Object.keys(CollectionStringModelMapperService.collectionStringsTypeMapping).find( + (collectionString: string) => { + return ctor === CollectionStringModelMapperService.collectionStringsTypeMapping[collectionString]; + } + ); + } + /** * Constructor to create the NotifyService. Registers itself to the WebsocketService. * @param websocketService diff --git a/client/src/app/core/services/data-store.service.ts b/client/src/app/core/services/data-store.service.ts index 0eb42c298..767ba7bb4 100644 --- a/client/src/app/core/services/data-store.service.ts +++ b/client/src/app/core/services/data-store.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Observable, BehaviorSubject } from 'rxjs'; -import { BaseModel, ModelId } from 'app/shared/models/base.model'; +import { BaseModel, ModelId, ModelConstructor } from 'app/shared/models/base.model'; import { CacheService } from './cache.service'; import { CollectionStringModelMapperService } from './collectionStringModelMapper.service'; @@ -125,7 +125,7 @@ export class DataStoreService { const storage: ModelStorage = {}; Object.keys(serializedStore).forEach(collectionString => { storage[collectionString] = {} as ModelCollection; - const target = CollectionStringModelMapperService.getCollectionStringType(collectionString); + const target = CollectionStringModelMapperService.getModelConstructor(collectionString); if (target) { Object.keys(serializedStore[collectionString]).forEach(id => { const data = JSON.parse(serializedStore[collectionString][id]); @@ -149,69 +149,86 @@ export class DataStoreService { }); } - /** - * 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) - */ - public get(collectionType, ...ids: ModelId[]): BaseModel[] | BaseModel { - let collectionString: string; + private getCollectionString(collectionType: ModelConstructor | string): string { if (typeof collectionType === 'string') { - collectionString = collectionType; + return collectionType; } else { - // get the collection string by making an empty object - const tempObject = new collectionType(); - collectionString = tempObject.collectionString; + return CollectionStringModelMapperService.getCollectionString(collectionType); } + } + + /** + * Read one model based on the collection and id from the DataStore. + * + * @param collectionType The desired BaseModel or collectionString to be read from the dataStore + * @param ids One ID of the BaseModel + * @return The given BaseModel-subclass instance + * @example: this.DS.get(User, 1) + * @example: this.DS.get('core/countdown', 2) + */ + public get(collectionType: ModelConstructor | string, id: ModelId): T { + const collectionString = this.getCollectionString(collectionType); const collection: ModelCollection = this.modelStore[collectionString]; - - const models = []; if (!collection) { - return models; - } - - if (ids.length === 0) { - return Object.values(collection); + return; } else { - ids.forEach(id => { - const model: BaseModel = collection[id]; - models.push(model); - }); + return collection[id]; } - return models.length === 1 ? models[0] : models; } /** - * Prints the whole dataStore + * Read multiple ID's from dataStore + * @param collectionType The desired BaseModel or collectionString to be read from the dataStore + * @param ids Multiple IDs as a list of IDs of BaseModel + * @return The BaseModel-list corresponding to the given ID(s) + * @example: this.DS.get(User, [1,2,3,4,5]) */ - public printWhole(): void { - console.log('Everything in DataStore: ', this.modelStore); + public getMany(collectionType: ModelConstructor | string, ids: ModelId[]): T[] { + const collectionString = this.getCollectionString(collectionType); + + const collection: ModelCollection = this.modelStore[collectionString]; + if (!collection) { + return []; + } + const models = ids + .map(id => { + return collection[id]; + }) + .filter(model => !!model); // remove non valid models. + return models; } /** - * Filters the dataStore by type - * @param Type The desired BaseModel type to be read from the dataStore + * Get all models of the given collection from the DataStore. + * @param collectionType The desired BaseModel or collectionString to be read from the dataStore + * @return The BaseModel-list of all instances of T + * @example: this.DS.get(User) + */ + public getAll(collectionType: ModelConstructor | string): T[] { + const collectionString = this.getCollectionString(collectionType); + + const collection: ModelCollection = this.modelStore[collectionString]; + if (!collection) { + return []; + } else { + return Object.values(collection); + } + } + + /** + * Filters the dataStore by type. + * + * @param collectionType 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") + * @example this.DS.filter(User, myUser => myUser.first_name === "Max") */ - public 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); + public filter( + collectionType: ModelConstructor | string, + callback: (model: T) => boolean + ): BaseModel[] { + return this.getAll(collectionType).filter(callback); } /** @@ -301,4 +318,12 @@ export class DataStoreService { private setObservable(value): void { this.dataStoreSubject.next(value); } + + /** + * Prints the whole dataStore + * @deprecated Shouldn't be used, will be removed later + */ + public printWhole(): void { + console.log('Everything in DataStore: ', this.modelStore); + } } diff --git a/client/src/app/shared/models/agenda/content-object.ts b/client/src/app/shared/models/agenda/content-object.ts deleted file mode 100644 index bbc1aed73..000000000 --- a/client/src/app/shared/models/agenda/content-object.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Deserializable } from '../deserializable.model'; - -/** - * Representation of the content object in agenda item - * @ignore - */ -export class ContentObject implements Deserializable { - /** - * Is the same with dataStores collectionString - */ - public collection: string; - public id: number; - - /** - * Needs to be completely optional because agenda has (yet) the optional parameter 'speaker' - * @param collection - * @param id - */ - public constructor(collection?: string, id?: number) { - this.collection = collection; - this.id = id; - } - - public deserialize(input: any): this { - Object.assign(this, input); - return this; - } -} diff --git a/client/src/app/shared/models/agenda/item.ts b/client/src/app/shared/models/agenda/item.ts index 4598c9686..055767de7 100644 --- a/client/src/app/shared/models/agenda/item.ts +++ b/client/src/app/shared/models/agenda/item.ts @@ -1,6 +1,11 @@ import { BaseModel } from '../base.model'; import { Speaker } from './speaker'; -import { ContentObject } from './content-object'; +import { User } from '../users/user'; + +interface ContentObject { + id: number; + collection: string; +} /** * Representations of agenda Item @@ -19,7 +24,7 @@ export class Item extends BaseModel { public duration: number; public speakers: Speaker[]; public speaker_list_closed: boolean; - public content_object: ContentObject; + private content_object: ContentObject; public weight: number; public parent_id: number; @@ -57,26 +62,25 @@ export class Item extends BaseModel { this.parent_id = parent_id; } - public getSpeakersAsUser(): BaseModel | BaseModel[] { - const speakerIds = []; - this.speakers.forEach(speaker => { - speakerIds.push(speaker.user_id); - }); - return this.DS.get('users/user', ...speakerIds); + public getSpeakers(): User[] { + const speakerIds: number[] = this.speakers + .sort((a: Speaker, b: Speaker) => { + return a.weight - b.weight; + }) + .map((speaker: Speaker) => speaker.user_id); + return this.DS.getMany('users/user', speakerIds); } - public getContentObject(): BaseModel | BaseModel[] { - return this.DS.get(this.content_object.collection, this.content_object.id); + public get contentObject(): BaseModel { + return this.DS.get(this.content_object.collection, this.content_object.id); } public 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)); + this.speakers = input.speakers.map(speakerData => { + return new Speaker().deserialize(speakerData); }); } return this; diff --git a/client/src/app/shared/models/assignments/assignment.ts b/client/src/app/shared/models/assignments/assignment.ts index fc94d826e..1ae32d750 100644 --- a/client/src/app/shared/models/assignments/assignment.ts +++ b/client/src/app/shared/models/assignments/assignment.ts @@ -1,6 +1,8 @@ import { BaseModel } from '../base.model'; import { AssignmentUser } from './assignment-user'; import { Poll } from './poll'; +import { Tag } from '../core/tag'; +import { User } from '../users/user'; /** * Representation of an assignment. @@ -50,11 +52,11 @@ export class Assignment extends BaseModel { this.assignment_related_users.forEach(user => { userIds.push(user.user_id); }); - return this.DS.get('users/user', ...userIds); + return this.DS.getMany('users/user', userIds); } public getTags(): BaseModel | BaseModel[] { - return this.DS.get('core/tag', ...this.tags_id); + return this.DS.getMany('core/tag', this.tags_id); } public deserialize(input: any): this { diff --git a/client/src/app/shared/models/motions/motion.ts b/client/src/app/shared/models/motions/motion.ts index 5f3087740..13939a2d5 100644 --- a/client/src/app/shared/models/motions/motion.ts +++ b/client/src/app/shared/models/motions/motion.ts @@ -102,7 +102,7 @@ export class Motion extends BaseModel { */ public initDataStoreValues() { // check the containing Workflows in DataStore - const allWorkflows = this.DS.get(Workflow) as Workflow[]; + const allWorkflows = this.DS.getAll(Workflow); allWorkflows.forEach(localWorkflow => { if (localWorkflow.isStateContained(this.state_id)) { this.workflow = localWorkflow as Workflow; @@ -189,34 +189,25 @@ export class Motion extends BaseModel { * return the submitters as uses objects */ public get submitterAsUser() { - const submitterIds = []; - if (this.submitters && this.submitters.length > 0) { - this.submitters.forEach(submitter => { - submitterIds.push(submitter.user_id); - }); - const users = this.DS.get(User, ...submitterIds); - return users; - } else { - return null; - } + const submitterIds: number[] = this.submitters + .sort((a: MotionSubmitter, b: MotionSubmitter) => { + return a.weight - b.weight; + }) + .map((submitter: MotionSubmitter) => submitter.user_id); + return this.DS.getMany('users/user', submitterIds); } /** * get the category of a motion as object */ - public get category(): any { - if (this.category_id) { - const motionCategory = this.DS.get(Category, this.category_id); - return motionCategory as Category; - } else { - return ''; - } + public get category(): Category { + return this.DS.get(Category, this.category_id); } /** * Set the category in the motion */ - public set category(newCategory: any) { + public set category(newCategory: Category) { this.category_id = newCategory.id; } @@ -260,7 +251,7 @@ export class Motion extends BaseModel { * returns the value of 'config.motions_recommendations_by' */ public get recomBy() { - const motionsRecommendationsByConfig = this.DS.filter( + const motionsRecommendationsByConfig = this.DS.filter( Config, config => config.key === 'motions_recommendations_by' )[0] as Config; diff --git a/client/src/app/shared/models/topics/topic.ts b/client/src/app/shared/models/topics/topic.ts index 3d2b68d45..8c489c890 100644 --- a/client/src/app/shared/models/topics/topic.ts +++ b/client/src/app/shared/models/topics/topic.ts @@ -1,4 +1,6 @@ import { BaseModel } from '../base.model'; +import { Mediafile } from '../mediafiles/mediafile'; +import { Item } from '../agenda/item'; /** * Representation of a topic. @@ -22,12 +24,12 @@ export class Topic extends BaseModel { this.agenda_item_id = agenda_item_id; } - public getAttachments(): BaseModel | BaseModel[] { - return this.DS.get('mediafiles/mediafile', ...this.attachments_id); + public getAttachments(): Mediafile[] { + return this.DS.getMany('mediafiles/mediafile', this.attachments_id); } - public getAgenda(): BaseModel | BaseModel[] { - return this.DS.get('agenda/item', this.agenda_item_id); + public getAgenda(): Item { + return this.DS.get('agenda/item', this.agenda_item_id); } } diff --git a/client/src/app/shared/models/users/group.ts b/client/src/app/shared/models/users/group.ts index c383e28dd..ad3218515 100644 --- a/client/src/app/shared/models/users/group.ts +++ b/client/src/app/shared/models/users/group.ts @@ -1,4 +1,5 @@ import { BaseModel } from '../base.model'; +import { User } from './user'; /** * Representation of user group. @@ -17,6 +18,13 @@ export class Group extends BaseModel { this.name = name; this.permissions = permissions; } + + public get users() { + // We have to use the string version to avoid circular dependencies. + return this.DS.filter('users/user', user => { + return user.groups_id.includes(this.id); + }); + } } BaseModel.registerCollectionElement('users/group', Group); diff --git a/client/src/app/shared/models/users/user.ts b/client/src/app/shared/models/users/user.ts index 71563a517..89acd70c3 100644 --- a/client/src/app/shared/models/users/user.ts +++ b/client/src/app/shared/models/users/user.ts @@ -63,14 +63,7 @@ export class User extends BaseModel { } public get groups(): Group[] { - const groups = this.DS.get('users/group', ...this.groups_id); - if (!groups) { - return []; - } else if (groups instanceof BaseModel) { - return [groups] as Group[]; - } else { - return groups as Group[]; - } + return this.DS.getMany(Group, this.groups_id); } public get full_name(): string { diff --git a/client/src/app/site/agenda/agenda-list/agenda-list.component.ts b/client/src/app/site/agenda/agenda-list/agenda-list.component.ts index 3678a9257..bd679e0a2 100644 --- a/client/src/app/site/agenda/agenda-list/agenda-list.component.ts +++ b/client/src/app/site/agenda/agenda-list/agenda-list.component.ts @@ -2,6 +2,8 @@ import { Component, OnInit } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { BaseComponent } from 'app/base.component'; import { TranslateService } from '@ngx-translate/core'; +import { Item } from '../../../shared/models/agenda/item'; +import { Topic } from '../../../shared/models/topics/topic'; /** * List view for the agenda. @@ -29,6 +31,10 @@ export class AgendaListComponent extends BaseComponent implements OnInit { */ public ngOnInit() { super.setTitle('Agenda'); + // tslint:disable-next-line + const i: Item = new Item(); // Needed, that the Item.ts is loaded. Can be removed, if something else creates/uses items. + // tslint:disable-next-line + const t: Topic = new Topic(); // Needed, that the Topic.ts is loaded. Can be removed, if something else creates/uses topics. } /** diff --git a/client/src/app/site/assignments/assignment-list/assignment-list.component.ts b/client/src/app/site/assignments/assignment-list/assignment-list.component.ts index 1bfa974b5..71eb498aa 100644 --- a/client/src/app/site/assignments/assignment-list/assignment-list.component.ts +++ b/client/src/app/site/assignments/assignment-list/assignment-list.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { BaseComponent } from '../../../base.component'; import { TranslateService } from '@ngx-translate/core'; import { Title } from '@angular/platform-browser'; +import { Assignment } from '../../../shared/models/assignments/assignment'; /** * Listview for the assignments @@ -47,6 +48,9 @@ export class AssignmentListComponent extends BaseComponent implements OnInit { */ public ngOnInit() { super.setTitle('Assignments'); + + // tslint:disable-next-line + const a: Assignment = new Assignment(); // Needed, that the Assignment.ts is loaded. Can be removed, if something else creates/uses assignments. } /** diff --git a/client/src/app/site/motions/category-list/category-list.component.ts b/client/src/app/site/motions/category-list/category-list.component.ts index 70263c0db..41cd371a6 100644 --- a/client/src/app/site/motions/category-list/category-list.component.ts +++ b/client/src/app/site/motions/category-list/category-list.component.ts @@ -53,7 +53,7 @@ export class CategoryListComponent extends BaseComponent implements OnInit { */ public ngOnInit() { super.setTitle('Category'); - this.categoryArray = this.DS.get(Category) as Category[]; + this.categoryArray = this.DS.getAll(Category); this.dataSource = new MatTableDataSource(this.categoryArray); this.dataSource.sort = this.sort; @@ -61,7 +61,7 @@ export class CategoryListComponent extends BaseComponent implements OnInit { // The alternative approach is to put the observable as DataSource to the table this.DS.getObservable().subscribe(newModel => { if (newModel instanceof Category) { - this.categoryArray = this.DS.get(Category) as Category[]; + this.categoryArray = this.DS.getAll(Category); this.dataSource.data = this.categoryArray; } }); diff --git a/client/src/app/site/motions/motion-detail/motion-detail.component.ts b/client/src/app/site/motions/motion-detail/motion-detail.component.ts index 9ab561d1a..c4ff3a259 100644 --- a/client/src/app/site/motions/motion-detail/motion-detail.component.ts +++ b/client/src/app/site/motions/motion-detail/motion-detail.component.ts @@ -167,8 +167,7 @@ export class MotionDetailComponent extends BaseComponent implements OnInit { * return all Categories. */ public getMotionCategories(): Category[] { - const categories = this.DS.get(Category); - return categories as Category[]; + return this.DS.getAll(Category); } /** diff --git a/client/src/app/site/motions/motion-list/motion-list.component.ts b/client/src/app/site/motions/motion-list/motion-list.component.ts index 01c22e458..b55032f92 100644 --- a/client/src/app/site/motions/motion-list/motion-list.component.ts +++ b/client/src/app/site/motions/motion-list/motion-list.component.ts @@ -93,8 +93,8 @@ export class MotionListComponent extends BaseComponent implements OnInit { */ public ngOnInit() { super.setTitle('Motions'); - this.workflowArray = this.DS.get(Workflow) as Workflow[]; - this.motionArray = this.DS.get(Motion) as Motion[]; + this.workflowArray = this.DS.getAll(Workflow); + this.motionArray = this.DS.getAll(Motion); this.dataSource = new MatTableDataSource(this.motionArray); this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; @@ -103,7 +103,7 @@ export class MotionListComponent extends BaseComponent implements OnInit { // The alternative approach is to put the observable as DataSource to the table this.DS.getObservable().subscribe(newModel => { if (newModel instanceof Motion) { - this.motionArray = this.DS.get(Motion) as Motion[]; + this.motionArray = this.DS.getAll(Motion); this.dataSource.data = this.motionArray; } }); diff --git a/client/src/app/site/site.component.ts b/client/src/app/site/site.component.ts index 67b780f79..679753285 100644 --- a/client/src/app/site/site.component.ts +++ b/client/src/app/site/site.component.ts @@ -8,6 +8,8 @@ import { BaseComponent } from 'app/base.component'; import { pageTransition, navItemAnim } from 'app/shared/animations'; import { MatDialog, MatSidenav } from '@angular/material'; import { ViewportService } from '../core/services/viewport.service'; +import { Projector } from '../shared/models/core/projector'; +import { Tag } from '../shared/models/core/tag'; @Component({ selector: 'os-site', @@ -69,6 +71,11 @@ export class SiteComponent extends BaseComponent implements OnInit { // this.translate.get('Motions').subscribe((res: string) => { // console.log('translation of motions in the target language: ' + res); // }); + + // tslint:disable-next-line + const p: Projector = new Projector(); // Needed, that the Projector.ts is loaded. Can be removed, if something else creates/uses projectors. + // tslint:disable-next-line + const t: Tag = new Tag(); // Needed, that the Tag.ts is loaded. Can be removed, if something else creates/uses tags. } /** diff --git a/client/src/app/site/start/start.component.ts b/client/src/app/site/start/start.component.ts index 4ba4c93ec..2b677a819 100644 --- a/client/src/app/site/start/start.component.ts +++ b/client/src/app/site/start/start.component.ts @@ -45,7 +45,7 @@ export class StartComponent extends BaseComponent implements OnInit { super.setTitle('Home'); // set welcome title and text - const welcomeTitleConfig = this.DS.filter( + const welcomeTitleConfig = this.DS.filter( Config, config => config.key === 'general_event_welcome_title' )[0] as Config; @@ -54,7 +54,7 @@ export class StartComponent extends BaseComponent implements OnInit { this.welcomeTitle = welcomeTitleConfig.value as string; } - const welcomeTextConfig = this.DS.filter( + const welcomeTextConfig = this.DS.filter( Config, config => config.key === 'general_event_welcome_text' )[0] as Config;