diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 4a716d26e..ca0c46f21 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; -import { OpenslidesService } from './core/services/openslides.service'; import { TranslateService } from '@ngx-translate/core'; +import { OpenslidesService } from 'app/core/services/openslides.service'; +import { AutoupdateService } from 'app/core/services/autoupdate.service'; @Component({ selector: 'app-root', @@ -8,7 +9,11 @@ import { TranslateService } from '@ngx-translate/core'; styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { - constructor(private openSlides: OpenslidesService, public translate: TranslateService) { + constructor( + private openSlides: OpenslidesService, + private autoupdate: AutoupdateService, + private translate: TranslateService + ) { // manually add the supported languages translate.addLangs(['en', 'de', 'fr']); // this language will be used as a fallback when a translation isn't found in the current language diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 567a106ea..c09101b98 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -34,10 +34,7 @@ import { MotionsComponent } from './site/motions/motions.component'; import { AgendaComponent } from './site/agenda/agenda.component'; import { SiteComponent } from './site/site.component'; import { StartComponent } from './site/start/start.component'; -import { ToastComponent } from './core/directives/toast/toast.component'; -import { ToastService } from './core/services/toast.service'; import { WebsocketService } from './core/services/websocket.service'; -import { DS } from './core/services/DS.service'; import { ProjectorContainerComponent } from './projector-container/projector-container.component'; import { AlertComponent } from './core/directives/alert/alert.component'; @@ -62,7 +59,6 @@ library.add(fas); AgendaComponent, SiteComponent, StartComponent, - ToastComponent, ProjectorContainerComponent, AlertComponent ], @@ -96,7 +92,7 @@ library.add(fas); }), AppRoutingModule ], - providers: [Title, ToastService, WebsocketService, DS], + providers: [Title, WebsocketService], bootstrap: [AppComponent] }) export class AppModule {} diff --git a/client/src/app/base.component.ts b/client/src/app/base.component.ts index 7df0926de..1537dace2 100644 --- a/client/src/app/base.component.ts +++ b/client/src/app/base.component.ts @@ -1,12 +1,39 @@ +import { Injector } from '@angular/core'; import { Title } from '@angular/platform-browser'; +import { DataStoreService } from 'app/core/services/DS.service'; +// import { TranslateService } from '@ngx-translate/core'; -//provides functions that might be used by a lot of components +// provides functions that might be used by a lot of components export abstract class BaseComponent { + protected injector: Injector; + protected dataStore: DataStoreService; + // would die in every scope change. disabled for now + // protected _translateService: TranslateService; private titleSuffix = ' - OpenSlides 3'; - constructor(protected titleService: Title) {} + constructor(protected titleService?: Title) { + // throws a warning even tho it is the new syntax. Ignored for now. + this.injector = Injector.create([{ provide: DataStoreService, useClass: DataStoreService, deps: [] }]); + // this._injector = Injector.create([{ provide: TranslateService, useClass: TranslateService, deps: [] }]); + } setTitle(prefix: string) { 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; + } + + // get translate(): TranslateService { + // if (this._translateService == null) { + // this._translateService = this._injector.get(TranslateService); + // } + // return this._translateService; + // } } diff --git a/client/src/app/core/models/agenda/item.ts b/client/src/app/core/models/agenda/item.ts new file mode 100644 index 000000000..d0a39d296 --- /dev/null +++ b/client/src/app/core/models/agenda/item.ts @@ -0,0 +1,54 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class Item extends BaseModel { + static collectionString = 'agenda/item'; + id: number; + closed: boolean; + comment: string; + content_object: Object; + duration: number; //time? + is_hidden: boolean; + 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; + type: number; + weight: number; + + constructor( + id: number, + closed?: boolean, + comment?: string, + content_object?: Object, + duration?: number, + is_hidden?: boolean, + item_number?: string, + list_view_title?: string, + parent_id?: number, + speaker_list_closed?: boolean, + speakers?: BaseModel[], + title?: string, + type?: number, + weight?: number + ) { + super(id); + this.comment = comment; + this.content_object = content_object; + this.duration = duration; + this.is_hidden = is_hidden; + 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.type = type; + this.weight = weight; + } + + public getCollectionString(): string { + return Item.collectionString; + } +} diff --git a/client/src/app/core/models/assignments/assignment.ts b/client/src/app/core/models/assignments/assignment.ts new file mode 100644 index 000000000..03663ed75 --- /dev/null +++ b/client/src/app/core/models/assignments/assignment.ts @@ -0,0 +1,41 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class Assignment extends BaseModel { + static collectionString = 'assignments/assignment'; + id: number; + agenda_item_id: number; + description: string; + open_posts: number; + phase: number; + poll_description_default: number; + polls: Object[]; + tags_id: number[]; + title: string; + + constructor( + id: number, + agenda_item_id?: number, + description?: string, + open_posts?: number, + phase?: number, + poll_description_default?: number, + polls?: Object[], + tags_id?: number[], + title?: string + ) { + super(id); + this.id = id; + this.agenda_item_id = agenda_item_id; + this.description = description; + this.open_posts = open_posts; + this.phase = phase; + this.poll_description_default = poll_description_default; + this.polls = polls; + this.tags_id = tags_id; + this.title = title; + } + + public getCollectionString(): string { + return Assignment.collectionString; + } +} diff --git a/client/src/app/core/models/baseModel.ts b/client/src/app/core/models/baseModel.ts index f3a2db6dc..63373aacd 100644 --- a/client/src/app/core/models/baseModel.ts +++ b/client/src/app/core/models/baseModel.ts @@ -8,35 +8,35 @@ const INVALID_COLLECTION_STRING = 'invalid-collection-string'; export type ModelId = number | string; export abstract class BaseModel { + static collectionString = INVALID_COLLECTION_STRING; id: ModelId; - constructor() {} + 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: {}, T): BaseModel { + static fromJSON(jsonString: {}, Type): BaseModel { // create an instance of the User class - const model = Object.create(T.prototype); + const model = Object.create(Type.prototype); // copy all the fields from the json object return Object.assign(model, jsonString); } - //hast to be overwritten by the children. - //Could be more generic: e.g. a model-enum public getCollectionString(): string { - return INVALID_COLLECTION_STRING; + return BaseModel.collectionString; } //TODO document this function. - public getCheckedCollectionString(): string { - const collectionString: string = this.getCollectionString(); - if (collectionString === INVALID_COLLECTION_STRING) { - throw new ImproperlyConfiguredError( - 'Invalid collection string: Please override the static getCollectionString method!' - ); - } - return collectionString; - } + // public getCheckedCollectionString(): string { + // if (this.collectionString === INVALID_COLLECTION_STRING) { + // throw new ImproperlyConfiguredError( + // 'Invalid collection string: Please override the static getCollectionString method!' + // ); + // } + // return collectionString; + // } } diff --git a/client/src/app/core/models/core/chat-message.ts b/client/src/app/core/models/core/chat-message.ts new file mode 100644 index 000000000..87a75be83 --- /dev/null +++ b/client/src/app/core/models/core/chat-message.ts @@ -0,0 +1,19 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class ChatMessage extends BaseModel { + static collectionString = 'core/chat-message'; + id: number; + message: string; + timestamp: string; // TODO: Type for timestamp + user_id: number; + + constructor(id: number, message?: string, timestamp?: string, user_id?: number) { + super(id); + this.message = message; + this.timestamp = timestamp; + } + + public getCollectionString(): string { + return ChatMessage.collectionString; + } +} diff --git a/client/src/app/core/models/core/config.ts b/client/src/app/core/models/core/config.ts new file mode 100644 index 000000000..a729d2c68 --- /dev/null +++ b/client/src/app/core/models/core/config.ts @@ -0,0 +1,18 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class Config extends BaseModel { + static collectionString = 'core/config'; + id: number; + key: string; + value: Object; + + constructor(id: number, key?: string, value?: Object) { + super(id); + this.key = key; + this.value = value; + } + + public getCollectionString(): string { + return Config.collectionString; + } +} diff --git a/client/src/app/core/models/core/countdown.ts b/client/src/app/core/models/core/countdown.ts new file mode 100644 index 000000000..4d22045e1 --- /dev/null +++ b/client/src/app/core/models/core/countdown.ts @@ -0,0 +1,21 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class Countdown extends BaseModel { + static collectionString = 'core/countdown'; + id: number; + countdown_time: number; + default_time: number; + description: string; + + constructor(id: number, countdown_time?: number, default_time?: number, description?: string) { + super(id); + this.id = id; + this.countdown_time = countdown_time; + this.default_time = default_time; + this.description = description; + } + + public getCollectionString(): string { + return Countdown.collectionString; + } +} diff --git a/client/src/app/core/models/core/projector-message.ts b/client/src/app/core/models/core/projector-message.ts new file mode 100644 index 000000000..2db8a258a --- /dev/null +++ b/client/src/app/core/models/core/projector-message.ts @@ -0,0 +1,16 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class ProjectorMessage extends BaseModel { + static collectionString = 'core/projector-message'; + id: number; + message: string; + + constructor(id: number, message?: string) { + super(id); + this.message = message; + } + + public getCollectionString(): string { + return ProjectorMessage.collectionString; + } +} diff --git a/client/src/app/core/models/core/projector.ts b/client/src/app/core/models/core/projector.ts new file mode 100644 index 000000000..c2abfcf70 --- /dev/null +++ b/client/src/app/core/models/core/projector.ts @@ -0,0 +1,40 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class Projector extends BaseModel { + static collectionString = 'core/projector'; + id: number; + blank: boolean; + elements: Object; + height: number; + name: string; + projectiondefaults: BaseModel[]; + scale: number; + scroll: number; + width: number; + + constructor( + id: number, + blank?: boolean, + elements?: Object, + height?: number, + name?: string, + projectiondefaults?: BaseModel[], + scale?: number, + scroll?: number, + width?: number + ) { + super(id); + this.blank = blank; + this.elements = elements; + this.height = height; + this.name = name; + this.projectiondefaults = projectiondefaults; + this.scale = scale; + this.scroll = scroll; + this.width = width; + } + + public getCollectionString(): string { + return Projector.collectionString; + } +} diff --git a/client/src/app/core/models/core/tag.ts b/client/src/app/core/models/core/tag.ts new file mode 100644 index 000000000..ec24656f8 --- /dev/null +++ b/client/src/app/core/models/core/tag.ts @@ -0,0 +1,16 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class Tag extends BaseModel { + static collectionString = 'core/tag'; + id: number; + name: string; + + constructor(id: number, name?: string) { + super(id); + this.name = name; + } + + public getCollectionString(): string { + return Tag.collectionString; + } +} diff --git a/client/src/app/core/models/mediafiles/mediafile.ts b/client/src/app/core/models/mediafiles/mediafile.ts new file mode 100644 index 000000000..042287b81 --- /dev/null +++ b/client/src/app/core/models/mediafiles/mediafile.ts @@ -0,0 +1,37 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class Mediafile extends BaseModel { + static collectionString = 'mediafiles/mediafile'; + id: number; + filesize: string; + hidden: boolean; + media_url_prefix: string; + mediafile: Object; + timestamp: string; + title: string; + uploader_id: number; + + constructor( + id: number, + filesize?: string, + hidden?: boolean, + media_url_prefix?: string, + mediafile?: Object, + timestamp?: string, + title?: string, + uploader_id?: number + ) { + super(id); + this.filesize = filesize; + this.hidden = hidden; + this.media_url_prefix = media_url_prefix; + this.mediafile = mediafile; + this.timestamp = timestamp; + this.title = title; + this.uploader_id = uploader_id; + } + + public getCollectionString(): string { + return Mediafile.collectionString; + } +} diff --git a/client/src/app/core/models/motions/category.ts b/client/src/app/core/models/motions/category.ts new file mode 100644 index 000000000..6113dea25 --- /dev/null +++ b/client/src/app/core/models/motions/category.ts @@ -0,0 +1,18 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class Category extends BaseModel { + static collectionString = 'motions/category'; + id: number; + name: string; + prefix: string; + + constructor(id: number, name?: string, prefix?: string) { + super(id); + this.name = name; + this.prefix = prefix; + } + + public getCollectionString(): string { + return Category.collectionString; + } +} diff --git a/client/src/app/core/models/motions/motion-block.ts b/client/src/app/core/models/motions/motion-block.ts new file mode 100644 index 000000000..0ba789fca --- /dev/null +++ b/client/src/app/core/models/motions/motion-block.ts @@ -0,0 +1,18 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class MotionBlock extends BaseModel { + static collectionString = 'motions/motion-block'; + id: number; + agenda_item_id: number; + title: string; + + constructor(id: number, agenda_item_id?: number, title?: string) { + super(id); + this.agenda_item_id = agenda_item_id; + this.title = title; + } + + public getCollectionString(): string { + return MotionBlock.collectionString; + } +} diff --git a/client/src/app/core/models/motions/motion-change-reco.ts b/client/src/app/core/models/motions/motion-change-reco.ts new file mode 100644 index 000000000..d95b07f57 --- /dev/null +++ b/client/src/app/core/models/motions/motion-change-reco.ts @@ -0,0 +1,40 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class MotionChangeReco extends BaseModel { + static collectionString = 'motions/motion-change-recommendation'; + id: number; + creation_time: string; + line_from: number; + line_to: number; + motion_version_id: number; + other_description: string; + rejected: boolean; + text: string; + type: number; + + constructor( + id: number, + creation_time?: string, + line_from?: number, + line_to?: number, + motion_version_id?: number, + other_description?: string, + rejected?: boolean, + text?: string, + type?: number + ) { + super(id); + this.creation_time = creation_time; + this.line_from = line_from; + this.line_to = line_to; + this.motion_version_id = motion_version_id; + this.other_description = other_description; + this.rejected = rejected; + this.text = text; + this.type = type; + } + + public getCollectionString(): string { + return MotionChangeReco.collectionString; + } +} diff --git a/client/src/app/core/models/motions/motion.ts b/client/src/app/core/models/motions/motion.ts new file mode 100644 index 000000000..92a4c23d9 --- /dev/null +++ b/client/src/app/core/models/motions/motion.ts @@ -0,0 +1,70 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class Motion extends BaseModel { + static collectionString = 'motions/motion'; + id: number; + active_version: number; + agenda_item_id: number; + attachments_id: number[]; + category_id: number; + comments: Object; + identifier: string; + log_messages: Object[]; + motion_block_id: number; + origin: string; + parent_id: number; + polls: BaseModel[]; + recommendation_id: number; + state_id: number; + state_required_permission_to_see: string; + submitters: Object[]; + supporters_id: number[]; + tags_id: number[]; + versions: Object[]; + + constructor( + id: number, + active_version?: number, + agenda_item_id?: number, + attachments_id?: number[], + category_id?: number, + comments?: Object, + identifier?: string, + log_messages?: Object[], + motion_block_id?: number, + origin?: string, + parent_id?: number, + polls?: BaseModel[], + recommendation_id?: number, + state_id?: number, + state_required_permission_to_see?: string, + submitters?: Object[], + supporters_id?: number[], + tags_id?: number[], + versions?: Object[] + ) { + super(id); + this.active_version = active_version; + this.agenda_item_id = agenda_item_id; + this.attachments_id = attachments_id; + this.category_id = category_id; + this.comments = comments; + this.identifier = identifier; + this.log_messages = log_messages; + this.motion_block_id = motion_block_id; + 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.supporters_id = supporters_id; + this.tags_id = tags_id; + this.versions = versions; + } + + public getCollectionString(): string { + return Motion.collectionString; + } +} diff --git a/client/src/app/core/models/motions/workflow.ts b/client/src/app/core/models/motions/workflow.ts new file mode 100644 index 000000000..0767d0edc --- /dev/null +++ b/client/src/app/core/models/motions/workflow.ts @@ -0,0 +1,20 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class Workflow extends BaseModel { + static collectionString = 'motions/workflow'; + id: number; + first_state: number; + name: string; + states: Object[]; + + constructor(id: number, first_state?, name?, states?) { + super(id); + this.first_state = first_state; + this.name = name; + this.states = states; + } + + public getCollectionString(): string { + return Workflow.collectionString; + } +} diff --git a/client/src/app/core/models/topics/topic.ts b/client/src/app/core/models/topics/topic.ts new file mode 100644 index 000000000..0d1e62fb8 --- /dev/null +++ b/client/src/app/core/models/topics/topic.ts @@ -0,0 +1,22 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class Topic extends BaseModel { + static collectionString = 'topics/topic'; + id: number; + agenda_item_id: number; + attachments_id: number[]; + text: string; + title: string; + + constructor(id: number, agenda_item_id?: number, attachments_id?: number[], text?: string, title?: string) { + super(id); + this.agenda_item_id = agenda_item_id; + this.attachments_id = attachments_id; + this.text = text; + this.title = title; + } + + public getCollectionString(): string { + return Topic.collectionString; + } +} diff --git a/client/src/app/core/models/group.ts b/client/src/app/core/models/users/group.ts similarity index 61% rename from client/src/app/core/models/group.ts rename to client/src/app/core/models/users/group.ts index 84daab72f..2c5595547 100644 --- a/client/src/app/core/models/group.ts +++ b/client/src/app/core/models/users/group.ts @@ -1,18 +1,19 @@ -import { BaseModel } from './baseModel'; +import { BaseModel } from 'app/core/models/baseModel'; export class Group extends BaseModel { + static collectionString = 'users/group'; id: number; name: string; permissions: string[]; //TODO permissions could be an own model? constructor(id: number, name?: string, permissions?: string[]) { - super(); + super(id); this.id = id; this.name = name; this.permissions = permissions; } - getCollectionString(): string { - return 'users/group'; + public getCollectionString(): string { + return Group.collectionString; } } diff --git a/client/src/app/core/models/users/personal-note.ts b/client/src/app/core/models/users/personal-note.ts new file mode 100644 index 000000000..b87aa33d4 --- /dev/null +++ b/client/src/app/core/models/users/personal-note.ts @@ -0,0 +1,18 @@ +import { BaseModel } from 'app/core/models/baseModel'; + +export class PersonalNote extends BaseModel { + static collectionString = 'users/personal-note'; + id: number; + notes: Object; + user_id: number; + + constructor(id: number, notes?: Object, user_id?: number) { + super(id); + this.notes = notes; + this.user_id = user_id; + } + + public getCollectionString(): string { + return PersonalNote.collectionString; + } +} diff --git a/client/src/app/core/models/user.ts b/client/src/app/core/models/users/user.ts similarity index 74% rename from client/src/app/core/models/user.ts rename to client/src/app/core/models/users/user.ts index 3a2aa755c..343664c03 100644 --- a/client/src/app/core/models/user.ts +++ b/client/src/app/core/models/users/user.ts @@ -1,9 +1,9 @@ -import { BaseModel } from './baseModel'; +import { BaseModel } from 'app/core/models/baseModel'; // import { DS } from 'app/core/services/DS.service'; export class User extends BaseModel { - //TODO potentially make them private and use getters and setters + static collectionString = 'users/user'; id: number; username: string; title: string; @@ -40,8 +40,7 @@ export class User extends BaseModel { is_active?: boolean, default_password?: string ) { - super(); - this.id = id; + super(id); this.username = username; this.title = title; this.first_name = first_name; @@ -59,15 +58,7 @@ export class User extends BaseModel { this.default_password = default_password; } - getCollectionString(): string { - return 'users/user'; + public getCollectionString(): string { + return User.collectionString; } - - // // convert an serialized version of the User to an instance of the class - // static fromJSON(jsonString: {}): User { - // // create an instance of the User class - // let user = Object.create(User.prototype); - // // copy all the fields from the json object - // return Object.assign(user, jsonString); - // } } diff --git a/client/src/app/core/services/DS.service.spec.ts b/client/src/app/core/services/DS.service.spec.ts index 456adea44..2d14b751a 100644 --- a/client/src/app/core/services/DS.service.spec.ts +++ b/client/src/app/core/services/DS.service.spec.ts @@ -1,11 +1,11 @@ import { TestBed, inject } from '@angular/core/testing'; -import { DS } from './DS.service'; +import { DataStoreService } from './DS.service'; describe('DS', () => { beforeEach(() => { TestBed.configureTestingModule({ - providers: [DS] + providers: [DataStoreService] }); }); diff --git a/client/src/app/core/services/DS.service.ts b/client/src/app/core/services/DS.service.ts index afc722a2d..62d71d615 100644 --- a/client/src/app/core/services/DS.service.ts +++ b/client/src/app/core/services/DS.service.ts @@ -10,11 +10,11 @@ interface Collection { [id: number]: BaseModel; } -interface DataStore { +interface Storrage { [collectionString: string]: Collection; } -//Todo: DRY. This is a copy from /authService. probably repository service necessary +// Todo: DRY. This is a copy from /authService. probably repository service necessary const httpOptions = { withCredentials: true, headers: new HttpHeaders({ @@ -25,67 +25,81 @@ const httpOptions = { @Injectable({ providedIn: 'root' }) -export class DS { - private store: DataStore = {}; +export class DataStoreService { + // needs to be static cause becauseusing dependency injection, services are unique for a scope. + private static store: Storrage = {}; constructor(private http: HttpClient) {} - get(collectionString: string, id: ModelId): BaseModel | undefined { - const collection: Collection = this.store[collectionString]; - if (!collection) { - return; - } - const model: BaseModel = collection[id]; - return model; - } + // 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 = []; - //todo return observable of base model - getAll(collectionString: string): BaseModel[] { - const collection: Collection = this.store[collectionString]; if (!collection) { return []; } - return Object.values(collection); + + 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 - filter(collectionString: string, callback): BaseModel[] { - return this.getAll(collectionString).filter(callback); + // example: this.DS.filder(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); } - inject(model: BaseModel): void { - const collectionString = model.getCollectionString(); - console.log('the collection string: ', 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 this.store[collectionString] === 'undefined') { - this.store[collectionString] = {}; - console.log('made new collection: ', collectionString); - } - this.store[collectionString][model.id] = model; - console.log('injected ; ', model); - } - - injectMany(models: BaseModel[]): void { + // 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 => { - this.inject(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; + // console.log('add model ', model, ' into Datastore'); }); } - eject(collectionString: string, id: ModelId) { - if (this.store[collectionString]) { - delete this.store[collectionString][id]; - } - } - - ejectMany(collectionString: string, ids: ModelId[]) { + // 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 => { - this.eject(collectionString, id); + if (DataStoreService.store[Type.collectionString]) { + delete DataStoreService.store[Type.collectionString][id]; + console.log(`did remove "${id}" from Datastore "${Type.collectionString}"`); + } }); } @@ -94,29 +108,27 @@ export class DS { if (!model.id) { throw new ImproperlyConfiguredError('The model must have an id!'); } - const collectionString: string = model.getCollectionString(); - //TODO not tested - return this.http.post(collectionString + '/', model, httpOptions).pipe( + // TODO not tested + return this.http.post(model.getCollectionString() + '/', model, httpOptions).pipe( tap(response => { console.log('the response: ', response); - this.inject(model); + this.add(model); }) ); } - // TODO remove the any there and in BaseModel. - delete(model: BaseModel): Observable { + // send a http request to the server to delete the given model + delete(model: BaseModel): Observable { if (!model.id) { throw new ImproperlyConfiguredError('The model must have an id!'); } - const collectionString: string = model.getCollectionString(); - //TODO not tested - return this.http.post(collectionString + '/', model, httpOptions).pipe( + // TODO not tested + return this.http.post(model.getCollectionString() + '/', model, httpOptions).pipe( tap(response => { console.log('the response: ', response); - this.eject(collectionString, model.id); + this.remove(model, model.id); }) ); } diff --git a/client/src/app/core/services/auth.service.ts b/client/src/app/core/services/auth.service.ts index cc877f34d..7a5a1c3cc 100644 --- a/client/src/app/core/services/auth.service.ts +++ b/client/src/app/core/services/auth.service.ts @@ -3,7 +3,7 @@ import { HttpClient, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angul import { Observable, of, throwError } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; -import { User } from 'app/core/models/user'; +import { User } from 'app/core/models/users/user'; const httpOptions = { withCredentials: true, diff --git a/client/src/app/core/services/autoupdate.service.spec.ts b/client/src/app/core/services/autoupdate.service.spec.ts new file mode 100644 index 000000000..ca22f8700 --- /dev/null +++ b/client/src/app/core/services/autoupdate.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { AutoupdateService } from './autoupdate.service'; + +describe('AutoupdateService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [AutoupdateService] + }); + }); + + it('should be created', inject([AutoupdateService], (service: AutoupdateService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/client/src/app/core/services/autoupdate.service.ts b/client/src/app/core/services/autoupdate.service.ts new file mode 100644 index 000000000..7d26d8bb2 --- /dev/null +++ b/client/src/app/core/services/autoupdate.service.ts @@ -0,0 +1,126 @@ +import { Injectable } from '@angular/core'; +import { BaseComponent } from 'app/base.component'; +import { WebsocketService } from './websocket.service'; + +import { BaseModel } from 'app/core/models/baseModel'; +import { Item } from 'app/core/models/agenda/item'; +import { Assignment } from 'app/core/models/assignments/assignment'; +import { ChatMessage } from 'app/core/models/core/chat-message'; +import { Config } from 'app/core/models/core/config'; +import { Countdown } from 'app/core/models/core/countdown'; +import { ProjectorMessage } from 'app/core/models/core/projector-message'; +import { Projector } from 'app/core/models/core/projector'; +import { Tag } from 'app/core/models/core/tag'; +import { Mediafile } from 'app/core/models/mediafiles/mediafile'; +import { Category } from 'app/core/models/motions/category'; +import { MotionBlock } from 'app/core/models/motions/motion-block'; +import { MotionChangeReco } from 'app/core/models/motions/motion-change-reco'; +import { Motion } from 'app/core/models/motions/motion'; +import { Workflow } from 'app/core/models/motions/workflow'; +import { Topic } from 'app/core/models/topics/topic'; +import { Group } from 'app/core/models/users/group'; +import { PersonalNote } from 'app/core/models/users/personal-note'; +import { User } from 'app/core/models/users/user'; + +/** + * Basically handles the inital update and all automatic updates. + */ + +@Injectable({ + providedIn: 'root' +}) +export class AutoupdateService extends BaseComponent { + private socket; + + constructor(private websocketService: WebsocketService) { + super(); + this.socket = this.websocketService.connect(); + this.socket.subscribe(response => { + this.storeResponse(response); + }); + } + + //create models out of socket answer + storeResponse(socketResponse): void { + socketResponse.forEach(model => { + switch (model.collection) { + case 'core/projector': { + 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; + } + } + }); + } +} diff --git a/client/src/app/site/site.component.ts b/client/src/app/site/site.component.ts index e3d23f3fc..0fd7dc325 100644 --- a/client/src/app/site/site.component.ts +++ b/client/src/app/site/site.component.ts @@ -3,34 +3,28 @@ import { Router } from '@angular/router'; import { BreakpointObserver, Breakpoints, BreakpointState } from '@angular/cdk/layout'; import { AuthService } from 'app/core/services/auth.service'; -import { WebsocketService } from 'app/core/services/websocket.service'; import { Subject } from 'rxjs'; import { tap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; //showcase - -//into own service -import { DS } from 'app/core/services/DS.service'; -import { User } from 'app/core/models/user'; -import { Group } from 'app/core/models/group'; -import { BaseModel } from '../core/models/baseModel'; +import { BaseComponent } from 'app/base.component'; @Component({ selector: 'app-site', templateUrl: './site.component.html', styleUrls: ['./site.component.css'] }) -export class SiteComponent implements OnInit { +export class SiteComponent extends BaseComponent implements OnInit { isMobile = false; constructor( private authService: AuthService, - private websocketService: WebsocketService, private router: Router, private breakpointObserver: BreakpointObserver, - private translate: TranslateService, - private dS: DS - ) {} + private translate: TranslateService + ) { + super(); + } ngOnInit() { this.breakpointObserver @@ -43,47 +37,12 @@ export class SiteComponent implements OnInit { } }); - // connect to a the websocket - const socket = this.websocketService.connect(); - - // subscribe to the socket - socket.subscribe(response => { - console.log('log : ', response); // will contain all the config variables - this.storeResponse(response); - }); - - // basically everything needed for AutoUpdate - socket.next(val => { - console.log('socket.next: ', val); - }); - //get a translation via code: use the translation service this.translate.get('Motions').subscribe((res: string) => { console.log(res); }); } - //test. will move to an own service later - //create models out of socket answer - storeResponse(socketResponse): void { - socketResponse.forEach(model => { - switch (model.collection) { - case 'users/group': { - this.dS.inject(BaseModel.fromJSON(model.data, Group)); - break; - } - case 'users/user': { - this.dS.inject(BaseModel.fromJSON(model.data, User)); - break; - } - default: { - console.log('collection: "' + model.collection + '" is not yet parsed'); - break; - } - } - }); - } - selectLang(lang: string): void { console.log('selected langauge: ', lang); console.log('get Langs : ', this.translate.getLangs()); diff --git a/client/src/app/site/start/start.component.html b/client/src/app/site/start/start.component.html index 3dc2f62e7..8dc17742c 100644 --- a/client/src/app/site/start/start.component.html +++ b/client/src/app/site/start/start.component.html @@ -6,5 +6,9 @@ {{'Welcome to OpenSlides' | translate}}

Hello user

- - + +
+ +
+ + \ No newline at end of file diff --git a/client/src/app/site/start/start.component.ts b/client/src/app/site/start/start.component.ts index bac06cb82..d0d7cc979 100644 --- a/client/src/app/site/start/start.component.ts +++ b/client/src/app/site/start/start.component.ts @@ -2,9 +2,11 @@ import { Component, OnInit } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { BaseComponent } from 'app/base.component'; +import { TranslateService } from '@ngx-translate/core'; //showcase + // for testing the DS and BaseModel -import { User } from 'app/core/models/user'; -import { DS } from 'app/core/services/DS.service'; +import { User } from 'app/core/models/users/user'; +import { Group } from 'app/core/models/users/group'; @Component({ selector: 'app-start', @@ -12,42 +14,49 @@ import { DS } from 'app/core/services/DS.service'; styleUrls: ['./start.component.css'] }) export class StartComponent extends BaseComponent implements OnInit { - private dS: DS; - - constructor(titleService: Title, dS: DS) { + constructor(titleService: Title, private translate: TranslateService) { super(titleService); - this.dS = dS; } ngOnInit() { super.setTitle('Start page'); } - test() { - // This can be a basic unit test ;) - // console.log(User.get(1)); - const user1: User = new User(32, 'testuser'); - const user2: User = new User(42, 'testuser 2'); - - console.log(`User1 | ID ${user1.id}, Name: ${user1.username}`); - console.log(`User2 | ID ${user2.id}, Name: ${user2.username}`); - - this.dS.inject(user1); - this.dS.inject(user2); - console.log('All users = ', this.dS.getAll('users/user')); + //quick testing of some data store functions + DataStoreTest() { + console.log('add a user to dataStore'); + this.DS.add(new User(100)); + console.log('add three users to dataStore'); + this.DS.add(new User(200), new User(201), new User(202)); + console.log('use the spread operator "..." to add an array'); + const userArray = []; + for (let i = 300; i < 400; i++) { + userArray.push(new User(i)); + } + this.DS.add(...userArray); console.log('try to get user with ID 1:'); - const user1fromStore = this.dS.get('users/user', 1); + const user1fromStore = this.DS.get(User, 1); console.log('the user: ', user1fromStore); - console.log('inject many:'); - this.dS.injectMany([user1, user2]); + console.log('remove a single user:'); + this.DS.remove(User, 100); + console.log('remove more users'); + this.DS.remove(User, 200, 201, 202); + console.log('remove an array of users'); + this.DS.remove(User, ...[321, 363, 399]); - console.log('eject user 1'); - this.dS.eject('users/user', user1.id); - console.log(this.dS.getAll('users/user')); + console.log('test filter: '); + console.log(this.DS.filter(User, user => user.id === 1)); + } - // console.log(User.filter(user => user.id === 1)); - // console.log(User.filter(user => user.id === 2)); + giveDataStore() { + this.DS.printWhole(); + } + + // shows how to use synchronous translations: + TranslateTest() { + console.log('lets translate the word "motion" in the current in the current lang'); + console.log('Motions in ' + this.translate.currentLang + ' is ' + this.translate.instant('Motions')); } }