Merge pull request #3841 from FinnStutzenstein/models-more-types-and-cleanup

More type annotations and resulting changes, cleanup
This commit is contained in:
Finn Stutzenstein 2018-09-04 11:27:51 +02:00 committed by GitHub
commit fa9d8de21c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 159 additions and 134 deletions

View File

@ -60,7 +60,7 @@ export class AutoupdateService extends OpenSlidesComponent {
// Add the objects to the DataStore. // Add the objects to the DataStore.
Object.keys(autoupdate.changed).forEach(collection => { Object.keys(autoupdate.changed).forEach(collection => {
const targetClass = CollectionStringModelMapperService.getCollectionStringType(collection); const targetClass = CollectionStringModelMapperService.getModelConstructor(collection);
if (!targetClass) { if (!targetClass) {
// TODO: throw an error later.. // TODO: throw an error later..
/*throw new Error*/ console.log(`Unregistered resource ${collection}`); /*throw new Error*/ console.log(`Unregistered resource ${collection}`);

View File

@ -23,10 +23,22 @@ export class CollectionStringModelMapperService {
* Returns the constructor of the requested collection or undefined, if it is not registered. * Returns the constructor of the requested collection or undefined, if it is not registered.
* @param collectionString the requested collection * @param collectionString the requested collection
*/ */
public static getCollectionStringType(collectionString: string): ModelConstructor { public static getModelConstructor(collectionString: string): ModelConstructor {
return CollectionStringModelMapperService.collectionStringsTypeMapping[collectionString]; 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. * Constructor to create the NotifyService. Registers itself to the WebsocketService.
* @param websocketService * @param websocketService

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs'; 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 { CacheService } from './cache.service';
import { CollectionStringModelMapperService } from './collectionStringModelMapper.service'; import { CollectionStringModelMapperService } from './collectionStringModelMapper.service';
@ -125,7 +125,7 @@ export class DataStoreService {
const storage: ModelStorage = {}; const storage: ModelStorage = {};
Object.keys(serializedStore).forEach(collectionString => { Object.keys(serializedStore).forEach(collectionString => {
storage[collectionString] = {} as ModelCollection; storage[collectionString] = {} as ModelCollection;
const target = CollectionStringModelMapperService.getCollectionStringType(collectionString); const target = CollectionStringModelMapperService.getModelConstructor(collectionString);
if (target) { if (target) {
Object.keys(serializedStore[collectionString]).forEach(id => { Object.keys(serializedStore[collectionString]).forEach(id => {
const data = JSON.parse(serializedStore[collectionString][id]); const data = JSON.parse(serializedStore[collectionString][id]);
@ -149,69 +149,86 @@ export class DataStoreService {
}); });
} }
/** private getCollectionString(collectionType: ModelConstructor | string): string {
* 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;
if (typeof collectionType === 'string') { if (typeof collectionType === 'string') {
collectionString = collectionType; return collectionType;
} else { } else {
// get the collection string by making an empty object return CollectionStringModelMapperService.getCollectionString(collectionType);
const tempObject = new collectionType(); }
collectionString = tempObject.collectionString;
} }
const collection: ModelCollection = this.modelStore[collectionString]; /**
* 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<T extends BaseModel>(collectionType: ModelConstructor | string, id: ModelId): T {
const collectionString = this.getCollectionString(collectionType);
const models = []; const collection: ModelCollection = this.modelStore[collectionString];
if (!collection) { if (!collection) {
return;
} else {
return collection[id];
}
}
/**
* 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 getMany<T extends BaseModel>(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; 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 * 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 printWhole(): void { public getAll<T extends BaseModel>(collectionType: ModelConstructor | string): T[] {
console.log('Everything in DataStore: ', this.modelStore); const collectionString = this.getCollectionString(collectionType);
const collection: ModelCollection = this.modelStore[collectionString];
if (!collection) {
return [];
} else {
return Object.values(collection);
}
} }
/** /**
* Filters the dataStore by type * Filters the dataStore by type.
* @param Type The desired BaseModel type to be read from the dataStore *
* @param collectionType The desired BaseModel type to be read from the dataStore
* @param callback a filter function * @param callback a filter function
* @return The BaseModel-list corresponding to the 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>(User, myUser => myUser.first_name === "Max")
*/ */
public filter(Type, callback): BaseModel[] { public filter<T extends BaseModel>(
// TODO: type for callback function collectionType: ModelConstructor | string,
let filterCollection = []; callback: (model: T) => boolean
const typeCollection = this.get(Type); ): BaseModel[] {
return this.getAll<T>(collectionType).filter(callback);
if (Array.isArray(typeCollection)) {
filterCollection = [...filterCollection, ...typeCollection];
} else {
filterCollection.push(typeCollection);
}
return filterCollection.filter(callback);
} }
/** /**
@ -301,4 +318,12 @@ export class DataStoreService {
private setObservable(value): void { private setObservable(value): void {
this.dataStoreSubject.next(value); 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);
}
} }

View File

@ -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;
}
}

View File

@ -1,6 +1,11 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base.model';
import { Speaker } from './speaker'; import { Speaker } from './speaker';
import { ContentObject } from './content-object'; import { User } from '../users/user';
interface ContentObject {
id: number;
collection: string;
}
/** /**
* Representations of agenda Item * Representations of agenda Item
@ -19,7 +24,7 @@ export class Item extends BaseModel {
public duration: number; public duration: number;
public speakers: Speaker[]; public speakers: Speaker[];
public speaker_list_closed: boolean; public speaker_list_closed: boolean;
public content_object: ContentObject; private content_object: ContentObject;
public weight: number; public weight: number;
public parent_id: number; public parent_id: number;
@ -57,26 +62,25 @@ export class Item extends BaseModel {
this.parent_id = parent_id; this.parent_id = parent_id;
} }
public getSpeakersAsUser(): BaseModel | BaseModel[] { public getSpeakers(): User[] {
const speakerIds = []; const speakerIds: number[] = this.speakers
this.speakers.forEach(speaker => { .sort((a: Speaker, b: Speaker) => {
speakerIds.push(speaker.user_id); return a.weight - b.weight;
}); })
return this.DS.get('users/user', ...speakerIds); .map((speaker: Speaker) => speaker.user_id);
return this.DS.getMany<User>('users/user', speakerIds);
} }
public getContentObject(): BaseModel | BaseModel[] { public get contentObject(): BaseModel {
return this.DS.get(this.content_object.collection, this.content_object.id); return this.DS.get<BaseModel>(this.content_object.collection, this.content_object.id);
} }
public deserialize(input: any): this { public deserialize(input: any): this {
Object.assign(this, input); Object.assign(this, input);
this.content_object = new ContentObject().deserialize(input.content_object);
if (input.speakers instanceof Array) { if (input.speakers instanceof Array) {
this.speakers = []; this.speakers = input.speakers.map(speakerData => {
input.speakers.forEach(speakerData => { return new Speaker().deserialize(speakerData);
this.speakers.push(new Speaker().deserialize(speakerData));
}); });
} }
return this; return this;

View File

@ -1,6 +1,8 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base.model';
import { AssignmentUser } from './assignment-user'; import { AssignmentUser } from './assignment-user';
import { Poll } from './poll'; import { Poll } from './poll';
import { Tag } from '../core/tag';
import { User } from '../users/user';
/** /**
* Representation of an assignment. * Representation of an assignment.
@ -50,11 +52,11 @@ export class Assignment extends BaseModel {
this.assignment_related_users.forEach(user => { this.assignment_related_users.forEach(user => {
userIds.push(user.user_id); userIds.push(user.user_id);
}); });
return this.DS.get('users/user', ...userIds); return this.DS.getMany<User>('users/user', userIds);
} }
public getTags(): BaseModel | BaseModel[] { public getTags(): BaseModel | BaseModel[] {
return this.DS.get('core/tag', ...this.tags_id); return this.DS.getMany<Tag>('core/tag', this.tags_id);
} }
public deserialize(input: any): this { public deserialize(input: any): this {

View File

@ -102,7 +102,7 @@ export class Motion extends BaseModel {
*/ */
public initDataStoreValues() { public initDataStoreValues() {
// check the containing Workflows in DataStore // check the containing Workflows in DataStore
const allWorkflows = this.DS.get(Workflow) as Workflow[]; const allWorkflows = this.DS.getAll<Workflow>(Workflow);
allWorkflows.forEach(localWorkflow => { allWorkflows.forEach(localWorkflow => {
if (localWorkflow.isStateContained(this.state_id)) { if (localWorkflow.isStateContained(this.state_id)) {
this.workflow = localWorkflow as Workflow; this.workflow = localWorkflow as Workflow;
@ -189,34 +189,25 @@ export class Motion extends BaseModel {
* return the submitters as uses objects * return the submitters as uses objects
*/ */
public get submitterAsUser() { public get submitterAsUser() {
const submitterIds = []; const submitterIds: number[] = this.submitters
if (this.submitters && this.submitters.length > 0) { .sort((a: MotionSubmitter, b: MotionSubmitter) => {
this.submitters.forEach(submitter => { return a.weight - b.weight;
submitterIds.push(submitter.user_id); })
}); .map((submitter: MotionSubmitter) => submitter.user_id);
const users = this.DS.get(User, ...submitterIds); return this.DS.getMany<User>('users/user', submitterIds);
return users;
} else {
return null;
}
} }
/** /**
* get the category of a motion as object * get the category of a motion as object
*/ */
public get category(): any { public get category(): Category {
if (this.category_id) { return this.DS.get<Category>(Category, this.category_id);
const motionCategory = this.DS.get(Category, this.category_id);
return motionCategory as Category;
} else {
return '';
}
} }
/** /**
* Set the category in the motion * Set the category in the motion
*/ */
public set category(newCategory: any) { public set category(newCategory: Category) {
this.category_id = newCategory.id; this.category_id = newCategory.id;
} }
@ -260,7 +251,7 @@ export class Motion extends BaseModel {
* returns the value of 'config.motions_recommendations_by' * returns the value of 'config.motions_recommendations_by'
*/ */
public get recomBy() { public get recomBy() {
const motionsRecommendationsByConfig = this.DS.filter( const motionsRecommendationsByConfig = this.DS.filter<Config>(
Config, Config,
config => config.key === 'motions_recommendations_by' config => config.key === 'motions_recommendations_by'
)[0] as Config; )[0] as Config;

View File

@ -1,4 +1,6 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base.model';
import { Mediafile } from '../mediafiles/mediafile';
import { Item } from '../agenda/item';
/** /**
* Representation of a topic. * Representation of a topic.
@ -22,12 +24,12 @@ export class Topic extends BaseModel {
this.agenda_item_id = agenda_item_id; this.agenda_item_id = agenda_item_id;
} }
public getAttachments(): BaseModel | BaseModel[] { public getAttachments(): Mediafile[] {
return this.DS.get('mediafiles/mediafile', ...this.attachments_id); return this.DS.getMany<Mediafile>('mediafiles/mediafile', this.attachments_id);
} }
public getAgenda(): BaseModel | BaseModel[] { public getAgenda(): Item {
return this.DS.get('agenda/item', this.agenda_item_id); return this.DS.get<Item>('agenda/item', this.agenda_item_id);
} }
} }

View File

@ -1,4 +1,5 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base.model';
import { User } from './user';
/** /**
* Representation of user group. * Representation of user group.
@ -17,6 +18,13 @@ export class Group extends BaseModel {
this.name = name; this.name = name;
this.permissions = permissions; this.permissions = permissions;
} }
public get users() {
// We have to use the string version to avoid circular dependencies.
return this.DS.filter<User>('users/user', user => {
return user.groups_id.includes(this.id);
});
}
} }
BaseModel.registerCollectionElement('users/group', Group); BaseModel.registerCollectionElement('users/group', Group);

View File

@ -63,14 +63,7 @@ export class User extends BaseModel {
} }
public get groups(): Group[] { public get groups(): Group[] {
const groups = this.DS.get('users/group', ...this.groups_id); return this.DS.getMany<Group>(Group, this.groups_id);
if (!groups) {
return [];
} else if (groups instanceof BaseModel) {
return [groups] as Group[];
} else {
return groups as Group[];
}
} }
public get full_name(): string { public get full_name(): string {

View File

@ -2,6 +2,8 @@ import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { BaseComponent } from 'app/base.component'; import { BaseComponent } from 'app/base.component';
import { TranslateService } from '@ngx-translate/core'; 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. * List view for the agenda.
@ -29,6 +31,10 @@ export class AgendaListComponent extends BaseComponent implements OnInit {
*/ */
public ngOnInit() { public ngOnInit() {
super.setTitle('Agenda'); 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.
} }
/** /**

View File

@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { BaseComponent } from '../../../base.component'; import { BaseComponent } from '../../../base.component';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { Assignment } from '../../../shared/models/assignments/assignment';
/** /**
* Listview for the assignments * Listview for the assignments
@ -47,6 +48,9 @@ export class AssignmentListComponent extends BaseComponent implements OnInit {
*/ */
public ngOnInit() { public ngOnInit() {
super.setTitle('Assignments'); 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.
} }
/** /**

View File

@ -53,7 +53,7 @@ export class CategoryListComponent extends BaseComponent implements OnInit {
*/ */
public ngOnInit() { public ngOnInit() {
super.setTitle('Category'); super.setTitle('Category');
this.categoryArray = this.DS.get(Category) as Category[]; this.categoryArray = this.DS.getAll<Category>(Category);
this.dataSource = new MatTableDataSource(this.categoryArray); this.dataSource = new MatTableDataSource(this.categoryArray);
this.dataSource.sort = this.sort; 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 // The alternative approach is to put the observable as DataSource to the table
this.DS.getObservable().subscribe(newModel => { this.DS.getObservable().subscribe(newModel => {
if (newModel instanceof Category) { if (newModel instanceof Category) {
this.categoryArray = this.DS.get(Category) as Category[]; this.categoryArray = this.DS.getAll<Category>(Category);
this.dataSource.data = this.categoryArray; this.dataSource.data = this.categoryArray;
} }
}); });

View File

@ -167,8 +167,7 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
* return all Categories. * return all Categories.
*/ */
public getMotionCategories(): Category[] { public getMotionCategories(): Category[] {
const categories = this.DS.get(Category); return this.DS.getAll<Category>(Category);
return categories as Category[];
} }
/** /**

View File

@ -93,8 +93,8 @@ export class MotionListComponent extends BaseComponent implements OnInit {
*/ */
public ngOnInit() { public ngOnInit() {
super.setTitle('Motions'); super.setTitle('Motions');
this.workflowArray = this.DS.get(Workflow) as Workflow[]; this.workflowArray = this.DS.getAll<Workflow>(Workflow);
this.motionArray = this.DS.get(Motion) as Motion[]; this.motionArray = this.DS.getAll<Motion>(Motion);
this.dataSource = new MatTableDataSource(this.motionArray); this.dataSource = new MatTableDataSource(this.motionArray);
this.dataSource.paginator = this.paginator; this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort; this.dataSource.sort = this.sort;
@ -103,7 +103,7 @@ export class MotionListComponent extends BaseComponent implements OnInit {
// The alternative approach is to put the observable as DataSource to the table // The alternative approach is to put the observable as DataSource to the table
this.DS.getObservable().subscribe(newModel => { this.DS.getObservable().subscribe(newModel => {
if (newModel instanceof Motion) { if (newModel instanceof Motion) {
this.motionArray = this.DS.get(Motion) as Motion[]; this.motionArray = this.DS.getAll<Motion>(Motion);
this.dataSource.data = this.motionArray; this.dataSource.data = this.motionArray;
} }
}); });

View File

@ -8,6 +8,8 @@ import { BaseComponent } from 'app/base.component';
import { pageTransition, navItemAnim } from 'app/shared/animations'; import { pageTransition, navItemAnim } from 'app/shared/animations';
import { MatDialog, MatSidenav } from '@angular/material'; import { MatDialog, MatSidenav } from '@angular/material';
import { ViewportService } from '../core/services/viewport.service'; import { ViewportService } from '../core/services/viewport.service';
import { Projector } from '../shared/models/core/projector';
import { Tag } from '../shared/models/core/tag';
@Component({ @Component({
selector: 'os-site', selector: 'os-site',
@ -69,6 +71,11 @@ export class SiteComponent extends BaseComponent implements OnInit {
// this.translate.get('Motions').subscribe((res: string) => { // this.translate.get('Motions').subscribe((res: string) => {
// console.log('translation of motions in the target language: ' + res); // console.log('translation of motions in the target language: ' + res);
// }); // });
// 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.
} }
/** /**

View File

@ -45,7 +45,7 @@ export class StartComponent extends BaseComponent implements OnInit {
super.setTitle('Home'); super.setTitle('Home');
// set welcome title and text // set welcome title and text
const welcomeTitleConfig = this.DS.filter( const welcomeTitleConfig = this.DS.filter<Config>(
Config, Config,
config => config.key === 'general_event_welcome_title' config => config.key === 'general_event_welcome_title'
)[0] as Config; )[0] as Config;
@ -54,7 +54,7 @@ export class StartComponent extends BaseComponent implements OnInit {
this.welcomeTitle = welcomeTitleConfig.value as string; this.welcomeTitle = welcomeTitleConfig.value as string;
} }
const welcomeTextConfig = this.DS.filter( const welcomeTextConfig = this.DS.filter<Config>(
Config, Config,
config => config.key === 'general_event_welcome_text' config => config.key === 'general_event_welcome_text'
)[0] as Config; )[0] as Config;