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.
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}`);

View File

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

View File

@ -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<T extends BaseModel>(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<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;
}
/**
* 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<T extends BaseModel>(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>(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<T extends BaseModel>(
collectionType: ModelConstructor | string,
callback: (model: T) => boolean
): BaseModel[] {
return this.getAll<T>(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);
}
}

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 { 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<User>('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<BaseModel>(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;

View File

@ -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<User>('users/user', userIds);
}
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 {

View File

@ -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>(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<User>('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>(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 => config.key === 'motions_recommendations_by'
)[0] as Config;

View File

@ -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<Mediafile>('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<Item>('agenda/item', this.agenda_item_id);
}
}

View File

@ -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<User>('users/user', user => {
return user.groups_id.includes(this.id);
});
}
}
BaseModel.registerCollectionElement('users/group', Group);

View File

@ -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>(Group, this.groups_id);
}
public get full_name(): string {

View File

@ -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.
}
/**

View File

@ -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.
}
/**

View File

@ -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>(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>(Category);
this.dataSource.data = this.categoryArray;
}
});

View File

@ -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>(Category);
}
/**

View File

@ -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>(Workflow);
this.motionArray = this.DS.getAll<Motion>(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>(Motion);
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 { 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.
}
/**

View File

@ -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 => 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 => config.key === 'general_event_welcome_text'
)[0] as Config;